/// <summary> /// Compresses arm9/overlays with a variant of LZSS encoding specific to binary files. The files have an /// uncompressed header section followed by a compressed body, but they are actually decompressed backwards /// starting from the end of the file until reaching the header. This approach allows the file to be /// decompressed in-place once it is loaded into memory, eventually overwriting the entire compressed section /// with the decompressed file as it works backwards to the header. /// </summary> /// <param name="uncompressedData">The raw binary file data.</param> /// <param name="headerLength">The length of the uncompressed header (usually 0x4000).</param> /// <returns>The compressed binary file data.</returns> private static byte[] CompressBinary(byte[] uncompressedData, int headerLength) { // Make a copy of the data that needs to be compressed and reverse it so the standard LZ77 compression // algorithm can handle it byte[] inputData = new byte[uncompressedData.Length - headerLength]; for (int destOffset = 0, sourceOffset = uncompressedData.Length - 1; sourceOffset >= headerLength; sourceOffset--) { inputData[destOffset++] = uncompressedData[sourceOffset]; } // Compress the data now that it is in the correct order byte[] outputData = Compress(inputData, LzssBufferSize, OnzCompressionType, false, BinaryMinDistance); int outputDataLength = headerLength + outputData.Length; int compressedDataLength = RoundUp(outputDataLength) + (2 * PointerLength); // Create a new array to hold the entire file byte[] compressedData = new byte[compressedDataLength]; // Copy the uncompressed header directly from the original data Array.Copy(uncompressedData, 0, compressedData, 0, headerLength); // Append the compressed data, returning it to reverse order for (int destOffset = headerLength, sourceOffset = outputData.Length - 1; sourceOffset >= 0; sourceOffset--) { compressedData[destOffset++] = outputData[sourceOffset]; } // Pad the compressed data to the next 4-byte boundary, so the length information in the footer will be // properly aligned for (int i = outputDataLength; i < RoundUp(outputDataLength); i++) { compressedData[i] = Padding; } // First comes the length of the compressed data (not including the uncompressed header) which takes up 3 // bytes, with the 4th byte holding the length of the footer (including any padding) int footerLength = compressedDataLength - outputDataLength; int compressedLengthPointer = (compressedDataLength - headerLength) | (footerLength << 24); StreamHelper.WriteBytes(compressedLengthPointer, compressedData, compressedDataLength - (2 * PointerLength)); // Next comes the difference between the file sizes of the compressed and uncompressed binaries, which // tells the processor how far out to put the start of the decompressed data int decompressedLengthPointer = uncompressedData.Length - compressedDataLength; StreamHelper.WriteBytes(decompressedLengthPointer, compressedData, compressedDataLength - PointerLength); return(compressedData); }
/// <summary> /// Compresses an arm9 executable with a variant of LZSS encoding specific to binary files. /// </summary> /// <param name="uncompressedData">The uncompressed arm9 file data.</param> /// <param name="headerLength">The length of the uncompressed header (usually 0x4000).</param> /// <param name="footer">The footer stripped from the original arm9 file during decompression.</param> /// <returns>The compressed arm9 file data.</returns> public static byte[] CompressArm9(byte[] uncompressedData, int headerLength, byte[] footer) { // Perform the standard compression routine byte[] compressedData = CompressBinary(uncompressedData, headerLength); // Arm9 executables have an additional footer pointing to the location in the decompressed section that // holds some important pointers, which needs to be appended to the compressed data byte[] arm9Data = new byte[compressedData.Length + footer.Length]; Array.Copy(compressedData, 0, arm9Data, 0, compressedData.Length); Array.Copy(footer, 0, arm9Data, compressedData.Length, footer.Length); // Update the pointer to the end of the compressed data int footerPointerOffset = BitConverter.ToInt32(footer, PointerLength) + 0x14; StreamHelper.WriteBytes(compressedData.Length | 0x02000000, arm9Data, footerPointerOffset); return(arm9Data); }