/// <summary> /// Load a level from a stream. The stream should be positioned at the beginning of the level data /// </summary> /// <param name="s">The stream</param> public Level(Stream s) { // Saved because tileset loading won't return the stream to its original position long pos = s.Position; this.tileset = new Tileset(s); s.Seek(pos, SeekOrigin.Begin); // Go to the pointer specified by the first two bytes (The level background data) s.Seek(SNES.toPC(s.ReadByte() | s.ReadByte() << 8, 0x84), SeekOrigin.Begin); // Move past the first two bytes, which specify nothing as far as I know s.Seek(0x2, SeekOrigin.Current); // BG 1 is interleaved across two blocks of compressed data byte[] BG1_1 = BMFMCompress.Decompress(s); byte[] BG1_3 = BMFMCompress.Decompress(s); BMFMCompress.Interleave(BG1_1, BG1_3); // Skip the tileset pointer and go to the BG 2 stuff s.Seek(6, SeekOrigin.Current); // BG 2 is not interleaved (becuase it uses 1 byte instead of 2 for tile indexes) byte[] BG2_1 = BMFMCompress.Decompress(s); // Get the large BG tile arrangement data byte[] BG1_2 = BMFMCompress.Decompress(s); byte[] BG2_2 = BMFMCompress.Decompress(s); // Create the backgrounds BG1 = new LevelBG(BG1_1, BG1_2); BG2 = new LevelBG(BG2_1, BG2_2, true); }
/// <summary> /// Loads a new palette from a stream. Must be positioned at the palette data /// </summary> /// <param name="s">The stream</param> public Palette(Stream s) { // The palette needs space for 0x200 bytes or 0x100 colors byte[] data = new byte[0x200]; // First two bytes indicate a pointer to the actual colors int pointer = s.ReadByte() | s.ReadByte() << 8; // When this is zero, all the colors have been loaded while (pointer != 0) { // Next byte is where this palette goes in the overall palette int index = s.ReadByte() * 0x10; // It will be necessary to return here after each loop long position = s.Position; // Go to the color data s.Seek(SNES.toPC(pointer, 0x8A), SeekOrigin.Begin); // Two bytes for how many total bytes will be in the output (not how many are in the input) int numBytes = s.ReadByte() | s.ReadByte() << 8; while (numBytes > 0) { // Two bytes are automatically skipped (remain 0) every 0x20 bytes, but not every 0x200 bytes if ((index & 0x1FF) != 0 && (index & 0x1F) == 0) { index += 2; } // Load the next color or fill int next = s.ReadByte() | s.ReadByte() << 8; numBytes -= 2; // If the high bit is set, then it indicates a fill with 0s if ((next & 0x8000) > 0) { // The number of zero bytes is the low byte * 2 int numZeros = (next & 0xFF) * 2; // Make sure not to exceed the number of bytes if (numZeros > numBytes) { numZeros = numBytes; } // Copy them for (int i = 0; i < numZeros; i++) { data[index++] = 0; } numBytes -= numZeros; } else { // Otherwise, simply copy those two bytes into the palette data[index++] = (byte)(next & 0xFF); data[index++] = (byte)((next & 0xFF00) >> 8); } } // Return to the next color block s.Seek(position, SeekOrigin.Begin); // Load the pointer for the next check pointer = s.ReadByte() | s.ReadByte() << 8; } // Convert all of this into colors that can actually be used colors = new Color[0x100]; for (int i = 0; i < data.Length; i += 2) { colors[i / 2] = SNES.toRGB(data[i], data[i + 1]); } }
/// <summary> /// Loads a tileset from a stream. The stream must be positioned at the beginning of the level data /// </summary> /// <param name="s">The stream that the tileset is loaded from.</param> public Tileset(Stream s) { // It will be necessary to return to the level data long pos = s.Position; // Skip past the level data to the palette s.Seek(2, SeekOrigin.Current); // Go to the palette int palPtr = s.ReadByte() | s.ReadByte() << 8; s.Seek(SNES.toPC(palPtr, 0x81), SeekOrigin.Begin); // Load the palette p = new Palette(s); SNES.MakePltTransparent(p.colors); // Return to the beginning of the level data s.Seek(pos, SeekOrigin.Begin); // Go to the level data int BGPointer = s.ReadByte() | s.ReadByte() << 8; s.Seek(SNES.toPC(BGPointer, 0x84), SeekOrigin.Begin); // Skip to the first page of graphics s.Seek(0x1e, SeekOrigin.Current); byte[] GFX = BMFMCompress.Decompress(s); // And second, etc. s.Seek(3, SeekOrigin.Current); byte[] GFX2 = BMFMCompress.Decompress(s); s.Seek(3, SeekOrigin.Current); byte[] GFX3 = BMFMCompress.Decompress(s); // Return to the background data s.Seek(SNES.toPC(BGPointer, 0x84), SeekOrigin.Begin); // Go to the map16 data s.Seek(8, SeekOrigin.Current); // Load it (interleaved again) byte[] tileset = BMFMCompress.Decompress(s); byte[] tileset2 = BMFMCompress.Decompress(s); BMFMCompress.Interleave(tileset, tileset2); // Convert the graphics to linear form and combine them all into one array byte[][,] linGFX = new byte[0x300][, ]; for (int i = 0; i < 256; i++) { linGFX[i] = SNES.PlanarToLinear(GFX, i * 0x20); } for (int i = 0; i < 256; i++) { linGFX[i + 0x100] = SNES.PlanarToLinear(GFX2, i * 0x20); } for (int i = 0; i < 256; i++) { linGFX[i + 0x200] = SNES.PlanarToLinear(GFX3, i * 0x20); } // allocate the tileset images, there are 0x20 bytes per tile int numImages = tileset.Length / 0x20; images = new Bitmap[numImages]; // Now it's finally time to load the images for (int block = 0; block < numImages; block++) { // Each image is 32x32 Bitmap img = new Bitmap(32, 32, PixelFormat.Format8bppIndexed); // Add the palette to this image SNES.FillPalette(img, p.colors); // Lock the image. Without this, loading would be really slow BitmapData d = img.LockBits(new Rectangle(0, 0, 32, 32), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); // Read the first two bytes of the current block int blockStart = tileset[block * 0x20] | tileset[block * 0x20 + 1] << 8; // If this is >= 0xFDB0, then this block is a horizontally flipped copy of another tile if (blockStart >= 0xFDB0) { // The number of the tile to flip is this blockStart -= 0xFDB0; // Copy the graphics for the flipped tile for (int tile = 0; tile < 16; tile++) { int x = (tile % 4) * 8; int y = (tile / 4) * 8; int tile2 = (tile / 4) * 4 + 3 - (tile % 4); SNES.DrawTile(d, x, y, (byte)(tileset[blockStart * 0x20 + tile2 * 2 + 1] ^ 0x40), tileset[blockStart * 0x20 + tile2 * 2], linGFX); } } else { // Copy the graphics regular for (int tile = 0; tile < 16; tile++) { int x = (tile % 4) * 8; int y = (tile / 4) * 8; SNES.DrawTile(d, x, y, tileset[block * 0x20 + tile * 2 + 1], tileset[block * 0x20 + tile * 2], linGFX); } } img.UnlockBits(d); images[block] = img; } }