public static byte[] Compress(ByteArrayStream target) { if (target.Address != 0) { throw new ArgumentException($"{nameof(target)} address must be 0"); } if (target.Size % 2 != 0) { throw new ArgumentException($"size is wrong ({target.Size.ToString("x2")}), {nameof(target)} needs to be in 2 byte chunks (words)"); } var output = new List <byte>(target.Size / 2); while (!target.AtEnd) { var low = target.Byte(); var high = target.Byte(); if ((low & 0x07) != low) { throw new Exception($"{nameof(low)} byte should be lower three bits only. value: ${low.ToString("x2")} address: ${(target.Address - 2).ToString("x2")}"); } if ((high & 0x07) != low) { throw new Exception($"{nameof(high)} byte should be lower three bits only. value: ${high.ToString("x2")} address: ${(target.Address - 1).ToString("x2")}"); } var data = (byte)(low + (high << 4)); output.Add(data); } return(output.ToArray()); }
public static byte[] Compress(ByteArrayStream target) { if (target.Address != 0) { throw new ArgumentException($"{nameof(target)} address must be 0"); } if (target.Size % 0x20 != 0) { throw new ArgumentException($"size is wrong ({target.Size.ToString("x2")}), {nameof(target)} needs to be in full $20 byte chunks"); } var output = new List <byte>(target.Size * 3 / 4); while (!target.AtEnd) { output.Add(target.Byte()); if ((target.Address % 0x20) >= 0x10) { var zero = target.Byte(); if (zero != 0) { throw new Exception($"byte should be zero. value: ${zero.ToString("x2")} address: ${(target.Address - 1).ToString("x4")}"); } } } return(output.ToArray()); }
public static byte[] Decompress(ByteArrayStream source, int outputSize) { if (source == null) { throw new ArgumentNullException(nameof(source)); } var output = new ByteArrayStream(outputSize); var work = new ByteRingBuffer(RingSize, StartWriteAddress); Queue <bool> commands = source.Byte().ToBooleanQueue(); while (output.HasSpace) { if (commands.Count == 0) { commands = source.Byte().ToBooleanQueue(); } if (commands.Dequeue()) { var b = source.Byte(); work.Byte(b); output.Byte(b); } else { var d1 = source.Byte(); var d2 = source.Byte(); var address = d1 + ((d2 << 2) & 0x0300); var counter = (d2 & 0x3f) + 3; var copySource = work.Branch(address); while (output.HasSpace && (counter != 0)) { var copy = copySource.Byte(); counter--; work.Byte(copy); output.Byte(copy); } } } return(output.Buffer); }
public static byte[] Decompress(ByteArrayStream source, int compressedSize) { var output = new List <byte>(compressedSize * 2); for (int i = 0; i < compressedSize; i++) { var data = source.Byte(); output.Add((byte)(data & 0x07)); output.Add((byte)((data >> 4) & 0x07)); } return(output.ToArray()); }
public static (byte[] comp, byte[] decomp) DecompressFull(ByteArrayStream source, int outputSize) { var startAddress = source.Address; var dataSourceOffset = source.Word(); var dataSource = source.Branch(source.Address + dataSourceOffset); var output = new ByteArrayStream(outputSize); byte command; while ((command = source.Byte()) != 0x00) { if ((command & 0x0f) != 0) { var length = command & 0x0f; dataSource.CopyTo(output, length); } if ((command & 0xf0) != 0) { var length = ((command & 0xf0) >> 4) + 2; var address = output.Address - source.Byte() - 1; if (address < 0) { throw new IndexOutOfRangeException($"{nameof(address)} cannot be less than 0"); } output.Branch(address).CopyTo(output, length); } } var comp = source.GetBytes(dataSource.Address - startAddress, startAddress); var decomp = output.GetBytes(output.Address, 0); return(comp, decomp); }
public static string AttemptTranslateLine(ByteArrayStream stream) { var line = new List <string>(); while (stream.ByteAt(0) != BasicTable.EndOfString) { line.Add(AttemptTranslate(stream)); } var text = string.Join("", line); stream.Byte(); return(text); }
public static string AttemptTranslate(ByteArrayStream stream, int depthLeft = 2) { var code = stream.Byte(); if (code >= 0x80) { return(BasicTable.Lookup(code)); } if (code < 0x30) { if (code == 0x01) { return($"{{windowbreak}}"); } if (code == 0x05) { return($"{{05:{stream.Byte().ToString("x2")}}}"); } if (code == 0x1b) { return($"{{swapspeaker:{stream.Byte().ToString("x2")}}}"); } if (code == 0x1d) { return($"{{character:{CharacterNames.GetString(stream.Byte())}}}"); } if (code == 0x1e) { return($"{{item:{ItemNames.GetString(stream.Byte())}}}"); } if (code == 0x1f) { return($"{{location:{LocationNames.GetString(stream.Byte())}}}"); } //if (code == 0x2f) { // return $"{{if:{stream.Byte().ToString("x2")} {stream.Byte().ToString("x2")} {stream.Byte().ToString("x2")}}}"; //} return($"{{{code.ToString("x2")}}}"); } if (depthLeft == 0) { return($"{{{code.ToString("x2")}}}"); } //var text = string.Join("", LookupBytes(code).Select(x => AttemptTranslate(x, depthLeft - 1))); var text = AttemptTranslateLine(LookupBytes(code), depthLeft - 1); return(text); }
public static string AttemptTranslateLine(byte[] input, int depthLeft = 3) { var line = new List <string>(); var stream = new ByteArrayStream(input); while (!stream.AtEnd) { line.Add(AttemptTranslate(stream, depthLeft)); } var text = string.Join("", line); if (!stream.AtEnd) { stream.Byte(); } return(text); }
public static byte[] Decompress(ByteArrayStream source, int compressedSize) { if (compressedSize % 0x18 != 0) { throw new ArgumentException($"{nameof(compressedSize)} is wrong ({compressedSize.ToString("x2")}), {nameof(source)} needs to be in full $18 byte chunks"); } var output = new List <byte>(compressedSize * 4 / 3); for (int i = 0; i < compressedSize; i++) { output.Add(source.Byte()); // skip a byte in destination if ((i % 0x18) >= 0x10) { output.Add(0); } } return(output.ToArray()); }
public static byte[] Compress(ByteArrayStream target) { var commands = new List <byte>(); var data = new List <byte>(); var copyData = 0; while (!target.AtEnd) { var term = target.GetBytes(0x11); var copyOutput = 0; var copyOffset = -1; while (term.Length >= 3) { var(found, address) = target.FindLastInWindow(term, target.Address - 256, target.Address + term.Length - 1); if (found) { copyOutput = term.Length - 2; copyOffset = target.Address - address - 1; break; } term = target.GetBytes(term.Length - 1); } if (copyOutput == 0) { if (copyData == 0xf) { commands.Add((byte)copyData); copyData = 1; } else { copyData++; } data.Add(target.Byte()); } else { commands.Add((byte)((copyOutput << 4) + copyData)); commands.Add((byte)copyOffset); copyData = 0; target.Address += term.Length; } } // Add last copy data command if (copyData != 0) { commands.Add((byte)copyData); } // Add terminating command commands.Add(0); var output = new ByteArrayStream(commands.Count + data.Count + 2); var dataOffset = commands.Count; if (dataOffset > 0xffff) { throw new Exception($"{nameof(dataOffset)} cannot be larger than 0xffff. {nameof(commands)} is too large"); } output.Word((ushort)dataOffset); output.Write(commands); output.Write(data); return(output.Buffer); }
public static void Go() { var rom = FFMQ.Game.Rom; //0B81A5 $AD $91 $0E LDA $0E91 [010E91] = $0D A:400D X:182E Y:0080 S:1FC8 D:0000 DB:01 P:nvMxdizc V:30 H:166 var start = 0x0d; // byte start *= 2; Console.WriteLine($"{nameof(start)} == {start.ToString("x2")}"); //0B81AF $BF $3B $AF $07 LDA $07AF3B,X[07AF55] = $0234 A: 001A X:001A Y:0080 S: 1FC8 D:0000 DB: 01 P: nvmxdizc V:30 H: 192 var lookup_07af3b = rom.GetStream(0x07af3b); var offset = lookup_07af3b.WordAt(start); Console.WriteLine($"{nameof(offset)} == {offset.ToString("x4")}"); //0B81BC $BF $13 $B0 $07 LDA $07B013,X[07B247] = $07 A: 0234 X: 0234 Y: 0000 S: 1FC8 D:0000 DB: 01 P: nvMxdiZc V:30 H: 229 //0B81C0 $99 $10 $19 STA $1910,Y[011910] = $00 A: 0207 X: 0234 Y: 0000 S: 1FC8 D:0000 DB: 01 P: nvMxdizc V:30 H: 239 //0B81C3 $E8 INX A: 0207 X: 0234 Y: 0000 S: 1FC8 D:0000 DB: 01 P: nvMxdizc V:30 H: 249 //0B81C4 $C8 INY A: 0207 X: 0235 Y: 0000 S: 1FC8 D:0000 DB: 01 P: nvMxdizc V:30 H: 252 //0B81C5 $C0 $07 $00 CPY #$0007 A:0207 X:0235 Y:0001 S:1FC8 D:0000 DB:01 P:nvMxdizc V:30 H:256 var optionsLookup = rom.GetStream(0x07b013); // $1910 - $1916 // $1910 is index into tilemap data // $1911 is index into bg tile graphics data // $1912 is index into color palettes var options = optionsLookup.GetBytesAt(7, offset); Console.WriteLine($"{nameof(options)} == {options.ToHexString()}"); //; multiply(lower six bits of $1910) * 3 //0b850e sep #$20 //0b8510 lda $1910 //0b8513 and #$3f //0b8515 sta $4202 //0b8518 lda #$03 //0b851a sta $4203 //; get multiply result //0b8526 ldx $4216 var mapDataOffsetIndex = (options[0] & 0x3f) * 3; Console.WriteLine($"{nameof(mapDataOffsetIndex)} == {mapDataOffsetIndex.ToString("x4")}"); var mapDataOffsetLookup = rom.GetStream(0x0b8735); //; store source data lookup address, long, at $0900 => value at $0b8735,x //0b8529 rep #$20 //0b852b lda $0b8735,x //0b852f sta $0900 //0b8532 sep #$20 //0b8534 lda $0b8737,x //0b8538 sta $0902 var mapDataOffset = mapDataOffsetLookup.LongAt(mapDataOffsetIndex); Console.WriteLine($"{nameof(mapDataOffset)} == {mapDataOffset.ToString("x4")}"); // $1000 seems to be the largest map, but using $2000 for now var(tilemapcomp, tilemapdecomp) = SimpleTailWindowCompression.DecompressFull(rom.GetStream(mapDataOffset), 0x2000); Utilities.WriteBytesToFile(tilemapcomp, @"c:\working\ffmq\~go! -- comp.txt"); Utilities.WriteBytesToFile(tilemapdecomp, @"c:\working\ffmq\~go! -- decomp.txt"); // $19b7 var bgGraphicsOffsetsIndex = options[1] * 0x0a; var lookup_0b8cd9 = rom.GetStream(0x0b8cd9); // $1918 - $1921 // $1918 is ??? // $1919 is color data index to // $191a to $1921 are bg tile graphics offsets var x1918 = lookup_0b8cd9.GetBytesAt(0x0a, bgGraphicsOffsetsIndex); // TODO: clean up data access, this is awkward // bgTileIndexes is $191a to $1921 var bgTileIndexes = new ByteArrayStream(x1918, 2); var bgTileData = rom.GetStream(0x058c80); var bgTilePaletteIndexData = rom.GetStream(0x05f280); var bgTileSets = new List <BgTileSetEntry>(); while (!bgTileIndexes.AtEnd) { var bgTileIndex = bgTileIndexes.Byte(); // $300 is $20 tiles of $18 bytes var tileDataAddressOffset = 0x300 * bgTileIndex; var tileData = ExpandSecondHalfWithZeros.Decompress(bgTileData.GetBytes(0x300, tileDataAddressOffset)); // $10 is $20 tiles, each a nibble var paletteAddress = 0x10 * bgTileIndex; var palette = ExpandNibblesMasked.Decompress(bgTilePaletteIndexData.GetBytes(0x10, paletteAddress)); bgTileSets.Add(new BgTileSetEntry { TileDataAddress = bgTileData.Address + tileDataAddressOffset, PaletteIndexAddress = bgTilePaletteIndexData.Address + paletteAddress, TileData = tileData, PaletteIndex = palette }); } Console.ReadKey(); }