diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 386fcdc4..8bba2238 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,11 @@ set( app/emu/snes.cc ) +set( + YAZE_UTIL_SRC + util/bps.cc +) + set(YAZE_RESOURCE_FILES ${CMAKE_SOURCE_DIR}/assets/layouts/overworld.zeml ${CMAKE_SOURCE_DIR}/assets/font/Karla-Regular.ttf diff --git a/src/app/app.cmake b/src/app/app.cmake index 3d9e3fb7..a4e93cda 100644 --- a/src/app/app.cmake +++ b/src/app/app.cmake @@ -15,6 +15,7 @@ if (APPLE) ${YAZE_APP_EDITOR_SRC} ${YAZE_APP_GFX_SRC} ${YAZE_APP_ZELDA3_SRC} + ${YAZE_UTIL_SRC} ${YAZE_GUI_SRC} ${IMGUI_SRC} # Bundled Resources @@ -30,6 +31,7 @@ else() ${YAZE_APP_EDITOR_SRC} ${YAZE_APP_GFX_SRC} ${YAZE_APP_ZELDA3_SRC} + ${YAZE_UTIL_SRC} ${YAZE_GUI_SRC} ${IMGUI_SRC} ) diff --git a/src/app/core/common.cc b/src/app/core/common.cc index f404b763..cce19536 100644 --- a/src/app/core/common.cc +++ b/src/app/core/common.cc @@ -1,7 +1,5 @@ #include "common.h" -#include - #include #include #include @@ -15,37 +13,6 @@ namespace core { namespace { -void encode(uint64_t data, std::vector &output) { - while (true) { - uint8_t x = data & 0x7f; - data >>= 7; - if (data == 0) { - output.push_back(0x80 | x); - break; - } - output.push_back(x); - data--; - } -} - -uint64_t decode(const std::vector &input, size_t &offset) { - uint64_t data = 0; - uint64_t shift = 1; - while (true) { - uint8_t x = input[offset++]; - data += (x & 0x7f) * shift; - if (x & 0x80) break; - shift <<= 7; - data += shift; - } - return data; -} - -uint32_t crc32(const std::vector &data) { - uint32_t crc = ::crc32(0L, Z_NULL, 0); - return ::crc32(crc, data.data(), data.size()); -} - // "load little endian value at the given byte offset and shift to get its // value relative to the base offset (powers of 256, essentially)" unsigned ldle(uint8_t const *const p_arr, unsigned const p_index) { @@ -197,164 +164,5 @@ uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index) { return ldle16b(p_arr + (2 * p_index)); } -void CreateBpsPatch(const std::vector &source, - const std::vector &target, - std::vector &patch) { - patch.clear(); - patch.insert(patch.end(), {'B', 'P', 'S', '1'}); - - encode(source.size(), patch); - encode(target.size(), patch); - encode(0, patch); // No metadata - - size_t source_offset = 0; - size_t target_offset = 0; - int64_t source_rel_offset = 0; - int64_t target_rel_offset = 0; - - while (target_offset < target.size()) { - if (source_offset < source.size() && - source[source_offset] == target[target_offset]) { - size_t length = 0; - while (source_offset + length < source.size() && - target_offset + length < target.size() && - source[source_offset + length] == target[target_offset + length]) { - length++; - } - encode((length - 1) << 2 | 0, patch); // SourceRead - source_offset += length; - target_offset += length; - } else { - size_t length = 0; - while ( - target_offset + length < target.size() && - (source_offset + length >= source.size() || - source[source_offset + length] != target[target_offset + length])) { - length++; - } - if (length > 0) { - encode((length - 1) << 2 | 1, patch); // TargetRead - for (size_t i = 0; i < length; i++) { - patch.push_back(target[target_offset + i]); - } - target_offset += length; - } - } - - // SourceCopy - if (source_offset < source.size()) { - size_t length = 0; - int64_t offset = source_offset - source_rel_offset; - while (source_offset + length < source.size() && - target_offset + length < target.size() && - source[source_offset + length] == target[target_offset + length]) { - length++; - } - if (length > 0) { - encode((length - 1) << 2 | 2, patch); - encode((offset < 0 ? 1 : 0) | (abs(offset) << 1), patch); - source_offset += length; - target_offset += length; - source_rel_offset = source_offset; - } - } - - // TargetCopy - if (target_offset > 0) { - size_t length = 0; - int64_t offset = target_offset - target_rel_offset; - while (target_offset + length < target.size() && - target[target_offset - 1] == target[target_offset + length]) { - length++; - } - if (length > 0) { - encode((length - 1) << 2 | 3, patch); - encode((offset < 0 ? 1 : 0) | (abs(offset) << 1), patch); - target_offset += length; - target_rel_offset = target_offset; - } - } - } - - patch.resize(patch.size() + 12); // Make space for the checksums - uint32_t source_checksum = crc32(source); - uint32_t target_checksum = crc32(target); - uint32_t patch_checksum = crc32(patch); - - memcpy(patch.data() + patch.size() - 12, &source_checksum, sizeof(uint32_t)); - memcpy(patch.data() + patch.size() - 8, &target_checksum, sizeof(uint32_t)); - memcpy(patch.data() + patch.size() - 4, &patch_checksum, sizeof(uint32_t)); -} - -void ApplyBpsPatch(const std::vector &source, - const std::vector &patch, - std::vector &target) { - if (patch.size() < 4 || patch[0] != 'B' || patch[1] != 'P' || - patch[2] != 'S' || patch[3] != '1') { - throw std::runtime_error("Invalid patch format"); - } - - size_t patch_offset = 4; - uint64_t target_size = decode(patch, patch_offset); - uint64_t metadata_size = decode(patch, patch_offset); - patch_offset += metadata_size; - - target.resize(target_size); - size_t source_offset = 0; - size_t target_offset = 0; - int64_t source_rel_offset = 0; - int64_t target_rel_offset = 0; - - while (patch_offset < patch.size() - 12) { - uint64_t data = decode(patch, patch_offset); - uint64_t command = data & 3; - uint64_t length = (data >> 2) + 1; - - switch (command) { - case 0: // SourceRead - while (length--) { - target[target_offset++] = source[source_offset++]; - } - break; - case 1: // TargetRead - while (length--) { - target[target_offset++] = patch[patch_offset++]; - } - break; - case 2: // SourceCopy - { - int64_t offsetData = decode(patch, patch_offset); - source_rel_offset += (offsetData & 1 ? -1 : +1) * (offsetData >> 1); - while (length--) { - target[target_offset++] = source[source_rel_offset++]; - } - } break; - case 3: // TargetCopy - { - uint64_t offsetData = decode(patch, patch_offset); - target_rel_offset += (offsetData & 1 ? -1 : +1) * (offsetData >> 1); - while (length--) { - target[target_offset++] = target[target_rel_offset++]; - } - } - default: - throw std::runtime_error("Invalid patch command"); - } - } - - uint32_t source_checksum; - uint32_t target_checksum; - uint32_t patch_checksum; - memcpy(&source_checksum, patch.data() + patch.size() - 12, sizeof(uint32_t)); - memcpy(&target_checksum, patch.data() + patch.size() - 8, sizeof(uint32_t)); - memcpy(&patch_checksum, patch.data() + patch.size() - 4, sizeof(uint32_t)); - - if (source_checksum != crc32(source) || target_checksum != crc32(target) || - patch_checksum != - crc32(std::vector(patch.begin(), patch.end() - 4))) { - throw std::runtime_error("Checksum mismatch"); - } -} - } // namespace core } // namespace yaze diff --git a/src/app/core/common.h b/src/app/core/common.h index d1319c8d..9e8f4a0f 100644 --- a/src/app/core/common.h +++ b/src/app/core/common.h @@ -7,8 +7,8 @@ #include #include -#include "absl/strings/str_format.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" namespace yaze { @@ -180,9 +180,9 @@ static void logf(const absl::FormatSpec &format, const Args &...args) { std::time_t now_tt = std::chrono::system_clock::to_time_t(timestamp); std::tm tm = *std::localtime(&now_tt); - message = absl::StrCat("[", tm.tm_hour, ":", tm.tm_min, ":", tm.tm_sec, "] ", - message, "\n"); - + message = absl::StrCat("[", tm.tm_hour, ":", tm.tm_min, ":", tm.tm_sec, "] ", + message, "\n"); + if (ExperimentFlags::get().kLogToConsole) { std::cout << message; } @@ -252,14 +252,6 @@ struct FolderItem { typedef struct FolderItem FolderItem; -void CreateBpsPatch(const std::vector &source, - const std::vector &target, - std::vector &patch); - -void ApplyBpsPatch(const std::vector &source, - const std::vector &patch, - std::vector &target); - } // namespace core } // namespace yaze diff --git a/src/app/emu/emu.cmake b/src/app/emu/emu.cmake index 7938f172..6ea187dc 100644 --- a/src/app/emu/emu.cmake +++ b/src/app/emu/emu.cmake @@ -7,6 +7,7 @@ add_executable( ${YAZE_APP_EDITOR_SRC} ${YAZE_APP_GFX_SRC} ${YAZE_APP_ZELDA3_SRC} + ${YAZE_UTIL_SRC} ${YAZE_GUI_SRC} ${IMGUI_SRC} ) @@ -35,4 +36,8 @@ target_link_libraries( ${CMAKE_DL_LIBS} ImGui ImGuiTestEngine -) \ No newline at end of file +) + +if (APPLE) + target_link_libraries(yaze_emu PUBLIC ${COCOA_LIBRARY}) +endif() \ No newline at end of file diff --git a/src/cli/handlers/patch.cc b/src/cli/handlers/patch.cc index 402bf2da..64b6c6eb 100644 --- a/src/cli/handlers/patch.cc +++ b/src/cli/handlers/patch.cc @@ -2,6 +2,8 @@ #include "asar-dll-bindings/c/asar.h" +#include "util/bps.h" + namespace yaze { namespace cli { @@ -17,7 +19,7 @@ absl::Status ApplyPatch::handle(const std::vector& arg_vec) { // Apply patch std::vector patched; - core::ApplyBpsPatch(source, patch, patched); + util::ApplyBpsPatch(source, patch, patched); // Save patched file std::ofstream patched_rom("patched.sfc", std::ios::binary); @@ -50,7 +52,7 @@ absl::Status CreatePatch::handle(const std::vector& arg_vec) { std::vector target; std::vector patch; // Create patch - core::CreateBpsPatch(source, target, patch); + util::CreateBpsPatch(source, target, patch); // Save patch to file // std::ofstream patchFile("patch.bps", ios::binary); diff --git a/src/cli/tui.cc b/src/cli/tui.cc index a62afca3..70f175fe 100644 --- a/src/cli/tui.cc +++ b/src/cli/tui.cc @@ -5,7 +5,7 @@ #include #include -#include "app/core/common.h" +#include "util/bps.h" namespace yaze { namespace cli { @@ -56,10 +56,10 @@ void ApplyBpsPatchComponent(ftxui::ScreenInteractive &screen) { // Button to apply the patch. auto apply_button = Button("Apply Patch", [&] { - std::vector source; - auto source_contents = core::LoadFile(base_file); - std::copy(source_contents.begin(), source_contents.end(), - std::back_inserter(source)); + std::vector source = app_context.rom.vector(); + // auto source_contents = core::LoadFile(base_file); + // std::copy(source_contents.begin(), source_contents.end(), + // std::back_inserter(source)); std::vector patch; auto patch_contents = core::LoadFile(patch_file); std::copy(patch_contents.begin(), patch_contents.end(), @@ -67,7 +67,7 @@ void ApplyBpsPatchComponent(ftxui::ScreenInteractive &screen) { std::vector patched; try { - core::ApplyBpsPatch(source, patch, patched); + util::ApplyBpsPatch(source, patch, patched); } catch (const std::runtime_error &e) { app_context.error_message = e.what(); SwitchComponents(screen, LayoutID::kError); diff --git a/src/cli/z3ed.cmake b/src/cli/z3ed.cmake index 966c1ee2..94a2c9b6 100644 --- a/src/cli/z3ed.cmake +++ b/src/cli/z3ed.cmake @@ -26,6 +26,7 @@ add_executable( ${YAZE_APP_EMU_SRC} ${YAZE_APP_GFX_SRC} ${YAZE_APP_ZELDA3_SRC} + ${YAZE_UTIL_SRC} ${YAZE_GUI_SRC} ${IMGUI_SRC} ${ASAR_STATIC_SRC} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 909903f0..064e3285 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -17,6 +17,7 @@ add_executable( ${YAZE_APP_GFX_SRC} ${YAZE_APP_ZELDA3_SRC} ${YAZE_APP_EDITOR_SRC} + ${YAZE_UTIL_SRC} ${YAZE_GUI_SRC} ${IMGUI_SRC} ${IMGUI_TEST_ENGINE_SOURCES} diff --git a/src/util/bps.cc b/src/util/bps.cc new file mode 100644 index 00000000..01b7faf2 --- /dev/null +++ b/src/util/bps.cc @@ -0,0 +1,207 @@ + +#include "bps.h" + +#include + +#include +#include + +namespace yaze { +namespace util { + +namespace { + +uint32_t crc32(const std::vector &data) { + uint32_t crc = ::crc32(0L, Z_NULL, 0); + return ::crc32(crc, data.data(), data.size()); +} + +void encode(uint64_t data, std::vector &output) { + while (true) { + uint8_t x = data & 0x7f; + data >>= 7; + if (data == 0) { + output.push_back(0x80 | x); + break; + } + output.push_back(x); + data--; + } +} + +uint64_t decode(const std::vector &input, size_t &offset) { + uint64_t data = 0; + uint64_t shift = 1; + while (true) { + uint8_t x = input[offset++]; + data += (x & 0x7f) * shift; + if (x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; +} + +} // namespace + +void CreateBpsPatch(const std::vector &source, + const std::vector &target, + std::vector &patch) { + patch.clear(); + patch.insert(patch.end(), {'B', 'P', 'S', '1'}); + + encode(source.size(), patch); + encode(target.size(), patch); + encode(0, patch); // No metadata + + size_t source_offset = 0; + size_t target_offset = 0; + int64_t source_rel_offset = 0; + int64_t target_rel_offset = 0; + + while (target_offset < target.size()) { + if (source_offset < source.size() && + source[source_offset] == target[target_offset]) { + size_t length = 0; + while (source_offset + length < source.size() && + target_offset + length < target.size() && + source[source_offset + length] == target[target_offset + length]) { + length++; + } + encode((length - 1) << 2 | 0, patch); // SourceRead + source_offset += length; + target_offset += length; + } else { + size_t length = 0; + while ( + target_offset + length < target.size() && + (source_offset + length >= source.size() || + source[source_offset + length] != target[target_offset + length])) { + length++; + } + if (length > 0) { + encode((length - 1) << 2 | 1, patch); // TargetRead + for (size_t i = 0; i < length; i++) { + patch.push_back(target[target_offset + i]); + } + target_offset += length; + } + } + + // SourceCopy + if (source_offset < source.size()) { + size_t length = 0; + int64_t offset = source_offset - source_rel_offset; + while (source_offset + length < source.size() && + target_offset + length < target.size() && + source[source_offset + length] == target[target_offset + length]) { + length++; + } + if (length > 0) { + encode((length - 1) << 2 | 2, patch); + encode((offset < 0 ? 1 : 0) | (abs(offset) << 1), patch); + source_offset += length; + target_offset += length; + source_rel_offset = source_offset; + } + } + + // TargetCopy + if (target_offset > 0) { + size_t length = 0; + int64_t offset = target_offset - target_rel_offset; + while (target_offset + length < target.size() && + target[target_offset - 1] == target[target_offset + length]) { + length++; + } + if (length > 0) { + encode((length - 1) << 2 | 3, patch); + encode((offset < 0 ? 1 : 0) | (abs(offset) << 1), patch); + target_offset += length; + target_rel_offset = target_offset; + } + } + } + + patch.resize(patch.size() + 12); // Make space for the checksums + uint32_t source_checksum = crc32(source); + uint32_t target_checksum = crc32(target); + uint32_t patch_checksum = crc32(patch); + + memcpy(patch.data() + patch.size() - 12, &source_checksum, sizeof(uint32_t)); + memcpy(patch.data() + patch.size() - 8, &target_checksum, sizeof(uint32_t)); + memcpy(patch.data() + patch.size() - 4, &patch_checksum, sizeof(uint32_t)); +} + +void ApplyBpsPatch(const std::vector &source, + const std::vector &patch, + std::vector &target) { + if (patch.size() < 4 || patch[0] != 'B' || patch[1] != 'P' || + patch[2] != 'S' || patch[3] != '1') { + throw std::runtime_error("Invalid patch format"); + } + + size_t patch_offset = 4; + uint64_t target_size = decode(patch, patch_offset); + uint64_t metadata_size = decode(patch, patch_offset); + patch_offset += metadata_size; + + target.resize(target_size); + size_t source_offset = 0; + size_t target_offset = 0; + int64_t source_rel_offset = 0; + int64_t target_rel_offset = 0; + + while (patch_offset < patch.size() - 12) { + uint64_t data = decode(patch, patch_offset); + uint64_t command = data & 3; + uint64_t length = (data >> 2) + 1; + + switch (command) { + case 0: // SourceRead + while (length--) { + target[target_offset++] = source[source_offset++]; + } + break; + case 1: // TargetRead + while (length--) { + target[target_offset++] = patch[patch_offset++]; + } + break; + case 2: // SourceCopy + { + int64_t offsetData = decode(patch, patch_offset); + source_rel_offset += (offsetData & 1 ? -1 : +1) * (offsetData >> 1); + while (length--) { + target[target_offset++] = source[source_rel_offset++]; + } + } break; + case 3: // TargetCopy + { + uint64_t offsetData = decode(patch, patch_offset); + target_rel_offset += (offsetData & 1 ? -1 : +1) * (offsetData >> 1); + while (length--) { + target[target_offset++] = target[target_rel_offset++]; + } + } + default: + throw std::runtime_error("Invalid patch command"); + } + } + + uint32_t source_checksum; + uint32_t target_checksum; + uint32_t patch_checksum; + memcpy(&source_checksum, patch.data() + patch.size() - 12, sizeof(uint32_t)); + memcpy(&target_checksum, patch.data() + patch.size() - 8, sizeof(uint32_t)); + memcpy(&patch_checksum, patch.data() + patch.size() - 4, sizeof(uint32_t)); + + if (source_checksum != crc32(source) || target_checksum != crc32(target) || + patch_checksum != + crc32(std::vector(patch.begin(), patch.end() - 4))) { + throw std::runtime_error("Checksum mismatch"); + } +} + +} // namespace util +} // namespace yaze \ No newline at end of file diff --git a/src/util/bps.h b/src/util/bps.h new file mode 100644 index 00000000..c72e4d99 --- /dev/null +++ b/src/util/bps.h @@ -0,0 +1,21 @@ +#ifndef YAZE_UTIL_BPS_H +#define YAZE_UTIL_BPS_H + +#include +#include + +namespace yaze { +namespace util { + +void CreateBpsPatch(const std::vector &source, + const std::vector &target, + std::vector &patch); + +void ApplyBpsPatch(const std::vector &source, + const std::vector &patch, + std::vector &target); + +} // namespace util +} // namespace yaze + +#endif // YAZE_UTIL_BPS_H \ No newline at end of file