From 82dd9dde1b0ff112a8857ec6864ec9dcf29cd9da Mon Sep 17 00:00:00 2001 From: scawful Date: Fri, 21 Jul 2023 03:44:44 -0400 Subject: [PATCH] Add LC_LZ2 Compression library Refactor ROM class Editor housekeeping --- src/CMakeLists.txt | 2 + src/app/core/common.h | 2 - src/app/editor/graphics_editor.cc | 29 +- src/app/editor/graphics_editor.h | 7 +- src/app/editor/master_editor.cc | 3 +- src/app/editor/master_editor.h | 8 +- src/app/editor/screen_editor.cc | 8 - src/app/editor/sprite_editor.cc | 11 + src/app/editor/sprite_editor.h | 17 ++ src/app/gfx/compression.cc | 333 +++++++++++++++++++++ src/app/gfx/compression.h | 100 +++++++ src/app/rom.cc | 467 ++++-------------------------- src/app/rom.h | 114 +++----- test/CMakeLists.txt | 1 + test/rom_test.cc | 48 ++- 15 files changed, 646 insertions(+), 504 deletions(-) create mode 100644 src/app/editor/sprite_editor.cc create mode 100644 src/app/editor/sprite_editor.h create mode 100644 src/app/gfx/compression.cc create mode 100644 src/app/gfx/compression.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5f2c2e7f..fe9519ee 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,11 +15,13 @@ set( app/editor/overworld_editor.cc app/editor/palette_editor.cc app/editor/screen_editor.cc + app/editor/sprite_editor.cc ) set( YAZE_APP_GFX_SRC app/gfx/bitmap.cc + app/gfx/compression.cc app/gfx/snes_palette.cc app/gfx/snes_tile.cc ) diff --git a/src/app/core/common.h b/src/app/core/common.h index 14d2ee5c..8b443550 100644 --- a/src/app/core/common.h +++ b/src/app/core/common.h @@ -30,8 +30,6 @@ class Editor { virtual void Replace() = 0; virtual void Goto() = 0; - - virtual void Indent() = 0; }; unsigned int SnesToPc(unsigned int addr); diff --git a/src/app/editor/graphics_editor.cc b/src/app/editor/graphics_editor.cc index 1661ddaa..8c80b2db 100644 --- a/src/app/editor/graphics_editor.cc +++ b/src/app/editor/graphics_editor.cc @@ -20,8 +20,8 @@ namespace editor { absl::Status GraphicsEditor::Update() { BEGIN_TABLE("#gfxEditTable", 2, gfx_edit_flags) - SETUP_COLUMN("Bin Importer") SETUP_COLUMN("Graphics Manager") + SETUP_COLUMN("Memory Editor") TABLE_HEADERS() NEXT_COLUMN() @@ -33,6 +33,7 @@ absl::Status GraphicsEditor::Update() { RETURN_IF_ERROR(DrawClipboardImport()) END_TAB_ITEM() END_TAB_BAR() + ImGui::Separator(); ImGui::Text("Graphics"); ImGui::Separator(); RETURN_IF_ERROR(DrawDecompressedData()) @@ -47,7 +48,6 @@ absl::Status GraphicsEditor::Update() { absl::Status GraphicsEditor::DrawFileImport() { static int size = 0; - ImGui::SetNextItemWidth(350.f); ImGui::InputText("File", file_path_, sizeof(file_path_)); ImGui::SameLine(); // Open the file dialog when the user clicks the "Browse" button @@ -98,6 +98,8 @@ absl::Status GraphicsEditor::DrawFileImport() { absl::Status GraphicsEditor::DrawClipboardImport() { static Bytes clipboard_data; + ImGui::Button("Paste"); + if (!is_open_) { clipboard_data.resize(0x1000); for (int i = 0; i < 0x1000; i++) clipboard_data.push_back(0x00); @@ -112,13 +114,14 @@ absl::Status GraphicsEditor::DrawMemoryEditor() { std::string title = "Memory Editor"; if (is_open_) { static MemoryEditor mem_edit; - mem_edit.DrawWindow(title.data(), (void *)&temp_rom_, temp_rom_.size()); + // mem_edit.DrawWindow(title.data(), (void *)&temp_rom_, temp_rom_.size()); + mem_edit.DrawContents(temp_rom_.data(), temp_rom_.size()); } return absl::OkStatus(); } absl::Status GraphicsEditor::DrawDecompressedData() { - if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)2); + if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)2); ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { import_canvas_.DrawBackground(ImVec2(0x100 + 1, (8192 * 2) + 1)); @@ -152,6 +155,24 @@ absl::Status GraphicsEditor::DecompressImportData(int size) { return absl::OkStatus(); } +absl::Status GraphicsEditor::DecompressSuperDonkey() { + for (const auto& offset : kSuperDonkeyTiles) { + int offset_value = + std::stoi(offset, nullptr, 16); // convert hex string to int + ASSIGN_OR_RETURN(auto decompressed_data, + temp_rom_.Decompress(offset_value, 0x1000)) + + } + + for (const auto& offset : kSuperDonkeySprites) { + int offset_value = + std::stoi(offset, nullptr, 16); // convert hex string to int + ASSIGN_OR_RETURN(auto decompressed_data, + temp_rom_.Decompress(offset_value, 0x1000)) + } + return absl::OkStatus(); +} + } // namespace editor } // namespace app } // namespace yaze \ No newline at end of file diff --git a/src/app/editor/graphics_editor.h b/src/app/editor/graphics_editor.h index 4844a34f..dac52583 100644 --- a/src/app/editor/graphics_editor.h +++ b/src/app/editor/graphics_editor.h @@ -52,13 +52,15 @@ class GraphicsEditor { absl::Status DrawDecompressedData(); absl::Status DecompressImportData(int size); - + + absl::Status DecompressSuperDonkey(); int current_offset_ = 0; int current_size_ = 0; int current_palette_ = 0; bool gfx_loaded_ = false; bool is_open_ = false; + bool super_donkey_ = false; char file_path_[256]; ROM rom_; @@ -71,9 +73,10 @@ class GraphicsEditor { gfx::BitmapTable graphics_bin_; PaletteEditor palette_editor_; - gfx::SNESPalette palette_; MemoryEditor memory_editor_; + gfx::SNESPalette palette_; + ImGuiTableFlags gfx_edit_flags = ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchSame; diff --git a/src/app/editor/master_editor.cc b/src/app/editor/master_editor.cc index ae9dd030..9d9f6332 100644 --- a/src/app/editor/master_editor.cc +++ b/src/app/editor/master_editor.cc @@ -141,7 +141,7 @@ void MasterEditor::DrawInfoPopup() { if (rom_info_) ImGui::OpenPopup("ROM Information"); if (ImGui::BeginPopupModal("ROM Information", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("Title: %s", rom_.GetTitle()); + ImGui::Text("Title: %s", rom_.title()); ImGui::Text("ROM Size: %ld", rom_.size()); if (ImGui::Button("Close", gui::kDefaultModalSize)) { @@ -335,6 +335,7 @@ void MasterEditor::DrawMusicEditor() { void MasterEditor::DrawSpriteEditor() { TAB_ITEM("Sprites") + status_ = sprite_editor_.Update(); END_TAB_ITEM() } diff --git a/src/app/editor/master_editor.h b/src/app/editor/master_editor.h index f39922db..b5bb092a 100644 --- a/src/app/editor/master_editor.h +++ b/src/app/editor/master_editor.h @@ -16,6 +16,7 @@ #include "app/editor/overworld_editor.h" #include "app/editor/palette_editor.h" #include "app/editor/screen_editor.h" +#include "app/editor/sprite_editor.h" #include "app/gfx/snes_palette.h" #include "app/gfx/snes_tile.h" #include "app/gui/canvas.h" @@ -57,17 +58,20 @@ class MasterEditor { bool backup_rom_ = true; bool show_status_ = false; - std::shared_ptr sdl_renderer_; absl::Status status_; absl::Status prev_status_; + std::shared_ptr sdl_renderer_; + std::shared_ptr current_editor_; + AssemblyEditor assembly_editor_; DungeonEditor dungeon_editor_; GraphicsEditor graphics_editor_; + MusicEditor music_editor_; OverworldEditor overworld_editor_; PaletteEditor palette_editor_; ScreenEditor screen_editor_; - MusicEditor music_editor_; + SpriteEditor sprite_editor_; ROM rom_; }; diff --git a/src/app/editor/screen_editor.cc b/src/app/editor/screen_editor.cc index e3422c39..37a01f68 100644 --- a/src/app/editor/screen_editor.cc +++ b/src/app/editor/screen_editor.cc @@ -140,14 +140,6 @@ void ScreenEditor::DrawMosaicEditor() { gui::InputHex("Routine Location", &overworldCustomMosaicASM); - if (ImGui::Button("Generate Mosaic Assembly")) { - auto mosaic = - rom_.PatchOverworldMosaic(mosaic_tiles_, overworldCustomMosaicASM); - if (!mosaic.ok()) { - std::cout << mosaic; - } - } - END_TAB_ITEM() } diff --git a/src/app/editor/sprite_editor.cc b/src/app/editor/sprite_editor.cc new file mode 100644 index 00000000..ba3d2a53 --- /dev/null +++ b/src/app/editor/sprite_editor.cc @@ -0,0 +1,11 @@ +#include "sprite_editor.h" + +namespace yaze { +namespace app { +namespace editor { + +absl::Status SpriteEditor::Update() { return absl::OkStatus(); } + +} // namespace editor +} // namespace app +} // namespace yaze \ No newline at end of file diff --git a/src/app/editor/sprite_editor.h b/src/app/editor/sprite_editor.h new file mode 100644 index 00000000..7fe7af10 --- /dev/null +++ b/src/app/editor/sprite_editor.h @@ -0,0 +1,17 @@ +#ifndef YAZE_APP_EDITOR_SPRITE_EDITOR_H +#define YAZE_APP_EDITOR_SPRITE_EDITOR_H + +#include "absl/status/status.h" + +namespace yaze { +namespace app { +namespace editor { +class SpriteEditor { + public: + absl::Status Update(); +}; +} // namespace editor +} // namespace app +} // namespace yaze + +#endif // YAZE_APP_EDITOR_SPRITE_EDITOR_H \ No newline at end of file diff --git a/src/app/gfx/compression.cc b/src/app/gfx/compression.cc new file mode 100644 index 00000000..abc31b7c --- /dev/null +++ b/src/app/gfx/compression.cc @@ -0,0 +1,333 @@ +#include "compression.h" + +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "app/core/constants.h" +#include "app/rom.h" + +namespace yaze { +namespace app { +namespace gfx { + +namespace lc_lz2 { + +void PrintCompressionPiece(const CompressionPiecePointer& piece) { + std::cout << "Command: " << std::to_string(piece->command) << "\n"; + std::cout << "Command Length: " << piece->length << "\n"; + std::cout << "Argument: "; + auto arg_size = piece->argument.size(); + for (int i = 0; i < arg_size; ++i) { + printf("%02X ", piece->argument.at(i)); + } + std::cout << "\nArgument Length: " << piece->argument_length << "\n"; +} + +void PrintCompressionChain(const CompressionPiecePointer& chain_head) { + auto compressed_chain = chain_head->next; + while (compressed_chain != nullptr) { + std::cout << "- Compression Piece -\n"; + PrintCompressionPiece(compressed_chain); + compressed_chain = compressed_chain->next; + } +} + +void CheckByteRepeat(const uchar* rom_data, DataSizeArray& data_size_taken, + CommandArgumentArray& cmd_args, uint& src_data_pos, + const uint last_pos) { + uint pos = src_data_pos; + char byte_to_repeat = rom_data[pos]; + while (pos <= last_pos && rom_data[pos] == byte_to_repeat) { + data_size_taken[kCommandByteFill]++; + pos++; + } + cmd_args[kCommandByteFill][0] = byte_to_repeat; +} + +void CheckWordRepeat(const uchar* rom_data, DataSizeArray& data_size_taken, + CommandArgumentArray& cmd_args, uint& src_data_pos, + const uint last_pos) { + if (src_data_pos + 2 <= last_pos && + rom_data[src_data_pos] != rom_data[src_data_pos + 1]) { + uint pos = src_data_pos; + char byte1 = rom_data[pos]; + char byte2 = rom_data[pos + 1]; + pos += 2; + data_size_taken[kCommandWordFill] = 2; + while (pos + 1 <= last_pos) { + if (rom_data[pos] == byte1 && rom_data[pos + 1] == byte2) + data_size_taken[kCommandWordFill] += 2; + else + break; + pos += 2; + } + cmd_args[kCommandWordFill][0] = byte1; + cmd_args[kCommandWordFill][1] = byte2; + } +} + +void CheckIncByte(const uchar* rom_data, DataSizeArray& data_size_taken, + CommandArgumentArray& cmd_args, uint& src_data_pos, + const uint last_pos) { + uint pos = src_data_pos; + char byte = rom_data[pos]; + pos++; + data_size_taken[kCommandIncreasingFill] = 1; + byte++; + while (pos <= last_pos && byte == rom_data[pos]) { + data_size_taken[kCommandIncreasingFill]++; + byte++; + pos++; + } + cmd_args[kCommandIncreasingFill][0] = rom_data[src_data_pos]; +} + +void CheckIntraCopy(const uchar* rom_data, DataSizeArray& data_size_taken, + CommandArgumentArray& cmd_args, uint& src_data_pos, + const uint last_pos, uint start) { + if (src_data_pos != start) { + uint searching_pos = start; + uint current_pos_u = src_data_pos; + uint copied_size = 0; + uint search_start = start; + + while (searching_pos < src_data_pos && current_pos_u <= last_pos) { + while (rom_data[current_pos_u] != rom_data[searching_pos] && + searching_pos < src_data_pos) + searching_pos++; + search_start = searching_pos; + while (current_pos_u <= last_pos && + rom_data[current_pos_u] == rom_data[searching_pos] && + searching_pos < src_data_pos) { + copied_size++; + current_pos_u++; + searching_pos++; + } + if (copied_size > data_size_taken[kCommandRepeatingBytes]) { + search_start -= start; + printf("- Found repeat of %d at %d\n", copied_size, search_start); + data_size_taken[kCommandRepeatingBytes] = copied_size; + cmd_args[kCommandRepeatingBytes][0] = search_start & kSnesByteMax; + cmd_args[kCommandRepeatingBytes][1] = search_start >> 8; + } + current_pos_u = src_data_pos; + copied_size = 0; + } + } +} + +// Check if a command managed to pick up `max_win` or more bytes +// Avoids being even with copy command, since it's possible to merge copy +void ValidateForByteGain(const DataSizeArray& data_size_taken, + const CommandSizeArray& cmd_size, uint& max_win, + uint& cmd_with_max) { + for (uint cmd_i = 1; cmd_i < 5; cmd_i++) { + uint cmd_size_taken = data_size_taken[cmd_i]; + // TODO(@scawful): Replace conditional with table of command sizes + // "Table that is even with copy but all other cmd are 2" + auto table_check = + !(cmd_i == kCommandRepeatingBytes && cmd_size_taken == 3); + if (cmd_size_taken > max_win && cmd_size_taken > cmd_size[cmd_i] && + table_check) { + printf("==> C:%d / S:%d\n", cmd_i, cmd_size_taken); + cmd_with_max = cmd_i; + max_win = cmd_size_taken; + } + } +} + +void CompressionCommandAlternative( + const uchar* rom_data, CompressionPiecePointer& compressed_chain, + const CommandSizeArray& cmd_size, const CommandArgumentArray& cmd_args, + uint& src_data_pos, uint& comp_accumulator, uint& cmd_with_max, + uint& max_win) { + printf("- Ok we get a gain from %d\n", cmd_with_max); + std::string buffer; + buffer.push_back(cmd_args[cmd_with_max][0]); + if (cmd_size[cmd_with_max] == 2) { + buffer.push_back(cmd_args[cmd_with_max][1]); + } + + auto new_comp_piece = std::make_shared( + cmd_with_max, max_win, buffer, cmd_size[cmd_with_max]); + PrintCompressionPiece(new_comp_piece); + // If we let non compressed stuff, we need to add a copy chunk before + if (comp_accumulator != 0) { + std::string copy_buff; + copy_buff.resize(comp_accumulator); + for (int i = 0; i < comp_accumulator; ++i) { + copy_buff[i] = rom_data[i + src_data_pos - comp_accumulator]; + } + auto copy_chunk = std::make_shared( + kCommandDirectCopy, comp_accumulator, copy_buff, comp_accumulator); + compressed_chain->next = copy_chunk; + compressed_chain = copy_chunk; + } else { + compressed_chain->next = new_comp_piece; + compressed_chain = new_comp_piece; + } + src_data_pos += max_win; + comp_accumulator = 0; +} + +absl::StatusOr SplitCompressionPiece( + CompressionPiecePointer& piece, int mode) { + CompressionPiecePointer new_piece; + uint length_left = piece->length - kMaxLengthCompression; + piece->length = kMaxLengthCompression; + + switch (piece->command) { + case kCommandByteFill: + case kCommandWordFill: + new_piece = std::make_shared( + piece->command, length_left, piece->argument, piece->argument_length); + break; + case kCommandIncreasingFill: + new_piece = std::make_shared( + piece->command, length_left, piece->argument, piece->argument_length); + new_piece->argument[0] = + (char)(piece->argument[0] + kMaxLengthCompression); + break; + case kCommandDirectCopy: + piece->argument_length = kMaxLengthCompression; + new_piece = std::make_shared( + piece->command, length_left, nullptr, length_left); + // MEMCPY + for (int i = 0; i < length_left; ++i) { + new_piece->argument[i] = piece->argument[i + kMaxLengthCompression]; + } + break; + case kCommandRepeatingBytes: { + piece->argument_length = kMaxLengthCompression; + uint offset = piece->argument[0] + (piece->argument[1] << 8); + new_piece = std::make_shared( + piece->command, length_left, piece->argument, piece->argument_length); + if (mode == kNintendoMode2) { + new_piece->argument[0] = + (offset + kMaxLengthCompression) & kSnesByteMax; + new_piece->argument[1] = (offset + kMaxLengthCompression) >> 8; + } + if (mode == kNintendoMode1) { + new_piece->argument[1] = + (offset + kMaxLengthCompression) & kSnesByteMax; + new_piece->argument[0] = (offset + kMaxLengthCompression) >> 8; + } + } break; + default: { + return absl::InvalidArgumentError( + "SplitCompressionCommand: Invalid Command"); + } + } + return new_piece; +} + +Bytes CreateCompressionString(CompressionPiecePointer& start, + int mode) { + uint pos = 0; + auto piece = start; + Bytes output; + + while (piece != nullptr) { + if (piece->length <= kMaxLengthNormalHeader) { // Normal header + output.push_back(BUILD_HEADER(piece->command, piece->length)); + pos++; + } else { + if (piece->length <= kMaxLengthCompression) { + output.push_back(kCompressionStringMod | ((uchar)piece->command << 2) | + (((piece->length - 1) & 0xFF00) >> 8)); + pos++; + printf("Building extended header : cmd: %d, length: %d - %02X\n", + piece->command, piece->length, output[pos - 1]); + output.push_back(((piece->length - 1) & 0x00FF)); // (char) + pos++; + } else { + // We need to split the command + auto new_piece = SplitCompressionPiece(piece, mode); + if (!new_piece.ok()) { + std::cout << new_piece.status().ToString() << std::endl; + } + printf("New added piece\n"); + auto piece_data = new_piece.value(); + PrintCompressionPiece(piece_data); + piece_data->next = piece->next; + piece->next = piece_data; + continue; + } + } + + if (piece->command == kCommandRepeatingBytes) { + char tmp[2]; + tmp[0] = piece->argument[0]; + tmp[1] = piece->argument[1]; + if (mode == kNintendoMode1) { + tmp[0] = piece->argument[1]; + tmp[1] = piece->argument[0]; + } + for (const auto& each : tmp) { + output.push_back(each); + pos++; + } + } else { + for (int i = 0; i < piece->argument_length; ++i) { + output.push_back(piece->argument[i]); + pos++; + } + } + pos += piece->argument_length; + piece = piece->next; + } + output.push_back(kSnesByteMax); + return output; +} + +absl::Status ValidateCompressionResult(CompressionPiecePointer& chain_head, + int mode, int start, int src_data_pos) { + if (chain_head->next != nullptr) { + ROM temp_rom; + RETURN_IF_ERROR( + temp_rom.LoadFromBytes(CreateCompressionString(chain_head->next, mode))) + ASSIGN_OR_RETURN(auto decomp_data, temp_rom.Decompress(0, temp_rom.size())) + if (!std::equal(decomp_data.begin() + start, decomp_data.end(), + temp_rom.begin())) { + return absl::InternalError(absl::StrFormat( + "Compressed data does not match uncompressed data at %d\n", + (uint)(src_data_pos - start))); + } + } + return absl::OkStatus(); +} + +// Merge consecutive copy if possible +CompressionPiecePointer MergeCopy(CompressionPiecePointer& start) { + CompressionPiecePointer piece = start; + + while (piece != nullptr) { + if (piece->command == kCommandDirectCopy && piece->next != nullptr && + piece->next->command == kCommandDirectCopy && + piece->length + piece->next->length <= kMaxLengthCompression) { + uint previous_length = piece->length; + piece->length = piece->length + piece->next->length; + + for (int i = 0; i < piece->next->argument_length; ++i) { + piece->argument[i + previous_length] = piece->next->argument[i]; + } + piece->argument_length = piece->length; + PrintCompressionPiece(piece); + + auto p_next_next = piece->next->next; + piece->next = p_next_next; + continue; // Next could be another copy + } + piece = piece->next; + } + return start; +} + +} // namespace lc_lz2 +} // namespace gfx +} // namespace app +} // namespace yaze \ No newline at end of file diff --git a/src/app/gfx/compression.h b/src/app/gfx/compression.h new file mode 100644 index 00000000..649b16ad --- /dev/null +++ b/src/app/gfx/compression.h @@ -0,0 +1,100 @@ +#ifndef YAZE_APP_GFX_COMPRESSION_H +#define YAZE_APP_GFX_COMPRESSION_H + +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "app/core/constants.h" + +#define BUILD_HEADER(command, length) (command << 5) + (length - 1) + +namespace yaze { +namespace app { +namespace gfx { + +namespace lc_lz2 { + +constexpr int kCommandDirectCopy = 0; +constexpr int kCommandByteFill = 1; +constexpr int kCommandWordFill = 2; +constexpr int kCommandIncreasingFill = 3; +constexpr int kCommandRepeatingBytes = 4; +constexpr int kCommandLongLength = 7; +constexpr int kMaxLengthNormalHeader = 32; +constexpr int kMaxLengthCompression = 1024; +constexpr int kNintendoMode1 = 0; +constexpr int kNintendoMode2 = 1; +constexpr int kSnesByteMax = 0xFF; +constexpr int kCommandMod = 0x07; +constexpr int kExpandedMod = 0xE0; +constexpr int kExpandedLengthMod = 0x3FF; +constexpr int kNormalLengthMod = 0x1F; +constexpr int kCompressionStringMod = 7 << 5; + +using CommandArgumentArray = std::array, 5>; +using CommandSizeArray = std::array; +using DataSizeArray = std::array; +struct CompressionPiece { + char command; + int length; + int argument_length; + std::string argument; + std::shared_ptr next = nullptr; + CompressionPiece() = default; + CompressionPiece(int cmd, int len, std::string args, int arg_len) + : command(cmd), length(len), argument_length(arg_len), argument(args) {} +}; +using CompressionPiece = struct CompressionPiece; +using CompressionPiecePointer = std::shared_ptr; + +void PrintCompressionPiece(const CompressionPiecePointer& piece); + +void PrintCompressionChain(const CompressionPiecePointer& chain_head); + +void CheckByteRepeat(const uchar* rom_data, DataSizeArray& data_size_taken, + CommandArgumentArray& cmd_args, uint& src_data_pos, + const uint last_pos); + +void CheckWordRepeat(const uchar* rom_data, DataSizeArray& data_size_taken, + CommandArgumentArray& cmd_args, uint& src_data_pos, + const uint last_pos); + +void CheckIncByte(const uchar* rom_data, DataSizeArray& data_size_taken, + CommandArgumentArray& cmd_args, uint& src_data_pos, + const uint last_pos); + +void CheckIntraCopy(const uchar* rom_data, DataSizeArray& data_size_taken, + CommandArgumentArray& cmd_args, uint& src_data_pos, + const uint last_pos, uint start); + +void ValidateForByteGain(const DataSizeArray& data_size_taken, + const CommandSizeArray& cmd_size, uint& max_win, + uint& cmd_with_max); + +void CompressionCommandAlternative(const uchar* rom_data, + CompressionPiecePointer& compressed_chain, + const CommandSizeArray& cmd_size, + const CommandArgumentArray& cmd_args, + uint& src_data_pos, uint& comp_accumulator, + uint& cmd_with_max, uint& max_win); + +absl::StatusOr SplitCompressionPiece( + CompressionPiecePointer& piece, int mode); + +Bytes CreateCompressionString(CompressionPiecePointer& start, int mode); + +absl::Status ValidateCompressionResult(CompressionPiecePointer& chain_head, + int mode, int start, int src_data_pos); + +CompressionPiecePointer MergeCopy(CompressionPiecePointer& start); + +} // namespace lc_lz2 + +} // namespace gfx +} // namespace app +} // namespace yaze + +#endif // YAZE_APP_GFX_COMPRESSION_H \ No newline at end of file diff --git a/src/app/rom.cc b/src/app/rom.cc index 256af9c0..60ca7714 100644 --- a/src/app/rom.cc +++ b/src/app/rom.cc @@ -21,327 +21,16 @@ #include "app/core/common.h" #include "app/core/constants.h" #include "app/gfx/bitmap.h" +#include "app/gfx/compression.h" #include "app/gfx/snes_tile.h" namespace yaze { namespace app { -namespace lc_lz2 { - -void PrintCompressionPiece(const std::shared_ptr& piece) { - printf("Command: %d\n", piece->command); - printf("Command kength: %d\n", piece->length); - printf("Argument:"); - auto arg_size = piece->argument.size(); - for (int i = 0; i < arg_size; ++i) { - printf("%02X ", piece->argument.at(i)); - } - printf("\nArgument length: %d\n", piece->argument_length); -} - -void PrintCompressionChain( - const std::shared_ptr& compressed_chain_start) { - auto compressed_chain = compressed_chain_start->next; - while (compressed_chain != nullptr) { - printf("- Compression Piece -\n"); - PrintCompressionPiece(compressed_chain); - compressed_chain = compressed_chain->next; - } -} - -void CheckByteRepeat(const uchar* rom_data, DataSizeArray& data_size_taken, - CommandArgumentArray& cmd_args, uint& src_data_pos, - const uint last_pos) { - uint pos = src_data_pos; - char byte_to_repeat = rom_data[pos]; - while (pos <= last_pos && rom_data[pos] == byte_to_repeat) { - data_size_taken[kCommandByteFill]++; - pos++; - } - cmd_args[kCommandByteFill][0] = byte_to_repeat; -} - -void CheckWordRepeat(const uchar* rom_data, DataSizeArray& data_size_taken, - CommandArgumentArray& cmd_args, uint& src_data_pos, - const uint last_pos) { - if (src_data_pos + 2 <= last_pos && - rom_data[src_data_pos] != rom_data[src_data_pos + 1]) { - uint pos = src_data_pos; - char byte1 = rom_data[pos]; - char byte2 = rom_data[pos + 1]; - pos += 2; - data_size_taken[kCommandWordFill] = 2; - while (pos + 1 <= last_pos) { - if (rom_data[pos] == byte1 && rom_data[pos + 1] == byte2) - data_size_taken[kCommandWordFill] += 2; - else - break; - pos += 2; - } - cmd_args[kCommandWordFill][0] = byte1; - cmd_args[kCommandWordFill][1] = byte2; - } -} - -void CheckIncByte(const uchar* rom_data, DataSizeArray& data_size_taken, - CommandArgumentArray& cmd_args, uint& src_data_pos, - const uint last_pos) { - uint pos = src_data_pos; - char byte = rom_data[pos]; - pos++; - data_size_taken[kCommandIncreasingFill] = 1; - byte++; - while (pos <= last_pos && byte == rom_data[pos]) { - data_size_taken[kCommandIncreasingFill]++; - byte++; - pos++; - } - cmd_args[kCommandIncreasingFill][0] = rom_data[src_data_pos]; -} - -void CheckIntraCopy(const uchar* rom_data, DataSizeArray& data_size_taken, - CommandArgumentArray& cmd_args, uint& src_data_pos, - const uint last_pos, uint start) { - if (src_data_pos != start) { - uint searching_pos = start; - uint current_pos_u = src_data_pos; - uint copied_size = 0; - uint search_start = start; - - while (searching_pos < src_data_pos && current_pos_u <= last_pos) { - while (rom_data[current_pos_u] != rom_data[searching_pos] && - searching_pos < src_data_pos) - searching_pos++; - search_start = searching_pos; - while (current_pos_u <= last_pos && - rom_data[current_pos_u] == rom_data[searching_pos] && - searching_pos < src_data_pos) { - copied_size++; - current_pos_u++; - searching_pos++; - } - if (copied_size > data_size_taken[kCommandRepeatingBytes]) { - search_start -= start; - printf("- Found repeat of %d at %d\n", copied_size, search_start); - data_size_taken[kCommandRepeatingBytes] = copied_size; - cmd_args[kCommandRepeatingBytes][0] = search_start & kSnesByteMax; - cmd_args[kCommandRepeatingBytes][1] = search_start >> 8; - } - current_pos_u = src_data_pos; - copied_size = 0; - } - } -} - -// Check if a command managed to pick up `max_win` or more bytes -// Avoids being even with copy command, since it's possible to merge copy -void ValidateForByteGain(const DataSizeArray& data_size_taken, - const CommandSizeArray& cmd_size, uint& max_win, - uint& cmd_with_max) { - for (uint cmd_i = 1; cmd_i < 5; cmd_i++) { - uint cmd_size_taken = data_size_taken[cmd_i]; - // TODO(@scawful): Replace conditional with table of command sizes - // "Table that is even with copy but all other cmd are 2" - auto table_check = - !(cmd_i == kCommandRepeatingBytes && cmd_size_taken == 3); - if (cmd_size_taken > max_win && cmd_size_taken > cmd_size[cmd_i] && - table_check) { - printf("==> C:%d / S:%d\n", cmd_i, cmd_size_taken); - cmd_with_max = cmd_i; - max_win = cmd_size_taken; - } - } -} - -void CompressionCommandAlternative( - const uchar* rom_data, std::shared_ptr& compressed_chain, - const CommandSizeArray& cmd_size, const CommandArgumentArray& cmd_args, - uint& src_data_pos, uint& comp_accumulator, uint& cmd_with_max, - uint& max_win) { - printf("- Ok we get a gain from %d\n", cmd_with_max); - std::string buffer; - buffer.push_back(cmd_args[cmd_with_max][0]); - if (cmd_size[cmd_with_max] == 2) { - buffer.push_back(cmd_args[cmd_with_max][1]); - } - - auto new_comp_piece = std::make_shared( - cmd_with_max, max_win, buffer, cmd_size[cmd_with_max]); - PrintCompressionPiece(new_comp_piece); - // If we let non compressed stuff, we need to add a copy chunk before - if (comp_accumulator != 0) { - std::string copy_buff; - copy_buff.resize(comp_accumulator); - for (int i = 0; i < comp_accumulator; ++i) { - copy_buff[i] = rom_data[i + src_data_pos - comp_accumulator]; - } - auto copy_chunk = std::make_shared( - kCommandDirectCopy, comp_accumulator, copy_buff, comp_accumulator); - compressed_chain->next = copy_chunk; - compressed_chain = copy_chunk; - } else { - compressed_chain->next = new_comp_piece; - compressed_chain = new_comp_piece; - } - src_data_pos += max_win; - comp_accumulator = 0; -} - -absl::StatusOr> SplitCompressionPiece( - std::shared_ptr& piece, int mode) { - std::shared_ptr new_piece; - uint length_left = piece->length - kMaxLengthCompression; - piece->length = kMaxLengthCompression; - - switch (piece->command) { - case kCommandByteFill: - case kCommandWordFill: - new_piece = std::make_shared( - piece->command, length_left, piece->argument, piece->argument_length); - break; - case kCommandIncreasingFill: - new_piece = std::make_shared( - piece->command, length_left, piece->argument, piece->argument_length); - new_piece->argument[0] = - (char)(piece->argument[0] + kMaxLengthCompression); - break; - case kCommandDirectCopy: - piece->argument_length = kMaxLengthCompression; - new_piece = std::make_shared( - piece->command, length_left, nullptr, length_left); - // MEMCPY - for (int i = 0; i < length_left; ++i) { - new_piece->argument[i] = piece->argument[i + kMaxLengthCompression]; - } - break; - case kCommandRepeatingBytes: { - piece->argument_length = kMaxLengthCompression; - uint offset = piece->argument[0] + (piece->argument[1] << 8); - new_piece = std::make_shared( - piece->command, length_left, piece->argument, piece->argument_length); - if (mode == kNintendoMode2) { - new_piece->argument[0] = - (offset + kMaxLengthCompression) & kSnesByteMax; - new_piece->argument[1] = (offset + kMaxLengthCompression) >> 8; - } - if (mode == kNintendoMode1) { - new_piece->argument[1] = - (offset + kMaxLengthCompression) & kSnesByteMax; - new_piece->argument[0] = (offset + kMaxLengthCompression) >> 8; - } - } break; - default: { - return absl::InvalidArgumentError( - "SplitCompressionCommand: Invalid Command"); - } - } - return new_piece; -} - -Bytes CreateCompressionString(std::shared_ptr& start, - int mode) { - uint pos = 0; - auto piece = start; - Bytes output; - - while (piece != nullptr) { - if (piece->length <= kMaxLengthNormalHeader) { // Normal header - output.push_back(BUILD_HEADER(piece->command, piece->length)); - pos++; - } else { - if (piece->length <= kMaxLengthCompression) { - output.push_back(kCompressionStringMod | ((uchar)piece->command << 2) | - (((piece->length - 1) & 0xFF00) >> 8)); - pos++; - printf("Building extended header : cmd: %d, length: %d - %02X\n", - piece->command, piece->length, output[pos - 1]); - output.push_back(((piece->length - 1) & 0x00FF)); // (char) - pos++; - } else { - // We need to split the command - auto new_piece = SplitCompressionPiece(piece, mode); - if (!new_piece.ok()) { - std::cout << new_piece.status().ToString() << std::endl; - } - printf("New added piece\n"); - auto piece_data = new_piece.value(); - PrintCompressionPiece(piece_data); - piece_data->next = piece->next; - piece->next = piece_data; - continue; - } - } - - if (piece->command == kCommandRepeatingBytes) { - char tmp[2]; - tmp[0] = piece->argument[0]; - tmp[1] = piece->argument[1]; - if (mode == kNintendoMode1) { - tmp[0] = piece->argument[1]; - tmp[1] = piece->argument[0]; - } - for (const auto& each : tmp) { - output.push_back(each); - pos++; - } - } else { - for (int i = 0; i < piece->argument_length; ++i) { - output.push_back(piece->argument[i]); - pos++; - } - } - pos += piece->argument_length; - piece = piece->next; - } - output.push_back(kSnesByteMax); - return output; -} - -absl::Status ValidateCompressionResult( - CompressionPiecePointer& compressed_chain_start, int mode, int start, - int src_data_pos) { - if (compressed_chain_start->next != nullptr) { - ROM temp_rom; - RETURN_IF_ERROR(temp_rom.LoadFromBytes( - CreateCompressionString(compressed_chain_start->next, mode))) - ASSIGN_OR_RETURN(auto decomp_data, temp_rom.Decompress(0, temp_rom.size())) - if (!std::equal(decomp_data.begin() + start, decomp_data.end(), - temp_rom.begin())) { - return absl::InternalError(absl::StrFormat( - "Compressed data does not match uncompressed data at %d\n", - (uint)(src_data_pos - start))); - } - } - return absl::OkStatus(); -} - -// Merge consecutive copy if possible -CompressionPiecePointer MergeCopy(CompressionPiecePointer& start) { - CompressionPiecePointer piece = start; - - while (piece != nullptr) { - if (piece->command == kCommandDirectCopy && piece->next != nullptr && - piece->next->command == kCommandDirectCopy && - piece->length + piece->next->length <= kMaxLengthCompression) { - uint previous_length = piece->length; - piece->length = piece->length + piece->next->length; - - for (int i = 0; i < piece->next->argument_length; ++i) { - piece->argument[i + previous_length] = piece->next->argument[i]; - } - piece->argument_length = piece->length; - PrintCompressionPiece(piece); - - auto p_next_next = piece->next->next; - piece->next = p_next_next; - continue; // Next could be another copy - } - piece = piece->next; - } - return start; -} - -} // namespace lc_lz2 +using gfx::lc_lz2::CompressionPiece; +using gfx::lc_lz2::kCommandDirectCopy; +using gfx::lc_lz2::kCommandMod; +using gfx::lc_lz2::kSnesByteMax; namespace { @@ -358,13 +47,16 @@ int GetGraphicsAddress(const uchar* data, uint8_t offset) { // TODO TEST compressed data border for each cmd absl::StatusOr ROM::Compress(const int start, const int length, int mode, bool check) { + if (length == 0) { + return Bytes(); + } // Worse case should be a copy of the string with extended header auto compressed_chain = std::make_shared(1, 1, "aaa", 2); auto compressed_chain_start = compressed_chain; - CommandArgumentArray cmd_args = {{}}; - DataSizeArray data_size_taken = {0, 0, 0, 0, 0}; - CommandSizeArray cmd_size = {0, 1, 2, 1, 2}; + gfx::lc_lz2::CommandArgumentArray cmd_args = {{}}; + gfx::lc_lz2::DataSizeArray data_size_taken = {0, 0, 0, 0, 0}; + gfx::lc_lz2::CommandSizeArray cmd_size = {0, 1, 2, 1, 2}; uint src_data_pos = start; uint last_pos = start + length - 1; @@ -374,19 +66,19 @@ absl::StatusOr ROM::Compress(const int start, const int length, int mode, data_size_taken.fill({}); cmd_args.fill({{}}); - lc_lz2::CheckByteRepeat(rom_data_.data(), data_size_taken, cmd_args, - src_data_pos, last_pos); - lc_lz2::CheckWordRepeat(rom_data_.data(), data_size_taken, cmd_args, - src_data_pos, last_pos); - lc_lz2::CheckIncByte(rom_data_.data(), data_size_taken, cmd_args, - src_data_pos, last_pos); - lc_lz2::CheckIntraCopy(rom_data_.data(), data_size_taken, cmd_args, - src_data_pos, last_pos, start); + gfx::lc_lz2::CheckByteRepeat(rom_data_.data(), data_size_taken, cmd_args, + src_data_pos, last_pos); + gfx::lc_lz2::CheckWordRepeat(rom_data_.data(), data_size_taken, cmd_args, + src_data_pos, last_pos); + gfx::lc_lz2::CheckIncByte(rom_data_.data(), data_size_taken, cmd_args, + src_data_pos, last_pos); + gfx::lc_lz2::CheckIntraCopy(rom_data_.data(), data_size_taken, cmd_args, + src_data_pos, last_pos, start); uint max_win = 2; uint cmd_with_max = kCommandDirectCopy; - lc_lz2::ValidateForByteGain(data_size_taken, cmd_size, max_win, - cmd_with_max); + gfx::lc_lz2::ValidateForByteGain(data_size_taken, cmd_size, max_win, + cmd_with_max); if (cmd_with_max == kCommandDirectCopy) { // This is the worst case scenario @@ -408,7 +100,7 @@ absl::StatusOr ROM::Compress(const int start, const int length, int mode, comp_accumulator = 0; } } else { - lc_lz2::CompressionCommandAlternative( + gfx::lc_lz2::CompressionCommandAlternative( rom_data_.data(), compressed_chain, cmd_size, cmd_args, src_data_pos, comp_accumulator, cmd_with_max, max_win); } @@ -419,28 +111,33 @@ absl::StatusOr ROM::Compress(const int start, const int length, int mode, } if (check) { - RETURN_IF_ERROR(lc_lz2::ValidateCompressionResult( + RETURN_IF_ERROR(gfx::lc_lz2::ValidateCompressionResult( compressed_chain_start, mode, start, src_data_pos)) } } // Skipping compression chain header - lc_lz2::MergeCopy(compressed_chain_start->next); - lc_lz2::PrintCompressionChain(compressed_chain_start); - return lc_lz2::CreateCompressionString(compressed_chain_start->next, mode); + gfx::lc_lz2::MergeCopy(compressed_chain_start->next); + gfx::lc_lz2::PrintCompressionChain(compressed_chain_start); + return gfx::lc_lz2::CreateCompressionString(compressed_chain_start->next, + mode); } absl::StatusOr ROM::CompressGraphics(const int pos, const int length) { - return Compress(pos, length, kNintendoMode2); + return Compress(pos, length, gfx::lc_lz2::kNintendoMode2); } absl::StatusOr ROM::CompressOverworld(const int pos, const int length) { - return Compress(pos, length, kNintendoMode1); + return Compress(pos, length, gfx::lc_lz2::kNintendoMode1); } // ============================================================================ absl::StatusOr ROM::Decompress(int offset, int size, int mode) { + if (size == 0) { + return Bytes(); + } + Bytes buffer(size, 0); uint length = 0; uint buffer_pos = 0; @@ -448,31 +145,32 @@ absl::StatusOr ROM::Decompress(int offset, int size, int mode) { uchar header = rom_data_[offset]; while (header != kSnesByteMax) { - if ((header & kExpandedMod) == kExpandedMod) { + if ((header & gfx::lc_lz2::kExpandedMod) == gfx::lc_lz2::kExpandedMod) { // Expanded Command command = ((header >> 2) & kCommandMod); - length = (((header << 8) | rom_data_[offset + 1]) & kExpandedLengthMod); + length = (((header << 8) | rom_data_[offset + 1]) & + gfx::lc_lz2::kExpandedLengthMod); offset += 2; // Advance 2 bytes in ROM } else { // Normal Command command = ((header >> 5) & kCommandMod); - length = (header & kNormalLengthMod); + length = (header & gfx::lc_lz2::kNormalLengthMod); offset += 1; // Advance 1 byte in ROM } length += 1; // each commands is at least of size 1 even if index 00 switch (command) { - case kCommandDirectCopy: // Does not advance in the ROM + case gfx::lc_lz2::kCommandDirectCopy: // Does not advance in the ROM memcpy(buffer.data() + buffer_pos, rom_data_.data() + offset, length); buffer_pos += length; offset += length; break; - case kCommandByteFill: + case gfx::lc_lz2::kCommandByteFill: memset(buffer.data() + buffer_pos, (int)(rom_data_[offset]), length); buffer_pos += length; offset += 1; // Advances 1 byte in the ROM break; - case kCommandWordFill: { + case gfx::lc_lz2::kCommandWordFill: { auto a = rom_data_[offset]; auto b = rom_data_[offset + 1]; for (int i = 0; i < length; i = i + 2) { @@ -482,7 +180,7 @@ absl::StatusOr ROM::Decompress(int offset, int size, int mode) { buffer_pos += length; offset += 2; // Advance 2 byte in the ROM } break; - case kCommandIncreasingFill: { + case gfx::lc_lz2::kCommandIncreasingFill: { auto inc_byte = rom_data_[offset]; for (int i = 0; i < length; i++) { buffer[buffer_pos] = inc_byte++; @@ -490,11 +188,12 @@ absl::StatusOr ROM::Decompress(int offset, int size, int mode) { } offset += 1; // Advance 1 byte in the ROM } break; - case kCommandRepeatingBytes: { + case gfx::lc_lz2::kCommandRepeatingBytes: { ushort s1 = ((rom_data_[offset + 1] & kSnesByteMax) << 8); ushort s2 = (rom_data_[offset] & kSnesByteMax); int addr = (s1 | s2); - if (mode == kNintendoMode1) { // Reversed byte order for overworld maps + if (mode == gfx::lc_lz2::kNintendoMode1) { // Reversed byte order for + // overworld maps addr = (rom_data_[offset + 1] & kSnesByteMax) | ((rom_data_[offset] & kSnesByteMax) << 8); } @@ -526,11 +225,11 @@ absl::StatusOr ROM::Decompress(int offset, int size, int mode) { } absl::StatusOr ROM::DecompressGraphics(int pos, int size) { - return Decompress(pos, size, kNintendoMode2); + return Decompress(pos, size, gfx::lc_lz2::kNintendoMode2); } absl::StatusOr ROM::DecompressOverworld(int pos, int size) { - return Decompress(pos, size, kNintendoMode1); + return Decompress(pos, size, gfx::lc_lz2::kNintendoMode1); } // ============================================================================ @@ -615,7 +314,7 @@ absl::Status ROM::LoadFromFile(const absl::string_view& filename) { } // copy ROM title - memcpy(title, rom_data_.data() + kTitleStringOffset, kTitleStringLength); + memcpy(title_, rom_data_.data() + kTitleStringOffset, kTitleStringLength); file.close(); LoadAllPalettes(); @@ -722,9 +421,9 @@ void ROM::LoadAllPalettes() { // ============================================================================ -void ROM::UpdatePaletteColor(const std::string& groupName, size_t paletteIndex, - size_t colorIndex, - const gfx::SNESColor& newColor) { +absl::Status ROM::UpdatePaletteColor(const std::string& groupName, + size_t paletteIndex, size_t colorIndex, + const gfx::SNESColor& newColor) { // Check if the groupName exists in the palette_groups_ map if (palette_groups_.find(groupName) != palette_groups_.end()) { // Check if the paletteIndex is within the range of available palettes in @@ -736,17 +435,18 @@ void ROM::UpdatePaletteColor(const std::string& groupName, size_t paletteIndex, // Update the color value in the palette palette_groups_[groupName][paletteIndex][colorIndex] = newColor; } else { - std::cerr << "Error: Invalid color index in UpdatePaletteColor." - << std::endl; + return absl::AbortedError( + "Error: Invalid color index in UpdatePaletteColor."); } } else { - std::cerr << "Error: Invalid palette index in UpdatePaletteColor." - << std::endl; + return absl::AbortedError( + "Error: Invalid palette index in UpdatePaletteColor."); } } else { - std::cerr << "Error: Invalid group name in UpdatePaletteColor." - << std::endl; + return absl::AbortedError( + "Error: Invalid group name in UpdatePaletteColor"); } + return absl::OkStatus(); } // ============================================================================ @@ -885,16 +585,16 @@ void ROM::WriteColor(uint32_t address, const gfx::SNESColor& color) { // ============================================================================ uint32_t ROM::GetPaletteAddress(const std::string& groupName, - size_t paletteIndex, size_t colorIndex) const { + size_t paletteIndex, size_t color_index) const { // Retrieve the base address for the palette group - uint32_t baseAddress = paletteGroupBaseAddresses.at(groupName); + uint32_t base_address = paletteGroupAddresses.at(groupName); // Retrieve the number of colors for each palette in the group - size_t colorsPerPalette = paletteGroupColorCounts.at(groupName); + uint32_t colors_per_palette = paletteGroupColorCounts.at(groupName); // Calculate the address for the specified color in the ROM - uint32_t address = - baseAddress + (paletteIndex * colorsPerPalette * 2) + (colorIndex * 2); + uint32_t address = base_address + (paletteIndex * colors_per_palette * 2) + + (color_index * 2); return address; } @@ -902,7 +602,7 @@ uint32_t ROM::GetPaletteAddress(const std::string& groupName, // ============================================================================ absl::Status ROM::ApplyAssembly(const absl::string_view& filename, - size_t patch_size) { + uint32_t size) { // int count = 0; // auto patch = filename.data(); // auto data = (char*)rom_data_.data(); @@ -914,46 +614,5 @@ absl::Status ROM::ApplyAssembly(const absl::string_view& filename, return absl::OkStatus(); } -// ============================================================================ - -// TODO(scawful): Test me! -absl::Status ROM::PatchOverworldMosaic( - char mosaic_tiles[core::kNumOverworldMaps], int routine_offset) { - // Write the data for the mosaic tile array used by the assembly code. - for (int i = 0; i < core::kNumOverworldMaps; i++) { - if (mosaic_tiles[i]) { - rom_data_[core::overworldCustomMosaicArray + i] = 0x01; - } else { - rom_data_[core::overworldCustomMosaicArray + i] = 0x00; - } - } - - std::string filename = "assets/asm/mosaic_change.asm"; - std::fstream file(filename, std::ios::out | std::ios::in); - if (!file.is_open()) { - return absl::InvalidArgumentError( - "Unable to open mosaic change assembly source"); - } - - std::stringstream assembly; - assembly << file.rdbuf(); - file.close(); - auto assembly_string = assembly.str(); - - if (!core::StringReplace(assembly_string, "", kMosaicChangeOffset)) { - return absl::InternalError( - "Mosaic template did not have proper `` to replace."); - } - - if (!core::StringReplace( - assembly_string, "", - absl::StrFormat("$%x", routine_offset + kSNESToPCOffset))) { - return absl::InternalError( - "Mosaic template did not have proper `` to replace."); - } - - return ApplyAssembly(filename, assembly_string.size()); -} - } // namespace app } // namespace yaze \ No newline at end of file diff --git a/src/app/rom.h b/src/app/rom.h index 7d49df6d..7baecd65 100644 --- a/src/app/rom.h +++ b/src/app/rom.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,7 @@ #include #include +#include "absl/container/flat_hash_map.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" @@ -28,60 +30,23 @@ #include "app/gfx/bitmap.h" #include "app/gfx/snes_palette.h" -#define BUILD_HEADER(command, length) (command << 5) + (length - 1) - namespace yaze { namespace app { -constexpr int kCommandDirectCopy = 0; -constexpr int kCommandByteFill = 1; -constexpr int kCommandWordFill = 2; -constexpr int kCommandIncreasingFill = 3; -constexpr int kCommandRepeatingBytes = 4; -constexpr int kCommandLongLength = 7; -constexpr int kMaxLengthNormalHeader = 32; -constexpr int kMaxLengthCompression = 1024; -constexpr int kNintendoMode1 = 0; -constexpr int kNintendoMode2 = 1; -constexpr int kTile32Num = 4432; -constexpr int kTitleStringOffset = 0x7FC0; -constexpr int kTitleStringLength = 20; constexpr int kOverworldGraphicsPos1 = 0x4F80; constexpr int kOverworldGraphicsPos2 = 0x505F; constexpr int kOverworldGraphicsPos3 = 0x513E; -constexpr int kSnesByteMax = 0xFF; -constexpr int kCommandMod = 0x07; -constexpr int kExpandedMod = 0xE0; -constexpr int kExpandedLengthMod = 0x3FF; -constexpr int kNormalLengthMod = 0x1F; -constexpr int kCompressionStringMod = 7 << 5; - -const std::string kMosaicChangeOffset = "$02AADB"; +constexpr int kTile32Num = 4432; +constexpr int kTitleStringOffset = 0x7FC0; +constexpr int kTitleStringLength = 20; constexpr int kSNESToPCOffset = 0x138000; -using CommandArgumentArray = std::array, 5>; -using CommandSizeArray = std::array; -using DataSizeArray = std::array; -struct CompressionPiece { - char command; - int length; - int argument_length; - std::string argument; - std::shared_ptr next = nullptr; - CompressionPiece() = default; - CompressionPiece(int cmd, int len, std::string args, int arg_len) - : command(cmd), length(len), argument_length(arg_len), argument(args) {} -}; -using CompressionPiece = struct CompressionPiece; -using CompressionPiecePointer = std::shared_ptr; - -const std::map paletteGroupBaseAddresses = { +const absl::flat_hash_map paletteGroupAddresses = { {"ow_main", core::overworldPaletteMain}, {"ow_aux", core::overworldPaletteAuxialiary}, {"ow_animated", core::overworldPaletteAnimated}, {"hud", core::hudPalettes}, - {"global_sprites", - core::globalSpritePalettesLW}, // Assuming LW is the first palette + {"global_sprites", core::globalSpritePalettesLW}, {"armors", core::armorPalettes}, {"swords", core::swordPalettes}, {"shields", core::shieldPalettes}, @@ -94,30 +59,20 @@ const std::map paletteGroupBaseAddresses = { {"ow_mini_map", core::overworldMiniMapPalettes}, }; -const std::map paletteGroupColorCounts = { - {"ow_main", 35}, - {"ow_aux", 21}, - {"ow_animated", 7}, - {"hud", 32}, - {"global_sprites", 60}, // Assuming both LW and DW - // palettes have the same - // color count - {"armors", 15}, - {"swords", 3}, - {"shields", 4}, - {"sprites_aux1", 7}, - {"sprites_aux2", 7}, - {"sprites_aux3", 7}, - {"dungeon_main", 90}, - {"grass", 1}, // Assuming grass palettes are - // individual colors - {"3d_object", 8}, // Assuming both triforcePalette and crystalPalette have - // the same color count - {"ow_mini_map", 128}, +const absl::flat_hash_map paletteGroupColorCounts = { + {"ow_main", 35}, {"ow_aux", 21}, {"ow_animated", 7}, + {"hud", 32}, {"global_sprites", 60}, {"armors", 15}, + {"swords", 3}, {"shields", 4}, {"sprites_aux1", 7}, + {"sprites_aux2", 7}, {"sprites_aux3", 7}, {"dungeon_main", 90}, + {"grass", 1}, {"3d_object", 8}, {"ow_mini_map", 128}, }; class ROM { public: + // Assembly functions + absl::Status ApplyAssembly(const absl::string_view& filename, uint32_t size); + + // Compression function absl::StatusOr Compress(const int start, const int length, int mode = 1, bool check = false); absl::StatusOr CompressGraphics(const int pos, const int length); @@ -137,40 +92,36 @@ class ROM { // Save functions absl::Status SaveToFile(bool backup, absl::string_view filename = ""); - void UpdatePaletteColor(const std::string& groupName, size_t paletteIndex, - size_t colorIndex, const gfx::SNESColor& newColor); + absl::Status UpdatePaletteColor(const std::string& groupName, + size_t paletteIndex, size_t colorIndex, + const gfx::SNESColor& newColor); void SaveAllPalettes(); + // Read functions gfx::SNESColor ReadColor(int offset); gfx::SNESPalette ReadPalette(int offset, int num_colors); + // Write functions void Write(int addr, int value); void WriteShort(int addr, int value); void WriteColor(uint32_t address, const gfx::SNESColor& color); + Bytes GetGraphicsBuffer() const { return graphics_buffer_; } + gfx::BitmapTable GetGraphicsBin() const { return graphics_bin_; } + uint32_t GetPaletteAddress(const std::string& groupName, size_t paletteIndex, size_t colorIndex) const; - absl::Status ApplyAssembly(const absl::string_view& filename, - size_t patch_size); - absl::Status PatchOverworldMosaic(char mosaic_tiles[core::kNumOverworldMaps], - int routine_offset); - - gfx::BitmapTable GetGraphicsBin() const { return graphics_bin_; } - auto GetGraphicsBuffer() const { return graphics_buffer_; } - - auto GetPaletteGroup(const std::string& group) { + gfx::PaletteGroup GetPaletteGroup(const std::string& group) { return palette_groups_[group]; } - auto GetTitle() const { return title; } - void SetupRenderer(std::shared_ptr renderer) { - renderer_ = renderer; - } + + auto title() const { return title_; } auto size() const { return size_; } - auto isLoaded() const { return is_loaded_; } auto begin() { return rom_data_.begin(); } auto end() { return rom_data_.end(); } auto data() { return rom_data_.data(); } + auto isLoaded() const { return is_loaded_; } auto char_data() { return reinterpret_cast(rom_data_.data()); } uchar& operator[](int i) { @@ -193,15 +144,18 @@ class ROM { return (ushort)((rom_data_[offset + 1]) << 8) | rom_data_[offset]; } + void SetupRenderer(std::shared_ptr renderer) { + renderer_ = renderer; + } + void RenderBitmap(gfx::Bitmap* bitmap) const { bitmap->CreateTexture(renderer_); } private: long size_ = 0; - uchar title[21] = "ROM Not Loaded"; - bool isbpp3[223]; bool is_loaded_ = false; + uchar title_[21] = "ROM Not Loaded"; std::string filename_; Bytes rom_data_; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6cc4c9ac..427de732 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable( ../src/app/gfx/bitmap.cc ../src/app/gfx/snes_tile.cc ../src/app/gfx/snes_palette.cc + ../src/app/gfx/compression.cc ../src/app/core/common.cc # ${ASAR_STATIC_SRC} ) diff --git a/test/rom_test.cc b/test/rom_test.cc index 1f1af99c..26b4e10a 100644 --- a/test/rom_test.cc +++ b/test/rom_test.cc @@ -6,14 +6,15 @@ #include #include "absl/status/statusor.h" +#include "app/gfx/compression.h" #define BUILD_HEADER(command, length) (command << 5) + (length - 1) namespace yaze_test { namespace rom_test { -using yaze::app::CompressionPiece; using yaze::app::ROM; +using yaze::app::gfx::lc_lz2::CompressionPiece; using ::testing::ElementsAreArray; using ::testing::TypedEq; @@ -311,5 +312,50 @@ TEST(ROMTest, ExtendedHeaderDecompress2) { } } */ + +TEST(ROMTest, CompressionDecompressionEmptyData) { + ROM rom; + uchar empty_input[0] = {}; + auto comp_result = ExpectCompressOk(rom, empty_input, 0); + EXPECT_EQ(0, comp_result.size()); + + auto decomp_result = ExpectDecompressOk(rom, empty_input, 0); + EXPECT_EQ(0, decomp_result.size()); +} + +TEST(ROMTest, CompressionDecompressionSingleByte) { + ROM rom; + uchar single_byte[1] = {0x2A}; + uchar single_byte_expected[3] = {BUILD_HEADER(0x00, 0x01), 0x2A, 0xFF}; + + auto comp_result = ExpectCompressOk(rom, single_byte, 1); + EXPECT_THAT(single_byte_expected, ElementsAreArray(comp_result.data(), 3)); + + auto decomp_result = ExpectDecompressOk(rom, single_byte, 1); + EXPECT_THAT(single_byte, ElementsAreArray(decomp_result.data(), 1)); +} + +TEST(ROMTest, CompressionDecompressionAllBitsSet) { + ROM rom; + uchar all_bits_set[5] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + uchar all_bits_set_expected[3] = {BUILD_HEADER(0x01, 0x05), 0xFF, 0xFF}; + + auto comp_result = ExpectCompressOk(rom, all_bits_set, 5); + EXPECT_THAT(all_bits_set_expected, ElementsAreArray(comp_result.data(), 3)); + + auto decomp_result = ExpectDecompressOk(rom, all_bits_set, 5); + EXPECT_THAT(all_bits_set, ElementsAreArray(decomp_result.data(), 5)); +} + +TEST(ROMTest, DecompressionInvalidData) { + ROM rom; + Bytes invalid_input = {0xFF, 0xFF}; // Invalid command + + auto load_status = rom.LoadFromBytes(invalid_input); + EXPECT_TRUE(load_status.ok()); + auto decompression_status = rom.Decompress(0, invalid_input.size()); + EXPECT_FALSE(decompression_status.ok()); // Expect failure +} + } // namespace rom_test } // namespace yaze_test \ No newline at end of file