From 36b0117ebcd1f413f18c2a228a2dd78478c4d546 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Mon, 3 Oct 2022 00:22:41 +0300 Subject: [PATCH] [ExeFS] LZ77 decompression for .code file --- CMakeLists.txt | 1 + include/loader/lz77.hpp | 88 ++++++++++++++++++++++++++++++++++++++++ src/core/loader/ncch.cpp | 26 +++++++++--- 3 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 include/loader/lz77.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ed72f317..3810bf5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc include/PICA/gpu.hpp include/PICA/regs.hpp include/services/ndm.hpp include/PICA/shader.hpp include/PICA/shader_unit.hpp include/PICA/float_types.hpp include/logger.hpp include/loader/ncch.hpp include/loader/ncsd.hpp include/io_file.hpp + include/loader/lz77.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp diff --git a/include/loader/lz77.hpp b/include/loader/lz77.hpp new file mode 100644 index 00000000..131459a5 --- /dev/null +++ b/include/loader/lz77.hpp @@ -0,0 +1,88 @@ +#pragma once +#include +#include +#include "helpers.hpp" + +// For parsing the LZ77 format used for compressing the .code file in the ExeFS +namespace CartLZ77 { + // The difference in size between the compressed and decompressed file is stored + // As a footer in the compressed file. To get the decompressed size, we extract the diff + // And add it to the compressed size + static u32 decompressedSize(const u8* buffer, u32 compressedSize) { + u32 sizeDiff; + std::memcpy(&sizeDiff, buffer + compressedSize - 4, sizeof(u32)); + return sizeDiff + compressedSize; + } + + template + static u32 decompressedSize(const std::vector& buffer) { + return decompressedSize((u8*)buffer.data(), u32(buffer.size() * sizeof(T))); + } + + static bool decompress(std::vector& output, const std::vector& input) { + u32 sizeCompressed = input.size() * sizeof(u8); + u32 sizeDecompressed = decompressedSize(input); + output.resize(sizeDecompressed); + + const u8* compressed = (u8*)input.data(); + const u8* footer = compressed + sizeCompressed - 8; + + u32 bufferTopAndBottom; + std::memcpy(&bufferTopAndBottom, footer, sizeof(u32)); + + u32 out = sizeDecompressed; // TODO: Is this meant to be u32 or s32? + u32 index = sizeCompressed - ((bufferTopAndBottom >> 24) & 0xff); + u32 stopIndex = sizeCompressed - (bufferTopAndBottom & 0xffffff); + + // Set all of the decompressed buffer to 0 and copy the compressed buffer to the start of it + std::fill(output.begin(), output.end(), 0); + std::copy(input.begin(), input.end(), output.begin()); + + while (index > stopIndex) { + u8 control = compressed[--index]; + + for (uint i = 0; i < 8; i++) { + if (index <= stopIndex) + break; + if (index <= 0) + break; + if (out <= 0) + break; + + if (control & 0x80) { + // Check if compression is out of bounds + if (index < 2) + return false; + index -= 2; + + u32 segmentOffset = compressed[index] | (compressed[index + 1] << 8); + u32 segment_size = ((segmentOffset >> 12) & 15) + 3; + segmentOffset &= 0x0FFF; + segmentOffset += 2; + + // Check if compression is out of bounds + if (out < segment_size) + return false; + + for (uint j = 0; j < segment_size; j++) { + // Check if compression is out of bounds + if (out + segmentOffset >= sizeDecompressed) + return false; + + u8 data = output[out + segmentOffset]; + output[--out] = data; + } + } + else { + // Check if compression is out of bounds + if (out < 1) + return false; + output[--out] = compressed[--index]; + } + control <<= 1; + } + } + + return true; + } +} diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index aab26001..ee99189e 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -1,4 +1,5 @@ #include +#include "loader/lz77.hpp" #include "loader/ncch.hpp" #include "memory.hpp" @@ -68,8 +69,8 @@ bool NCCH::loadFromHeader(u8* header, IOFile& file) { } // ExeFS format allows up to 10 files - for (int file = 0; file < 10; file++) { - u8* fileInfo = &exeFSHeader[file * 16]; + for (int i = 0; i < 10; i++) { + u8* fileInfo = &exeFSHeader[i * 16]; char name[9]; std::memcpy(name, fileInfo, 8); // Get file name as a string @@ -79,15 +80,30 @@ bool NCCH::loadFromHeader(u8* header, IOFile& file) { u32 fileSize = *(u32*)&fileInfo[0xC]; if (fileSize != 0) { - printf("File %d. Name: %s, Size: %08X, Offset: %08X\n", file, name, fileSize, fileOffset); + printf("File %d. Name: %s, Size: %08X, Offset: %08X\n", i, name, fileSize, fileOffset); } if (std::strcmp(name, ".code") == 0) { - std::vector buff; + std::vector code; if (compressCode) { - //Helpers::panic("Compressed .code file!"); + std::vector tmp; + tmp.resize(fileSize); + + // A file offset of 0 means our file is located right after the ExeFS header + // So in the ROM, files are located at (file offset + exeFS offset + exeFS header size) + file.seek(exeFSOffset + exeFSHeaderSize + fileOffset); + file.readBytes(tmp.data(), fileSize); + + // Decompress .code file from the tmp vector to the "code" vector + if (!CartLZ77::decompress(code, tmp)) { + printf("Failed to decompress .code file\n"); + return false; + } } else { + code.resize(fileSize); + file.seek(exeFSOffset + exeFSHeaderSize + fileOffset); + file.readBytes(code.data(), fileSize); Helpers::panic("Uncompressed .code file!"); } }