// read a block of the given index from the current LBX file into to the given LBXBlock object public void read(LBXBlock block, int index) { if (_fileIn != null) { if (index < _blockCount) { // some LBX files have 0-length blocks, so check for that if (_blockSizes[index] > 0) { _fileIn.BaseStream.Position = _blockOffsets[index]; block.data = _fileIn.ReadBytes(_blockSizes[index]); } else { block.data = null; } block.filePath = _filePath; block.index = index; block.offset = _blockOffsets[index]; block.size = _blockSizes[index]; } else { throw new IndexOutOfRangeException("Requested block index out of bounds."); } } else { throw new Exception("No LBX file opened."); } }
public void load(string filePath, int index) { LBXReader lbx = new LBXReader(filePath); LBXBlock block = new LBXBlock(); lbx.read(block, index); load(block); lbx.close(); }
private void loadPalettes() { if (gameFolderPathTextBox.Text.Length > 0 && Directory.Exists(gameFolderPathTextBox.Text)) { // populate the list box with the font .LBX files and the first several blocks. // all of the game fonts are stored here palettesListBox.Items.Clear(); //palettesListBox.Items.Add("FONTS.LBX:000"); palettesListBox.Items.Add("FONTS.LBX:001"); palettesListBox.Items.Add("FONTS.LBX:002"); palettesListBox.Items.Add("FONTS.LBX:003"); palettesListBox.Items.Add("FONTS.LBX:004"); palettesListBox.Items.Add("FONTS.LBX:005"); palettesListBox.Items.Add("IFONTS.LBX:000"); palettesListBox.Items.Add("IFONTS.LBX:001"); palettesListBox.Items.Add("IFONTS.LBX:002"); palettesListBox.Items.Add("IFONTS.LBX:003"); palettesListBox.Items.Add("IFONTS.LBX:004"); // a Palette[] holds the actual Palette objects palettes = new Palette[palettesListBox.Items.Count]; LBXBlock block = new LBXBlock(); // load those for (int i = 0; i < palettes.Length; ++i) { // create new palette, then parse out the filename from the block index palettes[i] = new Palette(); string listItem = (string)palettesListBox.Items[i]; string fontFileName = listItem.Substring(0, listItem.IndexOf(":")); int blockIndex = Int32.Parse(listItem.Substring(listItem.IndexOf(":") + 1, listItem.Length - listItem.IndexOf(":") - 1)); lbxReader.close(); lbxReader.open(gameFolderPathTextBox.Text + "\\" + fontFileName); lbxReader.read(block, blockIndex); palettes[i].load(block); } lbxReader.close(); // set the second font as the current, which should also trigger the event to // update currentPaletteIndex and redraw the palette view image palettesListBox.SetSelected(2, true); } }
/// <summary> /// Whoa, does this work? /// <para>paratextwee?</para> /// </summary> private void updateLBX() { LBXBlock block = new LBXBlock(); // load new LBX lbxReader.close(); lbxReader.open(gameFolderPathTextBox.Text + "\\" + currentLBXFileName); currentLBXBlock = 0; // update LBX file size label long size = lbxReader.getFileSize(); if (size < 1048576) { float kbCount = (float)size / 1024.0f; currentLBXFileSizeLabel.Text = String.Format("{0:G4} KB", kbCount); } else { float mbCount = (float)size / 1048576.0f; currentLBXFileSizeLabel.Text = String.Format("{0:G4} MB", mbCount); } // enable or disable arrows accordingly if (lbxReader.getBlockCount() <= 1) { prevLBXBlockButton.Enabled = false; nextLBXBlockButton.Enabled = false; } else { prevLBXBlockButton.Enabled = false; nextLBXBlockButton.Enabled = true; } updateBlock(); }
/// <summary> /// Load a full 256-entry palette from the data contained in the given LBXBlock. /// </summary> /// <param name="block"></param> public void load(LBXBlock block) { if (block != null) { for (int i = 0; i < 256; ++i) { /* * // convert ARGB [0,63] to RGBA [0,255] * uint color = ((uint)block.data[i * 4 + 1] << 2) + * ((uint)block.data[i * 4 + 2] << 10) + * ((uint)block.data[i * 4 + 3] << 18) + * ((uint)block.data[i * 4 + 0] << 24); * */ // convert ARGB [0,63] to ARGB [0,255] uint a = (uint)block.data[i * 4 + 0]; uint r = (uint)block.data[i * 4 + 1] << 2; uint g = (uint)block.data[i * 4 + 2] << 2; uint b = (uint)block.data[i * 4 + 3] << 2; //if (a == 1) a = 255; //_palette[i] = r + (g << 8) + (b << 16) + (a << 24); // BGRA ???????? this._palette[i] = b + (g << 8) + (r << 16) + (a << 24); } this.size = 256; this.offset = 0; } else { throw new Exception("Null block given to load"); } }
public void load(LBXBlock block) { if (block.size > 0 && block.data != null) { free(); BinaryReader data = new BinaryReader(new MemoryStream(block.data)); // read header information _header.width = data.ReadUInt16(); _header.height = data.ReadUInt16(); _header.zero = data.ReadUInt16(); _header.frameCount = data.ReadUInt16(); _header.frameDelay = data.ReadUInt16(); _header.flags = data.ReadUInt16(); // only support 640x480 images for now if (_header.width <= 640 && _header.height <= 480) { _frames = new ImageFrame[_header.frameCount]; // read frame offsets, plus the extra at the end uint start, end; start = data.ReadUInt32(); for (int i = 0; i < _header.frameCount; ++i) { end = data.ReadUInt32(); _frames[i].size = end - start; _frames[i].offset = start; start = end; } // if there's an internal palette, it follows the offsets and precedes the frame data if ((_header.flags & (ushort)ImageFlags.INTERNAL_PALETTE) != 0) { // color shift specifies where to start overwriting the palette with colorCount colors ushort colorShift = data.ReadUInt16(); ushort colorCount = data.ReadUInt16(); if (colorCount > 256 || colorShift > 255) { throw new Exception("Invalid internal palette"); } else { uint[] buffer = new uint[colorCount]; for (int i = 0; i < colorCount; ++i) { // read in ARGB [0, 63] and save as ARGB [0, 255] uint a = data.ReadByte(); uint r = (uint)data.ReadByte() << 2; uint g = (uint)data.ReadByte() << 2; uint b = (uint)data.ReadByte() << 2; //if (a == 1) // a = 255; //else if (a == 0) a = 255; uint color = b + (g << 8) + (r << 16) + (a << 24); buffer[i] = color; } _internalPalette = new Palette(); _internalPalette.load(buffer, colorShift); _updatePalettes(); } } // read in frame data for (int f = 0; f < _frames.Length; ++f) { data.BaseStream.Position = _frames[f].offset; // first byte of the "frame header" should be 1. // if not, invalid frame! current frame's buffer remains null if (data.ReadUInt16() != 1) { _frames[f].buffer = null; _frames[f].bitmap = null; continue; } else { //data.ReadUInt16(); //_frames[f].bitmap = new Bitmap(_header.width, _header.height); _frames[f].buffer = new byte[_header.width * _header.height]; _frames[f].bitmap = new Bitmap(_header.width, _header.height); short pixelCount, startY, xIndent, yIndent; short cx = 0, cy = 0; // get the starting Y position for drawing startY = data.ReadInt16(); cy += startY; // draw loop until break (yIdent == 1000) or we go beyond the frame size (shouldn't happen normally?) while ((data.BaseStream.Position - _frames[f].offset) < _frames[f].size) { pixelCount = data.ReadInt16(); // if the pixel count is > 0, this is a horizontal run if (pixelCount > 0) { xIndent = data.ReadInt16(); cx += xIndent; // copy the run of pixels data.Read(_frames[f].buffer, cx + cy * (short)_header.width, pixelCount); cx += pixelCount; // if the pixel count was odd, skip 1 extra byte! if ((pixelCount % 2) != 0) { data.ReadByte(); } } // else, it's a y-indent with no drawing else { yIndent = data.ReadInt16(); // if y-indent is 1000, it means quit? if (yIndent != 1000) { cy += yIndent; cx = 0; } else { break; } } } } } } else { throw new Exception("image larger than 640x480 (?)"); } } }
/// <summary> /// Loads a particular font (out of 6) from the given block. /// </summary> /// <param name="block"></param> /// <param name="index"></param> public void load(LBXBlock block, int index) { if (block == null || index < 0 || index > 5) { throw new Exception("Invalid font load parameters."); } else if (block.data == null) { throw new Exception("Font block data is null"); } else { // I love these things. BinaryReader data = new BinaryReader(new MemoryStream(block.data)); // jump to the start of font[index]'s glyph widths; // 256 glyphs, 1 byte per width, total of 0x100 bytes per font data.BaseStream.Position = 0x59C + (long)index * 0x100; byte[] glyphWidths = new byte[256]; this._maxGlyphWidth = 0; // read the widths for (int i = 0; i < 256; ++i) { glyphWidths[i] = data.ReadByte(); if (glyphWidths[i] > this._maxGlyphWidth) { this._maxGlyphWidth = glyphWidths[i]; } } // jump to the start of font[index]'s glyph offsets; // 256 glyphs, 4 bytes per offset, total of 0x400 bytes per font data.BaseStream.Position = 0xB9C + (long)index * 0x400; uint[] glyphOffsets = new uint[256]; uint[] glyphDataSize = new uint[256]; // read the offsets for (int i = 0; i < 256; ++i) { glyphOffsets[i] = data.ReadUInt32(); // compute the data size as the difference between offsets if (i > 0) { glyphDataSize[i - 1] = glyphOffsets[i] - glyphOffsets[i - 1]; } } // don't forget the last glyphDataSize //glyphDataSize[255] = data.ReadUInt32() - glyphOffsets[255]; glyphDataSize[255] = 0; // the max glyph width is used to generate the temporary buffer into which // font data is decoded. it's assumed no glyph would be taller than twice // the max width, but this isn't necessarily so. byte[,] glyphBuffer = new byte[maxGlyphWidth, maxGlyphWidth * 2]; int cx, cy; int maxW, maxH; // jagged array of 2D arrays, mmmm. this._glyphData = new byte[256][, ]; // this will track the distinct palette indices that this font uses this._glyphColors = new byte[256]; this._maxGlyphHeight = 0; // read in the font data! for (int i = 0; i < 256; ++i) { // all 6 fonts' offsets are relative to the start of the font data at 0x239C data.BaseStream.Position = 0x239C + glyphOffsets[i]; cx = 0; cy = 0; maxW = 0; maxH = 0; //Array.Clear(glyphBuffer, 0, glyphBuffer.Length); for (int y = 0; y < glyphBuffer.GetLength(1); ++y) { for (int x = 0; x < glyphBuffer.GetLength(0); ++x) { glyphBuffer[x, y] = 0xFF; } } try { while ((data.BaseStream.Position - 0x239C - glyphOffsets[i]) < glyphDataSize[i]) { byte val = data.ReadByte(); // 0x00-0x7f: color of next pixel if (val <= 0x7F) { if (this._glyphColors[val] == 0) { this._glyphColors[val]++; } if (cx >= 0 && cx < glyphBuffer.GetLength(0) && cy >= 0 && cy < glyphBuffer.GetLength(1)) { glyphBuffer[cx, cy] = val; // track the total pixel-drawing extent of this glyph if (cx > maxW) { maxW = cx; } if (cy > maxH) { maxH = cy; } // increment after tracking! cx++; } } // 0x80: skip to beginning of next line else if (val == 0x80) { cx = 0; cy++; } // 0x8n: skip n pixels else { cx += (val & 0x0F); } } } catch (Exception ex) { // } // now allocate the properly sized mini-buffer for this glyph and copy from the temp buffer if (true)//maxW > 0 && maxH > 0) { this._glyphData[i] = new byte[maxW + 1, maxH + 1]; for (int x = 0; x < (maxW + 1); ++x) { for (int y = 0; y < (maxH + 1); ++y) { this._glyphData[i][x, y] = glyphBuffer[x, y]; } } // track the max glyph height if ((maxH + 1) > this._maxGlyphHeight) { this._maxGlyphHeight = maxH + 1; } } } } }
private void testButton_Click(object sender, EventArgs e) { LBXBlock block = new LBXBlock(); // FONTS.LBX and IFONTS.LBX both contain palettes and fonts lbxReader.close(); lbxReader.open(gameFolderPathTextBox.Text + "\\" + "FONTS.LBX"); // load a palette to use lbxReader.read(block, 1); Palette pal = new Palette(); // force the last palette entry to white block.data[0xFF * 4 + 0] = 0xFF; block.data[0xFF * 4 + 1] = 0x33; block.data[0xFF * 4 + 2] = 0x33; block.data[0xFF * 4 + 3] = 0x33; pal.load(block); lbxReader.close(); lbxReader.open(gameFolderPathTextBox.Text + "\\" + "IFONTS.LBX"); lbxReader.read(block, 0); Font font = new Font(); // FONT THING font.load(block, 5); font.palette = pal; Bitmap b = new Bitmap(640, 480); Graphics gg = Graphics.FromImage(b); gg.Clear(Color.White); /* * for (int g = 32; g < 128; ++g) * { * int x = (g % 16) * (font.maxGlyphWidth + 1) + 1; * int y = (g / 16) * (font.maxGlyphHeight + 1) + 1; * font.renderGlyph((byte)g, b, x, y); * } */ font.renderString("Hello, Xus. :)", b, 10, 10); font.renderString(@"This seems to be working more or less? 2 + 2 = 4 (usually) \o/ :P", b, 10, 200); pictureBox1.Image = b; pictureBox1.Refresh(); string colorList = ""; for (int i = 0; i < 128; ++i) { if (font.glyphColors[i] > 0) { colorList = colorList + i.ToString() + " "; } } fontColorsListTextBox.Text = colorList; lbxReader.close(); }
private void updateBlock() { LBXBlock block = new LBXBlock(); // make sure an LBX file is loaded first (avoids crash on startup) if (currentLBXFileName.Length > 0) { // update block counter label currentLBXBlockLabel.Text = currentLBXBlock.ToString() + " / " + (lbxReader.getBlockCount() - 1).ToString(); lbxReader.read(block, currentLBXBlock); if (block.data != null) { // load new block as an image currentImage = new Image(); currentImage.setExternalPalette(palettes[currentPaletteIndex]); currentImage.load(block); currentImageFrame = 0; // update image header display with whatever was read imageHeaderWidthTextBox.Text = String.Format("{0:X2} {1:X2} ({2})", currentImage.getWidth() & 0xFF, (currentImage.getWidth() & 0xFF00) >> 8, currentImage.getWidth()); imageHeaderHeightTextBox.Text = String.Format("{0:X2} {1:X2} ({2})", currentImage.getHeight() & 0xFF, (currentImage.getHeight() & 0xFF00) >> 8, currentImage.getHeight()); imageHeaderZeroTextBox.Text = String.Format("{0:X2} {1:X2}", currentImage.getHeader().zero & 0xFF, (currentImage.getHeader().zero & 0xFF00) >> 8); imageHeaderFrameCountTextBox.Text = String.Format("{0:X2} {1:X2} ({2})", currentImage.getHeader().frameCount & 0xFF, (currentImage.getHeader().frameCount & 0xFF00) >> 8, currentImage.getHeader().frameCount); imageHeaderFrameDelayTextBox.Text = String.Format("{0:X2} {1:X2} ({2})", currentImage.getHeader().frameDelay & 0xFF, (currentImage.getHeader().frameDelay & 0xFF00) >> 8, currentImage.getHeader().frameDelay); imageHeaderFlagsTextBox.Text = formatImageFlagsString(currentImage.getHeader().flags); // if there's an internal palette currently locked, render with that if (internalPaletteLockButton.Checked) { currentImage.render(lockedInternalPalette); } else { currentImage.render(); if (currentImage.getInternalPalette() != null) { internalPaletteLockButton.Enabled = true; } else { internalPaletteLockButton.Enabled = false; } } updatePalettes(); // enable/disable the next/prev frame buttons as appropriate if (currentImage.getHeader().frameCount <= 1) { prevImageFrameButton.Enabled = false; nextImageFrameButton.Enabled = false; } else { prevImageFrameButton.Enabled = false; nextImageFrameButton.Enabled = true; } // update the track bar with an appropriate number of zoom levels: // computed as either log_2(640 / width) or log_2(480 / height) depending on which is greater; // determine this via the aspect ratio float ar = (float)currentImage.getWidth() / (float)currentImage.getHeight(); float range; // if the image is tall and skinny, use the height if (ar < (4.0f / 3.0f)) { range = 480.0f / (float)currentImage.getHeight(); } else { range = 640.0f / (float)currentImage.getWidth(); } // add 1 tick mark for the stretch-to-fit option at the end int tickCount = (int)Math.Floor(Math.Log(range, 2.0)) + 1; // snap the old trackbar value to the new range if (imageZoomTrackBar.Value > tickCount) { imageZoomTrackBar.Value = tickCount; } imageZoomTrackBar.Minimum = 0; // start at 2^0 or 1x zoom imageZoomTrackBar.Maximum = tickCount; imageZoomTrackBar.TickFrequency = 1; //imageZoomTrackBar.Value = 0; imageZoomTrackBar_ValueChanged(null, null); } } }