private void LoadMetadata(JJ2Block block) { palette = new Color[256]; for (int i = 0; i < 256; i++) { byte red = block.ReadByte(); byte green = block.ReadByte(); byte blue = block.ReadByte(); byte alpha = block.ReadByte(); palette[i] = Color.FromArgb(255 - alpha, red, green, blue); } tileCount = block.ReadInt32(); int maxTiles = MaxSupportedTiles; tiles = new TilesetTileSection[maxTiles]; for (int i = 0; i < maxTiles; ++i) { tiles[i].Opaque = block.ReadBool(); } // Block of unknown values, skip block.DiscardBytes(maxTiles); for (int i = 0; i < maxTiles; ++i) { tiles[i].ImageDataOffset = block.ReadUInt32(); } // Block of unknown values, skip block.DiscardBytes(4 * maxTiles); for (int i = 0; i < maxTiles; ++i) { tiles[i].AlphaDataOffset = block.ReadUInt32(); } // Block of unknown values, skip block.DiscardBytes(4 * maxTiles); for (int i = 0; i < maxTiles; ++i) { tiles[i].MaskDataOffset = block.ReadUInt32(); } // We don't care about the flipped masks, those are generated on runtime block.DiscardBytes(4 * maxTiles); }
public static void Convert(string path, string targetPath, bool isPlus) { JJ2Version version; RawList <AnimSection> anims = new RawList <AnimSection>(); RawList <SampleSection> samples = new RawList <SampleSection>(); using (Stream s = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)) using (BinaryReader r = new BinaryReader(s)) { Log.Write(LogType.Info, "Reading compressed stream..."); Log.PushIndent(); bool seemsLikeCC = false; uint magic = r.ReadUInt32(); if (magic != 0x42494C41 /*ALIB*/) { throw new InvalidOperationException("Invalid magic string"); } uint signature = r.ReadUInt32(); if (signature != 0x00BEBA00) { throw new InvalidOperationException("Invalid signature"); } uint headerLen = r.ReadUInt32(); uint magicUnknown = r.ReadUInt32(); if (magicUnknown != 0x18080200) { throw new InvalidOperationException("Invalid magic number"); } uint fileLen = r.ReadUInt32(); uint crc = r.ReadUInt32(); int setCnt = r.ReadInt32(); uint[] setAddresses = new uint[setCnt]; for (uint i = 0; i < setCnt; i++) { setAddresses[i] = r.ReadUInt32(); } if (headerLen != s.Position) { throw new InvalidOperationException("Header size mismatch"); } // Read content bool isStreamComplete = true; { int i = 0; try { for (; i < setCnt; i++) { uint magicANIM = r.ReadUInt32(); byte animCount = r.ReadByte(); byte sndCount = r.ReadByte(); ushort frameCount = r.ReadUInt16(); uint cumulativeSndIndex = r.ReadUInt32(); int infoBlockLenC = r.ReadInt32(); int infoBlockLenU = r.ReadInt32(); int frameDataBlockLenC = r.ReadInt32(); int frameDataBlockLenU = r.ReadInt32(); int imageDataBlockLenC = r.ReadInt32(); int imageDataBlockLenU = r.ReadInt32(); int sampleDataBlockLenC = r.ReadInt32(); int sampleDataBlockLenU = r.ReadInt32(); JJ2Block infoBlock = new JJ2Block(s, infoBlockLenC, infoBlockLenU); JJ2Block frameDataBlock = new JJ2Block(s, frameDataBlockLenC, frameDataBlockLenU); JJ2Block imageDataBlock = new JJ2Block(s, imageDataBlockLenC, imageDataBlockLenU); JJ2Block sampleDataBlock = new JJ2Block(s, sampleDataBlockLenC, sampleDataBlockLenU); if (magicANIM != 0x4D494E41) { Log.Write(LogType.Warning, "Header for set " + i + " is incorrect (bad magic value)! Skipping the subfile."); continue; } List <AnimSection> setAnims = new List <AnimSection>(); for (ushort j = 0; j < animCount; j++) { AnimSection anim = new AnimSection(); anim.Set = i; anim.Anim = j; anim.FrameCount = infoBlock.ReadUInt16(); anim.FrameRate = infoBlock.ReadUInt16(); anim.Frames = new AnimFrameSection[anim.FrameCount]; // Skip the rest, seems to be 0x00000000 for all headers infoBlock.DiscardBytes(4); anims.Add(anim); setAnims.Add(anim); } if (i == 65 && setAnims.Count > 5) { seemsLikeCC = true; } if (frameCount > 0) { if (setAnims.Count == 0) { throw new InvalidOperationException("Set has frames but no anims"); } short lastColdspotX = 0, lastColdspotY = 0; short lastHotspotX = 0, lastHotspotY = 0; short lastGunspotX = 0, lastGunspotY = 0; AnimSection currentAnim = setAnims[0]; ushort currentAnimIdx = 0, currentFrame = 0; for (ushort j = 0; j < frameCount; j++) { if (currentFrame >= currentAnim.FrameCount) { currentAnim = setAnims[++currentAnimIdx]; currentFrame = 0; while (currentAnim.FrameCount == 0 && currentAnimIdx < setAnims.Count) { currentAnim = setAnims[++currentAnimIdx]; } } ref AnimFrameSection frame = ref currentAnim.Frames[currentFrame]; frame.SizeX = frameDataBlock.ReadInt16(); frame.SizeY = frameDataBlock.ReadInt16(); frame.ColdspotX = frameDataBlock.ReadInt16(); frame.ColdspotY = frameDataBlock.ReadInt16(); frame.HotspotX = frameDataBlock.ReadInt16(); frame.HotspotY = frameDataBlock.ReadInt16(); frame.GunspotX = frameDataBlock.ReadInt16(); frame.GunspotY = frameDataBlock.ReadInt16(); frame.ImageAddr = frameDataBlock.ReadInt32(); frame.MaskAddr = frameDataBlock.ReadInt32(); // Adjust normalized position // In the output images, we want to make the hotspot and image size constant. currentAnim.NormalizedHotspotX = (short)Math.Max(-frame.HotspotX, currentAnim.NormalizedHotspotX); currentAnim.NormalizedHotspotY = (short)Math.Max(-frame.HotspotY, currentAnim.NormalizedHotspotY); currentAnim.LargestOffsetX = (short)Math.Max(frame.SizeX + frame.HotspotX, currentAnim.LargestOffsetX); currentAnim.LargestOffsetY = (short)Math.Max(frame.SizeY + frame.HotspotY, currentAnim.LargestOffsetY); currentAnim.AdjustedSizeX = (short)Math.Max( currentAnim.NormalizedHotspotX + currentAnim.LargestOffsetX, currentAnim.AdjustedSizeX ); currentAnim.AdjustedSizeY = (short)Math.Max( currentAnim.NormalizedHotspotY + currentAnim.LargestOffsetY, currentAnim.AdjustedSizeY ); #if DEBUG if (currentFrame > 0) { int diffPrevX, diffPrevY, diffNextX, diffNextY; if (frame.ColdspotX != 0 && frame.ColdspotY != 0) { diffPrevX = (lastColdspotX - lastHotspotX); diffPrevY = (lastColdspotY - lastHotspotY); diffNextX = (frame.ColdspotX - frame.HotspotX); diffNextY = (frame.ColdspotY - frame.HotspotY); if (diffPrevX != diffNextX || diffPrevY != diffNextY) { Log.Write(LogType.Warning, "Animation " + currentAnim.Anim + " coldspots in set " + currentAnim.Set + " are different!"); Log.PushIndent(); Log.Write(LogType.Warning, "Frame #" + (currentFrame - 1) + ": " + diffPrevX + "," + diffPrevY + " | " + "Frame #" + currentFrame + ": " + diffNextX + "," + diffNextY); Log.PopIndent(); } } if (frame.GunspotX != 0 && frame.GunspotY != 0) { diffPrevX = (lastGunspotX - lastHotspotX); diffPrevY = (lastGunspotY - lastHotspotY); diffNextX = (frame.GunspotX - frame.HotspotX); diffNextY = (frame.GunspotY - frame.HotspotY); if (diffPrevX != diffNextX || diffPrevY != diffNextY) { Log.Write(LogType.Warning, "Animation " + currentAnim.Anim + " gunspots in set " + currentAnim.Set + " are different!"); Log.PushIndent(); Log.Write(LogType.Warning, "Frame #" + (currentFrame - 1) + ": " + diffPrevX + "," + diffPrevY + " | " + "Frame #" + currentFrame + ": " + diffNextX + "," + diffNextY); Log.PopIndent(); } } } #endif lastColdspotX = frame.ColdspotX; lastColdspotY = frame.ColdspotY; lastHotspotX = frame.HotspotX; lastHotspotY = frame.HotspotY; lastGunspotX = frame.GunspotX; lastGunspotY = frame.GunspotY; currentFrame++; } // Read the image data for each animation frame for (ushort j = 0; j < setAnims.Count; j++) { AnimSection anim = setAnims[j]; if (anim.FrameCount < anim.Frames.Length) { Log.Write(LogType.Error, "Animation " + j + " frame count in set " + i + " doesn't match! Expected " + anim.FrameCount + " frames, but read " + anim.Frames.Length + " instead."); throw new InvalidOperationException(); } for (ushort frame = 0; frame < anim.FrameCount; ++frame) { int dpos = (anim.Frames[frame].ImageAddr + 4); imageDataBlock.SeekTo(dpos - 4); ushort width2 = imageDataBlock.ReadUInt16(); imageDataBlock.SeekTo(dpos - 2); ushort height2 = imageDataBlock.ReadUInt16(); ref AnimFrameSection frameData = ref anim.Frames[frame]; frameData.DrawTransparent = (width2 & 0x8000) > 0; int pxRead = 0; int pxTotal = (frameData.SizeX * frameData.SizeY); bool lastOpEmpty = true; List <byte> imageData = new List <byte>(pxTotal); while (pxRead < pxTotal) { if (dpos > 0x10000000) { Log.Write(LogType.Error, "Loading of animation " + j + " in set " + i + " failed! Aborting."); break; } imageDataBlock.SeekTo(dpos); byte op = imageDataBlock.ReadByte(); //if (op == 0) { // Console.WriteLine("[" + i + ":" + j + "] Next image operation should probably not be 0x00."); //} if (op < 0x80) { // Skip the given number of pixels, writing them with the transparent color 0 pxRead += op; while (op-- > 0) { imageData.Add((byte)0x00); } dpos++; } else if (op == 0x80) { // Skip until the end of the line ushort linePxLeft = (ushort)(frameData.SizeX - pxRead % frameData.SizeX); if (pxRead % anim.Frames[frame].SizeX == 0 && !lastOpEmpty) { linePxLeft = 0; } pxRead += linePxLeft; while (linePxLeft-- > 0) { imageData.Add((byte)0x00); } dpos++; } else { // Copy specified amount of pixels (ignoring the high bit) ushort bytesToRead = (ushort)(op & 0x7F); imageDataBlock.SeekTo(dpos + 1); byte[] nextData = imageDataBlock.ReadRawBytes(bytesToRead); imageData.AddRange(nextData); pxRead += bytesToRead; dpos += bytesToRead + 1; } lastOpEmpty = (op == 0x80); } frameData.ImageData = imageData.ToArray(); frameData.MaskData = new BitArray(pxTotal, false); dpos = frameData.MaskAddr; pxRead = 0; // No mask if (dpos == unchecked ((int)0xFFFFFFFF)) { continue; } while (pxRead < pxTotal) { imageDataBlock.SeekTo(dpos); byte b = imageDataBlock.ReadByte(); for (byte bit = 0; bit < 8 && (pxRead + bit) < pxTotal; ++bit) { frameData.MaskData[pxRead + bit] = ((b & (1 << (7 - bit))) != 0); } pxRead += 8; } } } } for (ushort j = 0; j < sndCount; ++j) { SampleSection sample; //sample.Id = (ushort)(cumulativeSndIndex + j); sample.IdInSet = j; sample.Set = i; int totalSize = sampleDataBlock.ReadInt32(); uint magicRIFF = sampleDataBlock.ReadUInt32(); int chunkSize = sampleDataBlock.ReadInt32(); // "ASFF" for 1.20, "AS " for 1.24 uint format = sampleDataBlock.ReadUInt32(); bool isASFF = (format == 0x46465341); uint magicSAMP = sampleDataBlock.ReadUInt32(); uint sampSize = sampleDataBlock.ReadUInt32(); // Padding/unknown data #1 // For set 0 sample 0: // 1.20 1.24 // +00 00 00 00 00 00 00 00 00 +00 40 00 00 00 00 00 00 00 // +08 00 00 00 00 00 00 00 00 +08 00 00 00 00 00 00 00 00 // +10 00 00 00 00 00 00 00 00 +10 00 00 00 00 00 00 00 00 // +18 00 00 00 00 +18 00 00 00 00 00 00 00 00 // +20 00 00 00 00 00 40 FF 7F sampleDataBlock.DiscardBytes(40 - (isASFF ? 12 : 0)); if (isASFF) { // All 1.20 samples seem to be 8-bit. Some of them are among those // for which 1.24 reads as 24-bit but that might just be a mistake. sampleDataBlock.DiscardBytes(2); sample.Multiplier = 0; } else { // for 1.24. 1.20 has "20 40" instead in s0s0 which makes no sense sample.Multiplier = sampleDataBlock.ReadUInt16(); } // Unknown. s0s0 1.20: 00 80, 1.24: 80 00 sampleDataBlock.DiscardBytes(2); uint payloadSize = sampleDataBlock.ReadUInt32(); // Padding #2, all zeroes in both sampleDataBlock.DiscardBytes(8); sample.SampleRate = sampleDataBlock.ReadUInt32(); int actualDataSize = chunkSize - 76 + (isASFF ? 12 : 0); sample.Data = sampleDataBlock.ReadRawBytes(actualDataSize); // Padding #3 sampleDataBlock.DiscardBytes(4); if (magicRIFF != 0x46464952 || magicSAMP != 0x504D4153) { throw new InvalidOperationException("Sample has invalid header"); } if (sample.Data.Length < actualDataSize) { Log.Write(LogType.Warning, "Sample " + j + " in set " + i + " was shorter than expected! Expected " + actualDataSize + " bytes, but read " + sample.Data.Length + " instead."); } if (totalSize > chunkSize + 12) { // Sample data is probably aligned to X bytes since the next sample doesn't always appear right after the first ends. Log.Write(LogType.Warning, "Adjusting read offset of sample " + j + " in set " + i + " by " + (totalSize - chunkSize - 12) + " bytes."); sampleDataBlock.DiscardBytes(totalSize - chunkSize - 12); } samples.Add(sample); } } } catch (EndOfStreamException) {