static bool PatchArm9( System.IO.FileStream nds, uint pos, uint len ) { nds.Position = pos; byte[] data = new byte[len]; nds.Read( data, 0, (int)len ); // decompress size info: http://www.crackerscrap.com/docs/dsromstructure.html // TODO: Is there a better way to figure out if an ARM9 is compressed? nds.Position = nds.Position - 8; uint compressedSize = nds.ReadUInt24(); byte headerLength = (byte)nds.ReadByte(); uint additionalCompressedSize = nds.ReadUInt32(); uint decompressedSize = additionalCompressedSize + len; bool compressed = false; byte[] decData = data; #if DEBUG Console.WriteLine( "ARM9 old dec size: 0x" + decompressedSize.ToString( "X6" ) ); Console.WriteLine( "ARM9 old cmp size: 0x" + compressedSize.ToString( "X6" ) ); Console.WriteLine( "ARM9 old filesize: 0x" + len.ToString( "X6" ) ); Console.WriteLine( "ARM9 old diff: 0x" + additionalCompressedSize.ToString( "X6" ) ); System.IO.File.WriteAllBytes( "arm9-raw.bin", data ); #endif blz blz = new blz(); // if one of these isn't true then it can't be blz-compressed so don't even try bool headerLengthValid = ( headerLength >= 8 && headerLength <= 11 ); bool compressedSizeValid = ( data.Length >= compressedSize + 0x4000 && data.Length <= compressedSize + 0x400B ); if ( headerLengthValid && compressedSizeValid ) { try { blz.arm9 = 1; byte[] maybeDecData = blz.BLZ_Decode( data ); if ( maybeDecData.Length == decompressedSize ) { compressed = true; decData = maybeDecData; #if DEBUG System.IO.File.WriteAllBytes( "arm9-dec.bin", decData ); #endif } } catch ( blzDecodingException ) { compressed = false; } } byte[] decDataUnmodified = (byte[])decData.Clone(); if ( ReplaceInData( decData, 0x00, true ) ) { if ( compressed ) { Console.WriteLine( "Replacing and recompressing ARM9..." ); data = blz.BLZ_Encode( decData, 0 ); uint newCompressedSize = (uint)data.Length; if ( newCompressedSize > len ) { // new ARM is actually bigger, redo without the additional nullterm replacement decData = decDataUnmodified; ReplaceInData( decData, 0x00, false ); data = blz.BLZ_Encode( decData, 0, supressWarnings: true ); newCompressedSize = (uint)data.Length; int arm9diff = (int)len - (int)newCompressedSize; if ( arm9diff < 0 ) { // still too big, remove debug strings if ( !RemoveStringsInKnownGames( GetGamecode( nds ), decData ) ) { RemoveDebugStrings( decData ); } #if DEBUG System.IO.File.WriteAllBytes( "arm9-dec-without-debug.bin", decData ); #endif data = blz.BLZ_Encode( decData, 0, supressWarnings: true ); newCompressedSize = (uint)data.Length; arm9diff = (int)len - (int)newCompressedSize; if ( arm9diff < 0 ) { Console.WriteLine( "WARNING: Recompressed ARM9 is " + -arm9diff + " bytes bigger than original!" ); Console.WriteLine( " Patched game may be corrupted!" ); #if DEBUG System.IO.File.WriteAllBytes( "arm9-too-big-recomp.bin", data ); #endif } } } if ( newCompressedSize != len ) { // new ARM is (still) different, attempt to find the metadata in the ARM9 secure area and replace that bool foundSize = false; for ( int i = 0; i < 0x4000; i += 4 ) { uint maybeSize = BitConverter.ToUInt32( data, i ); if ( maybeSize == len + 0x02000000u || maybeSize == len + 0x02004000u ) { foundSize = true; byte[] newCmpSizeBytes; if ( maybeSize == len + 0x02004000u ) { newCmpSizeBytes = BitConverter.GetBytes( newCompressedSize + 0x02004000u ); } else { newCmpSizeBytes = BitConverter.GetBytes( newCompressedSize + 0x02000000u ); } data[i + 0] = newCmpSizeBytes[0]; data[i + 1] = newCmpSizeBytes[1]; data[i + 2] = newCmpSizeBytes[2]; data[i + 3] = newCmpSizeBytes[3]; break; } } if ( !foundSize ) { Console.WriteLine( "WARNING: Recompressed ARM9 is different size, and size could not be found in secure area!" ); Console.WriteLine( " Patched game will probably not boot!" ); } } #if DEBUG uint newDecompressedSize = (uint)decData.Length; uint newAdditionalCompressedSize = newDecompressedSize - newCompressedSize; Console.WriteLine( "ARM9 new dec size: 0x" + newDecompressedSize.ToString( "X6" ) ); Console.WriteLine( "ARM9 new cmp size: 0x" + newCompressedSize.ToString( "X6" ) ); Console.WriteLine( "ARM9 new diff: 0x" + newAdditionalCompressedSize.ToString( "X6" ) ); #endif } else { Console.WriteLine( "Replacing ARM9..." ); data = decData; } #if DEBUG System.IO.File.WriteAllBytes( "arm9-new.bin", data ); #endif nds.Position = pos; nds.Write( data, 0, data.Length ); int newSize = data.Length; int diff = (int)len - newSize; // copy back footer if ( diff > 0 ) { List<byte> footer = new List<byte>(); nds.Position = pos + len; if ( nds.PeekUInt32() == 0xDEC00621 ) { for ( int j = 0; j < 12; ++j ) { footer.Add( (byte)nds.ReadByte() ); } nds.Position = pos + newSize; nds.Write( footer.ToArray(), 0, footer.Count ); } // padding for ( int j = 0; j < diff; ++j ) { nds.WriteByte( 0xFF ); } } // write new size byte[] newSizeBytes = BitConverter.GetBytes( newSize ); nds.Position = 0x2C; nds.Write( newSizeBytes, 0, 4 ); // recalculate checksums nds.Position = pos; ushort secureChecksum = new Crc16().ComputeChecksum( nds, 0x4000, 0xFFFF ); nds.Position = 0x6C; nds.Write( BitConverter.GetBytes( secureChecksum ), 0, 2 ); nds.Position = 0; ushort headerChecksum = new Crc16().ComputeChecksum( nds, 0x15E, 0xFFFF ); nds.Write( BitConverter.GetBytes( headerChecksum ), 0, 2 ); return true; } return false; }
static bool PatchOverlay( System.IO.FileStream nds, uint pos, uint len ) { // http://sourceforge.net/p/devkitpro/ndstool/ci/master/tree/source/ndsextract.cpp // http://sourceforge.net/p/devkitpro/ndstool/ci/master/tree/source/overlay.h // header compression info from http://gbatemp.net/threads/recompressing-an-overlay-file.329576/ nds.Position = 0x048; uint fatOffset = nds.ReadUInt32(); bool modified = false; for ( uint i = 0; i < len; i += 0x20 ) { nds.Position = pos + i; uint id = nds.ReadUInt32(); uint ramAddr = nds.ReadUInt32(); uint ramSize = nds.ReadUInt32(); uint bssSize = nds.ReadUInt32(); uint sinitInit = nds.ReadUInt32(); uint sinitInitEnd = nds.ReadUInt32(); uint fileId = nds.ReadUInt32(); uint compressedSize = nds.ReadUInt24(); byte compressedBitmask = (byte)nds.ReadByte(); nds.Position = fatOffset + 8 * id; uint overlayPositionStart = nds.ReadUInt32(); uint overlayPositionEnd = nds.ReadUInt32(); uint overlaySize = overlayPositionEnd - overlayPositionStart; if ( overlaySize == 0 ) { continue; } nds.Position = overlayPositionStart; byte[] data = new byte[overlaySize]; nds.Read( data, 0, (int)overlaySize ); blz blz = new blz(); byte[] decData; bool compressed = ( compressedBitmask & 0x01 ) == 0x01; if ( compressed ) { try { decData = blz.BLZ_Decode( data ); } catch ( blzDecodingException ) { Console.WriteLine( "WARNING: Decompression of Overlay " + ( i / 0x20 ) + " failed!" ); decData = data; compressed = false; } } else { decData = data; } #if DEBUG System.IO.File.WriteAllBytes( "overlay" + ( i / 0x20 ) + "-dec.bin", decData ); #endif if ( ReplaceInData( decData ) ) { modified = true; int newOverlaySize; int diff; // if something was replaced, put it back into the ROM if ( compressed ) { Console.WriteLine( "Replacing and recompressing overlay " + id + "..." ); uint newCompressedSize = 0; data = blz.BLZ_Encode( decData, 0 ); newCompressedSize = (uint)data.Length; newOverlaySize = data.Length; diff = (int)overlaySize - newOverlaySize; if ( diff < 0 ) { Console.WriteLine( "Removing known debug strings and recompressing overlay " + id + "..." ); RemoveDebugStrings( decData ); data = blz.BLZ_Encode( decData, 0, supressWarnings: true ); newCompressedSize = (uint)data.Length; newOverlaySize = data.Length; diff = (int)overlaySize - newOverlaySize; if ( diff < 0 ) { Console.WriteLine( "WARNING: Recompressed overlay is " + -diff + " bytes bigger than original!" ); Console.WriteLine( " Patched game may be corrupted!" ); } } // replace compressed size, if it was used before if ( compressedSize == overlaySize ) { byte[] newCompressedSizeBytes = BitConverter.GetBytes( newCompressedSize ); nds.Position = pos + i + 0x1C; nds.Write( newCompressedSizeBytes, 0, 3 ); } } else { Console.WriteLine( "Replacing overlay " + id + "..." ); data = decData; } newOverlaySize = data.Length; diff = (int)overlaySize - newOverlaySize; nds.Position = overlayPositionStart; nds.Write( data, 0, data.Length ); overlayPositionEnd = (uint)nds.Position; // padding for ( int j = 0; j < diff; ++j ) { nds.WriteByte( 0xFF ); } // new file end offset byte[] newPosEndData = BitConverter.GetBytes( overlayPositionEnd ); nds.Position = fatOffset + 8 * id + 4; nds.Write( newPosEndData, 0, 4 ); } } return modified; }
public static string ReadToNulltermAndDecode( System.IO.Stream s ) { StringBuilder sb = new StringBuilder(); byte[] buffer = new byte[2]; int b = s.ReadByte(); while ( b != 0 && b != -1 ) { if ( ( b >= 0 && b <= 0x80 ) || ( b >= 0xA0 && b <= 0xDF ) ) { // is a single byte buffer[0] = (byte)b; sb.Append( Util.ShiftJISEncoding.GetString( buffer, 0, 1 ) ); } else { if ( b != 0xFF ) { // is two bytes buffer[0] = (byte)b; buffer[1] = (byte)s.ReadByte(); sb.Append( Util.ShiftJISEncoding.GetString( buffer ) ); } else { // is a FFCC command b = s.ReadByte(); switch ( b ) { case 0xA0: // newline sb.AppendLine(); break; case 0xA1: // end of textbox sb.AppendLine(); sb.AppendLine(); break; case 0xA2: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xA3: // wait before printing more sb.Append( "<Wait: " + s.ReadUInt16().ToString( "X4" ) + ">" ); break; case 0xA4: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xA5: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xA6: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xA7: // multiple choice sb.Append( "<Choice: " + s.ReadUInt32().ToString( "X8" ) + ">" ); break; case 0xA8: sb.Append( "<Player Name: " + s.ReadUInt32().ToString( "X8" ) + ">" ); break; case 0xA9: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt32().ToString( "X8" ) + ">" ); break; case 0xAA: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt48().ToString( "X12" ) + ">" ); break; case 0xAB: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt16().ToString( "X4" ) + ">" ); break; case 0xAC: sb.Append( "<Color: Black>" ); break; case 0xAD: sb.Append( "<Color: Blue>" ); break; case 0xAE: sb.Append( "<Color: Red>" ); break; case 0xAF: sb.Append( "<Color: Pink>" ); break; case 0xB0: sb.Append( "<Color: Green>" ); break; case 0xB1: sb.Append( "<" + b.ToString( "X2" ) + ">" ); // color? break; case 0xB2: sb.Append( "<Color: Yellow>" ); break; case 0xB3: sb.Append( "<Color: Default>" ); break; case 0xB4: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt16().ToString( "X4" ) + ">" ); break; case 0xB5: sb.Append( "<Color: Orange>" ); break; case 0xB6: sb.Append( "<" + b.ToString( "X2" ) + ">" ); // color? break; case 0xBA: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt32().ToString( "X8" ) + ">" ); break; case 0xBD: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt32().ToString( "X8" ) + ">" ); break; case 0xC2: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt64().ToString( "X16" ) + ">" ); break; case 0xC4: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xC5: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt16().ToString( "X4" ) + ">" ); break; case 0xC6: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt16().ToString( "X4" ) + ">" ); break; case 0xC7: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt16().ToString( "X4" ) + ">" ); break; case 0xC8: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xC9: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xCA: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt32().ToString( "X8" ) + ">" ); break; case 0xCB: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt32().ToString( "X8" ) + ">" ); break; case 0xCC: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt32().ToString( "X8" ) + ">" ); break; case 0xCD: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt48().ToString( "X12" ) + ">" ); break; case 0xCE: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt16().ToString( "X4" ) + ">" ); break; case 0xCF: sb.Append( "<Hometown>" ); break; case 0xD0: // print numeric variable sb.Append( "<Numeric Variable " + s.ReadUInt24().ToString( "X6" ) + ">" ); break; case 0xD1: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt64().ToString( "X16" ) + ">" ); break; case 0xD2: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xD3: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt16().ToString( "X4" ) + ">" ); break; case 0xD4: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt16().ToString( "X4" ) + ">" ); break; case 0xD5: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt32().ToString( "X8" ) + ">" ); break; case 0xD6: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt24().ToString( "X6" ) + ">" ); break; case 0xD7: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt32().ToString( "X8" ) + ">" ); break; case 0xD8: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt32().ToString( "X8" ) + ">" ); break; case 0xD9: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt32().ToString( "X8" ) + ">" ); break; case 0xDA: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt40().ToString( "X10" ) + ">" ); break; case 0xDB: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt32().ToString( "X8" ) + ">" ); break; case 0xDC: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt40().ToString( "X10" ) + ">" ); break; case 0xDD: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt48().ToString( "X12" ) + ">" ); break; case 0xDE: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt56().ToString( "X14" ) + ">" ); break; case 0xDF: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt64().ToString( "X16" ) + ">" ); break; case 0xE0: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt56().ToString( "X14" ) + ">" ); break; case 0xE1: sb.Append( "<" + b.ToString( "X2" ) + ": " + s.ReadUInt16().ToString( "X4" ) + ">" ); break; case 0xE2: // start of a singular/plural either/or checking a variable; singular follows sb.Append( "<If Variable " + s.ReadUInt16().ToString( "X4" ) + " == 1 Then: " ); break; case 0xE3: // start of a singular/plural either/or checking the single/multiplayer status; singular follows sb.Append( "<If Player Count == 1 Then: " ); break; case 0xE4: // start of a male/female either/or checking the current player character; male follows sb.Append( "<If Player is Male Then: " ); break; case 0xE5: // start of a male/female either/or checking a different player character; male follows sb.Append( "<If Character is Male Then: " ); break; case 0xE6: // middle of an either/or; else case follows sb.Append( " / Else: " ); break; case 0xE8: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xE9: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xEB: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xED: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xEE: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xEF: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xF0: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xF2: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xF3: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xF4: sb.Append( "<" + b.ToString( "X2" ) + ">" ); break; case 0xE7: // end of an either/or sb.Append( ">" ); break; default: string unknownCommandString = "Unknown FFCC text command 0x" + b.ToString( "X2" ); sb.Append( "<" + unknownCommandString + ">" ); break; } } } b = s.ReadByte(); } return sb.ToString(); }