add z3ed cli tool

rom backups
export and import graphics
pc to snes and snes to pc addr conversion
This commit is contained in:
scawful
2023-08-18 11:42:46 -04:00
parent 7e87b1ed45
commit baf7547fff
8 changed files with 588 additions and 54 deletions

View File

@@ -1,4 +1,3 @@
# yaze source files -----------------------------------------------------------
set( set(
YAZE_APP_CORE_SRC YAZE_APP_CORE_SRC
app/core/common.cc app/core/common.cc
@@ -48,43 +47,6 @@ set(
app/gui/color.cc 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) set(SDL_TARGETS SDL2::SDL2)
if(WIN32 OR MINGW) if(WIN32 OR MINGW)
@@ -92,22 +54,8 @@ if(WIN32 OR MINGW)
add_definitions(-DSDL_MAIN_HANDLED) add_definitions(-DSDL_MAIN_HANDLED)
endif() endif()
# linking libraries ----------------------------------------------------------- include(app/CMakeLists.txt)
include(cli/CMakeLists.txt)
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
)
if (UNIX) if (UNIX)
target_compile_definitions(yaze PRIVATE "linux") target_compile_definitions(yaze PRIVATE "linux")

40
src/app/CMakeLists.txt Normal file
View File

@@ -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
)

30
src/cli/CMakeLists.txt Normal file
View File

@@ -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
)

View File

@@ -0,0 +1 @@
#include "cli/command_handler.h"

203
src/cli/command_handler.h Normal file
View File

@@ -0,0 +1,203 @@
#ifndef YAZE_CLI_COMMAND_HANDLER_H
#define YAZE_CLI_COMMAND_HANDLER_H
#include <cstdint>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#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<std::string> ParseArguments(const std::string_view args) {
std::vector<std::string> 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<uint8_t> patch;
patch_file.read(reinterpret_cast<char*>(patch.data()), patch.size());
// Apply patch
std::vector<uint8_t> patched;
ApplyBpsPatch(source, patch, patched);
// Save patched file
std::ofstream patched_rom("patched.sfc", std::ios::binary);
patched_rom.write(reinterpret_cast<const char*>(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<uint8_t> source;
std::vector<uint8_t> target;
std::vector<uint8_t> patch;
// Create patch
CreateBpsPatch(source, target, patch);
// Save patch to file
// std::ofstream patchFile("patch.bps", ios::binary);
// patchFile.write(reinterpret_cast<const char*>(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<std::string, std::shared_ptr<CommandHandler>> handlers = {
{"-a", std::make_shared<ApplyPatch>()},
{"-c", std::make_shared<CreatePatch>()},
{"-o", std::make_shared<Open>()},
{"-b", std::make_shared<Backup>()},
{"-i", std::make_shared<Compress>()}, // Import
{"-e", std::make_shared<Decompress>()}, // Export
{"-s", std::make_shared<SnesToPc>()},
{"-p", std::make_shared<PcToSnes>()}};
};
} // namespace cli
} // namespace yaze
#endif

205
src/cli/patch.cc Normal file
View File

@@ -0,0 +1,205 @@
#include "cli/patch.h"
#include <zlib.h>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>
namespace yaze {
namespace cli {
void encode(uint64_t data, std::vector<uint8_t>& 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<uint8_t>& 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<uint8_t>& data) {
uint32_t crc = ::crc32(0L, Z_NULL, 0);
return ::crc32(crc, data.data(), data.size());
}
void CreateBpsPatch(const std::vector<uint8_t>& source,
const std::vector<uint8_t>& target,
std::vector<uint8_t>& 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<uint8_t>& source,
const std::vector<uint8_t>& patch,
std::vector<uint8_t>& 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<uint8_t>(patch.begin(), patch.end() - 4))) {
throw std::runtime_error("Checksum mismatch");
}
}
} // namespace cli
} // namespace yaze

29
src/cli/patch.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef YAZE_CLI_PATCH_H
#define YAZE_CLI_PATCH_H
#include <zlib.h>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>
namespace yaze {
namespace cli {
void encode(uint64_t data, std::vector<uint8_t>& output);
uint64_t decode(const std::vector<uint8_t>& input, size_t& offset);
uint32_t crc32(const std::vector<uint8_t>& data);
void CreateBpsPatch(const std::vector<uint8_t>& source,
const std::vector<uint8_t>& target,
std::vector<uint8_t>& patch);
void ApplyBpsPatch(const std::vector<uint8_t>& source,
const std::vector<uint8_t>& patch,
std::vector<uint8_t>& target);
} // namespace cli
} // namespace yaze
#endif

78
src/cli/z3ed.cc Normal file
View File

@@ -0,0 +1,78 @@
#include <cstdint>
#include <cstring>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#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 <rom_file> <bps_file>\n";
std::cout << "Create BPS Patch -c <bps_file> <src_file> "
"<modified_file>\n\n";
std::cout << "Open ROM -o <rom_file>\n";
std::cout << "Backup ROM -b <rom_file> <optional:new_file>\n";
std::cout << "Expand ROM -x <rom_file> <file_size>\n\n";
std::cout << "Export Graphics -e <rom_file> <bin_file>\n";
std::cout << "Import Graphics -i <bin_file> <rom_file>\n\n";
std::cout << "SNES to PC Address -s <address>\n";
std::cout << "PC to SNES Address -p <address>\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);
}