diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 084ccfc6..b24f0f88 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,3 @@ -# yaze source files ----------------------------------------------------------- set( YAZE_APP_CORE_SRC app/core/common.cc @@ -48,43 +47,6 @@ set( app/gui/color.cc ) -set( - YAZE_VIEWER_SRC - app/viewer/cgx_viewer.cc -) - -# executable creation --------------------------------------------------------- - -add_executable( - yaze - app/yaze.cc - app/rom.cc - ${YAZE_APP_CORE_SRC} - ${YAZE_APP_EDITOR_SRC} - ${YAZE_APP_GFX_SRC} - ${YAZE_APP_ZELDA3_SRC} - ${YAZE_GUI_SRC} - ${YAZE_VIEWER_SRC} - # ${ASAR_STATIC_SRC} - # ${SNES_SPC_SOURCES} - ${IMGUI_SRC} -) - -# including libraries --------------------------------------------------------- - -target_include_directories( - yaze PUBLIC - lib/ - app/ - ${CMAKE_SOURCE_DIR}/src/ - ${PNG_INCLUDE_DIRS} - ${SDL2_INCLUDE_DIR} - lib/SDL_mixer/include/ - ${GLEW_INCLUDE_DIRS} - # lib/asar/src/asar/ - # lib/snes_spc/snes_spc/ -) - set(SDL_TARGETS SDL2::SDL2) if(WIN32 OR MINGW) @@ -92,22 +54,8 @@ if(WIN32 OR MINGW) add_definitions(-DSDL_MAIN_HANDLED) endif() -# linking libraries ----------------------------------------------------------- - -target_link_libraries( - yaze PUBLIC - ${ABSL_TARGETS} - ${SDL_TARGETS} - ${SDLMIXER_LIBRARY} - SDL2_mixer - ${PNG_LIBRARIES} - ${GLEW_LIBRARIES} - ${OPENGL_LIBRARIES} - ${CMAKE_DL_LIBS} - # asar-static - # snes_spc - ImGui -) +include(app/CMakeLists.txt) +include(cli/CMakeLists.txt) if (UNIX) target_compile_definitions(yaze PRIVATE "linux") diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt new file mode 100644 index 00000000..31b6069b --- /dev/null +++ b/src/app/CMakeLists.txt @@ -0,0 +1,40 @@ +add_executable( + yaze + app/yaze.cc + app/rom.cc + ${YAZE_APP_CORE_SRC} + ${YAZE_APP_EDITOR_SRC} + ${YAZE_APP_GFX_SRC} + ${YAZE_APP_ZELDA3_SRC} + ${YAZE_GUI_SRC} + # ${ASAR_STATIC_SRC} + # ${SNES_SPC_SOURCES} + ${IMGUI_SRC} +) + +target_include_directories( + yaze PUBLIC + lib/ + app/ + ${CMAKE_SOURCE_DIR}/src/ + ${PNG_INCLUDE_DIRS} + ${SDL2_INCLUDE_DIR} + ${GLEW_INCLUDE_DIRS} + lib/SDL_mixer/include/ + # lib/asar/src/asar/ + # lib/snes_spc/snes_spc/ +) + +target_link_libraries( + yaze PUBLIC + ${ABSL_TARGETS} + ${SDL_TARGETS} + ${PNG_LIBRARIES} + ${GLEW_LIBRARIES} + ${OPENGL_LIBRARIES} + ${CMAKE_DL_LIBS} + SDL2_mixer + ImGui + # asar-static + # snes_spc +) \ No newline at end of file diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt new file mode 100644 index 00000000..43d61fcb --- /dev/null +++ b/src/cli/CMakeLists.txt @@ -0,0 +1,30 @@ +add_executable( + z3ed + cli/z3ed.cc + cli/patch.cc + cli/command_handler.cc + app/rom.cc + app/core/common.cc + ${YAZE_APP_GFX_SRC} +) + +target_include_directories( + z3ed PUBLIC + lib/ + app/ + ${CMAKE_SOURCE_DIR}/src/ + ${PNG_INCLUDE_DIRS} + ${SDL2_INCLUDE_DIR} + ${GLEW_INCLUDE_DIRS} +) + +target_link_libraries( + z3ed PUBLIC + ${ABSL_TARGETS} + ${SDL_TARGETS} + ${PNG_LIBRARIES} + ${GLEW_LIBRARIES} + ${OPENGL_LIBRARIES} + ${CMAKE_DL_LIBS} + ImGui +) \ No newline at end of file diff --git a/src/cli/command_handler.cc b/src/cli/command_handler.cc new file mode 100644 index 00000000..1ff489fd --- /dev/null +++ b/src/cli/command_handler.cc @@ -0,0 +1 @@ +#include "cli/command_handler.h" \ No newline at end of file diff --git a/src/cli/command_handler.h b/src/cli/command_handler.h new file mode 100644 index 00000000..932fbfd7 --- /dev/null +++ b/src/cli/command_handler.h @@ -0,0 +1,203 @@ +#ifndef YAZE_CLI_COMMAND_HANDLER_H +#define YAZE_CLI_COMMAND_HANDLER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "app/core/common.h" +#include "app/core/constants.h" +#include "app/rom.h" +#include "cli/patch.h" + +namespace yaze { +namespace cli { + +namespace Color { +enum Code { + FG_RED = 31, + FG_GREEN = 32, + FG_YELLOW = 33, + FG_BLUE = 36, + FG_MAGENTA = 35, + FG_DEFAULT = 39, + FG_RESET = 0, + FG_UNDERLINE = 4, + BG_RED = 41, + BG_GREEN = 42, + BG_BLUE = 44, + BG_DEFAULT = 49 +}; +class Modifier { + Code code; + + public: + explicit Modifier(Code pCode) : code(pCode) {} + friend std::ostream& operator<<(std::ostream& os, const Modifier& mod) { + return os << "\033[" << mod.code << "m"; + } +}; +} // namespace Color + +namespace { +std::vector ParseArguments(const std::string_view args) { + std::vector arguments; + std::stringstream ss(args.data()); + std::string arg; + while (ss >> arg) { + arguments.push_back(arg); + } + return arguments; +} +} // namespace + +class CommandHandler { + public: + CommandHandler() = default; + virtual ~CommandHandler() = default; + virtual absl::Status handle(std::string_view arg) = 0; + + app::ROM rom_; +}; + +class ApplyPatch : public CommandHandler { + public: + absl::Status handle(std::string_view arg) override { + auto arg_vec = ParseArguments(arg); + std::string rom_filename = arg_vec[1]; + std::string patch_filename = arg_vec[2]; + RETURN_IF_ERROR(rom_.LoadFromFile(rom_filename)) + auto source = rom_.vector(); + std::ifstream patch_file(patch_filename, std::ios::binary); + std::vector patch; + patch_file.read(reinterpret_cast(patch.data()), patch.size()); + + // Apply patch + std::vector patched; + ApplyBpsPatch(source, patch, patched); + + // Save patched file + std::ofstream patched_rom("patched.sfc", std::ios::binary); + patched_rom.write(reinterpret_cast(patched.data()), + patched.size()); + patched_rom.close(); + return absl::OkStatus(); + } +}; + +class CreatePatch : public CommandHandler { + public: + absl::Status handle(std::string_view arg) override { + std::vector source; + std::vector target; + std::vector patch; + // Create patch + CreateBpsPatch(source, target, patch); + + // Save patch to file + // std::ofstream patchFile("patch.bps", ios::binary); + // patchFile.write(reinterpret_cast(patch.data()), + // patch.size()); patchFile.close(); + return absl::OkStatus(); + } +}; + +class Open : public CommandHandler { + public: + absl::Status handle(std::string_view arg) override { + Color::Modifier green(Color::FG_GREEN); + Color::Modifier blue(Color::FG_BLUE); + Color::Modifier reset(Color::FG_RESET); + RETURN_IF_ERROR(rom_.LoadFromFile(arg)) + std::cout << "Title: " << green << rom_.title() << std::endl; + std::cout << reset << "Size: " << blue << "0x" << std::hex << rom_.size() + << reset << std::endl; + return absl::OkStatus(); + } +}; + +class Backup : public CommandHandler { + public: + absl::Status handle(std::string_view arg) override { + auto args_vec = ParseArguments(arg); + RETURN_IF_ERROR(rom_.LoadFromFile(args_vec[0])) + if (args_vec.size() == 2) { + // Optional filename added + RETURN_IF_ERROR(rom_.SaveToFile(/*backup=*/true, args_vec[1])) + } else { + RETURN_IF_ERROR(rom_.SaveToFile(/*backup=*/true)) + } + return absl::OkStatus(); + } +}; + +// Compress Graphics +class Compress : public CommandHandler { + public: + absl::Status handle(std::string_view arg) override { + std::cout << "Compress selected with argument: " << arg << std::endl; + return absl::OkStatus(); + } +}; + +// Decompress Graphics +class Decompress : public CommandHandler { + public: + absl::Status handle(std::string_view arg) override { + RETURN_IF_ERROR(rom_.LoadFromFile(arg, true)) + RETURN_IF_ERROR(rom_.LoadAllGraphicsData()) + std::cout << "Decompress selected with argument: " << arg << std::endl; + return absl::OkStatus(); + } +}; + +// SnesToPc Conversion +class SnesToPc : public CommandHandler { + public: + absl::Status handle(std::string_view arg) override { + std::stringstream ss(arg.data()); + uint32_t snes_address; + ss >> std::hex >> snes_address; + uint32_t pc_address = app::core::SnesToPc(snes_address); + std::cout << std::hex << pc_address << std::endl; + return absl::OkStatus(); + } +}; + +class PcToSnes : public CommandHandler { + public: + absl::Status handle(std::string_view arg) override { + std::stringstream ss(arg.data()); + uint32_t pc_address; + ss >> std::hex >> pc_address; + uint32_t snes_address = app::core::PcToSnes(pc_address); + Color::Modifier blue(Color::FG_BLUE); + std::cout << "SNES LoROM Address: "; + std::cout << blue << "$" << std::uppercase << std::hex << snes_address + << "\n"; + return absl::OkStatus(); + } +}; + +struct Commands { + std::unordered_map> handlers = { + {"-a", std::make_shared()}, + {"-c", std::make_shared()}, + {"-o", std::make_shared()}, + {"-b", std::make_shared()}, + {"-i", std::make_shared()}, // Import + {"-e", std::make_shared()}, // Export + {"-s", std::make_shared()}, + {"-p", std::make_shared()}}; +}; + +} // namespace cli +} // namespace yaze + +#endif \ No newline at end of file diff --git a/src/cli/patch.cc b/src/cli/patch.cc new file mode 100644 index 00000000..1b6945c8 --- /dev/null +++ b/src/cli/patch.cc @@ -0,0 +1,205 @@ +#include "cli/patch.h" + +#include + +#include +#include +#include +#include +#include + +namespace yaze { +namespace cli { + +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()); +} + +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 sourceOffset = 0; + size_t targetOffset = 0; + int64_t sourceRelOffset = 0; + int64_t targetRelOffset = 0; + + while (targetOffset < target.size()) { + if (sourceOffset < source.size() && + source[sourceOffset] == target[targetOffset]) { + size_t length = 0; + while (sourceOffset + length < source.size() && + targetOffset + length < target.size() && + source[sourceOffset + length] == target[targetOffset + length]) { + length++; + } + encode((length - 1) << 2 | 0, patch); // SourceRead + sourceOffset += length; + targetOffset += length; + } else { + size_t length = 0; + while (targetOffset + length < target.size() && + (sourceOffset + length >= source.size() || + source[sourceOffset + length] != target[targetOffset + length])) { + length++; + } + if (length > 0) { + encode((length - 1) << 2 | 1, patch); // TargetRead + for (size_t i = 0; i < length; i++) { + patch.push_back(target[targetOffset + i]); + } + targetOffset += length; + } + } + + // SourceCopy + if (sourceOffset < source.size()) { + size_t length = 0; + int64_t offset = sourceOffset - sourceRelOffset; + while (sourceOffset + length < source.size() && + targetOffset + length < target.size() && + source[sourceOffset + length] == target[targetOffset + length]) { + length++; + } + if (length > 0) { + encode((length - 1) << 2 | 2, patch); + encode((offset < 0 ? 1 : 0) | (abs(offset) << 1), patch); + sourceOffset += length; + targetOffset += length; + sourceRelOffset = sourceOffset; + } + } + + // TargetCopy + if (targetOffset > 0) { + size_t length = 0; + int64_t offset = targetOffset - targetRelOffset; + while (targetOffset + length < target.size() && + target[targetOffset - 1] == target[targetOffset + length]) { + length++; + } + if (length > 0) { + encode((length - 1) << 2 | 3, patch); + encode((offset < 0 ? 1 : 0) | (abs(offset) << 1), patch); + targetOffset += length; + targetRelOffset = targetOffset; + } + } + } + + patch.resize(patch.size() + 12); // Make space for the checksums + uint32_t sourceChecksum = crc32(source); + uint32_t targetChecksum = crc32(target); + uint32_t patchChecksum = crc32(patch); + + memcpy(patch.data() + patch.size() - 12, &sourceChecksum, sizeof(uint32_t)); + memcpy(patch.data() + patch.size() - 8, &targetChecksum, sizeof(uint32_t)); + memcpy(patch.data() + patch.size() - 4, &patchChecksum, 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 patchOffset = 4; + uint64_t sourceSize = decode(patch, patchOffset); + uint64_t targetSize = decode(patch, patchOffset); + uint64_t metadataSize = decode(patch, patchOffset); + patchOffset += metadataSize; + + target.resize(targetSize); + size_t sourceOffset = 0; + size_t targetOffset = 0; + int64_t sourceRelOffset = 0; + int64_t targetRelOffset = 0; + + while (patchOffset < patch.size() - 12) { + uint64_t data = decode(patch, patchOffset); + uint64_t command = data & 3; + uint64_t length = (data >> 2) + 1; + + switch (command) { + case 0: // SourceRead + while (length--) { + target[targetOffset++] = source[sourceOffset++]; + } + break; + case 1: // TargetRead + while (length--) { + target[targetOffset++] = patch[patchOffset++]; + } + break; + case 2: // SourceCopy + { + int64_t offsetData = decode(patch, patchOffset); + sourceRelOffset += (offsetData & 1 ? -1 : +1) * (offsetData >> 1); + while (length--) { + target[targetOffset++] = source[sourceRelOffset++]; + } + } break; + case 3: // TargetCopy + { + uint64_t offsetData = decode(patch, patchOffset); + targetRelOffset += (offsetData & 1 ? -1 : +1) * (offsetData >> 1); + while (length--) { + target[targetOffset++] = target[targetRelOffset++]; + } + } + default: + throw std::runtime_error("Invalid patch command"); + } + } + + uint32_t sourceChecksum; + uint32_t targetChecksum; + uint32_t patchChecksum; + memcpy(&sourceChecksum, patch.data() + patch.size() - 12, sizeof(uint32_t)); + memcpy(&targetChecksum, patch.data() + patch.size() - 8, sizeof(uint32_t)); + memcpy(&patchChecksum, patch.data() + patch.size() - 4, sizeof(uint32_t)); + + if (sourceChecksum != crc32(source) || targetChecksum != crc32(target) || + patchChecksum != + crc32(std::vector(patch.begin(), patch.end() - 4))) { + throw std::runtime_error("Checksum mismatch"); + } +} + +} // namespace cli +} // namespace yaze \ No newline at end of file diff --git a/src/cli/patch.h b/src/cli/patch.h new file mode 100644 index 00000000..19892605 --- /dev/null +++ b/src/cli/patch.h @@ -0,0 +1,29 @@ +#ifndef YAZE_CLI_PATCH_H +#define YAZE_CLI_PATCH_H + +#include + +#include +#include +#include +#include +#include +namespace yaze { +namespace cli { +void encode(uint64_t data, std::vector& output); + +uint64_t decode(const std::vector& input, size_t& offset); + +uint32_t crc32(const std::vector& data); + +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 cli +} // namespace yaze +#endif \ No newline at end of file diff --git a/src/cli/z3ed.cc b/src/cli/z3ed.cc new file mode 100644 index 00000000..2b9cbb8c --- /dev/null +++ b/src/cli/z3ed.cc @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "app/core/common.h" +#include "app/core/constants.h" +#include "app/rom.h" +#include "cli/command_handler.h" +#include "cli/patch.h" + +namespace yaze { +namespace cli { +namespace { + +void HelpCommand() { + Color::Modifier ylw(Color::FG_YELLOW); + Color::Modifier mag(Color::FG_MAGENTA); + Color::Modifier red(Color::FG_RED); + Color::Modifier reset(Color::FG_RESET); + Color::Modifier underline(Color::FG_UNDERLINE); + std::cout << "\n"; + std::cout << ylw << " ▲ " << reset << " z3ed\n"; + std::cout << ylw << "▲ ▲ " << reset << " by " << mag << "scawful\n\n" + << reset; + std::cout << "The Legend of " << red << "Zelda" << reset + << ": A Link to the Past Hacking Tool\n\n"; + std::cout << underline; + std::cout << "Command" << reset << " " << underline << "Arg" + << reset << " " << underline << "Params\n" + << reset; + + std::cout << "Apply BPS Patch -a \n"; + std::cout << "Create BPS Patch -c " + "\n\n"; + + std::cout << "Open ROM -o \n"; + std::cout << "Backup ROM -b \n"; + std::cout << "Expand ROM -x \n\n"; + + std::cout << "Export Graphics -e \n"; + std::cout << "Import Graphics -i \n\n"; + + std::cout << "SNES to PC Address -s
\n"; + std::cout << "PC to SNES Address -p
\n"; + std::cout << "\n"; +} + +int RunCommandHandler(int argc, char* argv[]) { + if (argv[1] == "-h" || argc == 1) { + HelpCommand(); + return EXIT_SUCCESS; + } + + Commands commands; + std::string mode = argv[1]; + std::string arg = argv[2]; + if (commands.handlers.find(mode) != commands.handlers.end()) { + PRINT_IF_ERROR(commands.handlers[mode]->handle(arg)) + } else { + std::cerr << "Invalid mode specified: " << mode << std::endl; + } + return EXIT_SUCCESS; +} + +} // namespace +} // namespace cli +} // namespace yaze + +int main(int argc, char* argv[]) { + return yaze::cli::RunCommandHandler(argc, argv); +} \ No newline at end of file