backend-infra-engineer: Pre-0.2.2 snapshot (2023)
This commit is contained in:
37
src/cli/CMakeLists.txt
Normal file
37
src/cli/CMakeLists.txt
Normal file
@@ -0,0 +1,37 @@
|
||||
add_executable(
|
||||
z3ed
|
||||
cli/z3ed.cc
|
||||
cli/patch.cc
|
||||
cli/command_handler.cc
|
||||
app/rom.cc
|
||||
app/core/common.cc
|
||||
app/gui/pipeline.cc
|
||||
${YAZE_APP_EMU_SRC}
|
||||
${YAZE_APP_GFX_SRC}
|
||||
${YAZE_APP_ZELDA3_SRC}
|
||||
${YAZE_GUI_SRC}
|
||||
${IMGUI_SRC}
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
z3ed PUBLIC
|
||||
lib/
|
||||
app/
|
||||
lib/SDL_mixer/include/
|
||||
${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}
|
||||
SDL2_mixer
|
||||
ImGui
|
||||
)
|
||||
86
src/cli/command_handler.cc
Normal file
86
src/cli/command_handler.cc
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "cli/command_handler.h"
|
||||
|
||||
#include <string> // for basic_string, char_traits, stoi
|
||||
#include <vector> // for vector, vector<>::value_type
|
||||
|
||||
#include "absl/status/status.h" // for OkStatus, Status
|
||||
#include "app/core/common.h" // for app
|
||||
#include "app/core/constants.h" // for RETURN_IF_ERROR
|
||||
#include "app/rom.h" // for ROM
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
using namespace app;
|
||||
|
||||
absl::Status Tile16Transfer::handle(const std::vector<std::string>& arg_vec) {
|
||||
// Load the source rom
|
||||
RETURN_IF_ERROR(rom_.LoadFromFile(arg_vec[0]))
|
||||
|
||||
// Load the destination rom
|
||||
ROM dest_rom;
|
||||
RETURN_IF_ERROR(dest_rom.LoadFromFile(arg_vec[1]))
|
||||
|
||||
std::vector<uint32_t> tileIDs;
|
||||
|
||||
// Parse the CSV list of tile16 IDs.
|
||||
std::stringstream ss(arg_vec[2].data());
|
||||
for (std::string tileID; std::getline(ss, tileID, ',');) {
|
||||
if (tileID == "*") {
|
||||
// for (uint32_t i = 0; i <= rom_.GetMaxTileID(); ++i) {
|
||||
// tileIDs.push_back(i);
|
||||
// }
|
||||
break; // No need to continue parsing if * is used
|
||||
} else if (tileID.find('-') != std::string::npos) {
|
||||
// Handle range: split by hyphen and add all tile IDs in the range.
|
||||
std::stringstream rangeSS(tileID);
|
||||
std::string start;
|
||||
std::string end;
|
||||
std::getline(rangeSS, start, '-');
|
||||
std::getline(rangeSS, end);
|
||||
uint32_t startID = std::stoi(start, nullptr, 16);
|
||||
uint32_t endID = std::stoi(end, nullptr, 16);
|
||||
for (uint32_t i = startID; i <= endID; ++i) {
|
||||
tileIDs.push_back(i);
|
||||
}
|
||||
} else {
|
||||
// Handle single tile ID
|
||||
uint32_t tileID_int = std::stoi(tileID, nullptr, 16);
|
||||
tileIDs.push_back(tileID_int);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& tile16_id_int : tileIDs) {
|
||||
// Compare the tile16 data between source and destination ROMs.
|
||||
// auto source_tile16_data = rom_.ReadTile16(tile16_id_int);
|
||||
// auto dest_tile16_data = dest_rom.ReadTile16(tile16_id_int);
|
||||
ASSIGN_OR_RETURN(auto source_tile16_data, rom_.ReadTile16(tile16_id_int))
|
||||
ASSIGN_OR_RETURN(auto dest_tile16_data, dest_rom.ReadTile16(tile16_id_int))
|
||||
if (source_tile16_data != dest_tile16_data) {
|
||||
// Notify user of difference
|
||||
std::cout << "Difference detected in tile16 ID " << tile16_id_int
|
||||
<< ". Do you want to transfer it to dest rom? (y/n): ";
|
||||
char userChoice;
|
||||
std::cin >> userChoice;
|
||||
|
||||
// Transfer if user confirms
|
||||
if (userChoice == 'y' || userChoice == 'Y') {
|
||||
dest_rom.WriteTile16(tile16_id_int, source_tile16_data);
|
||||
std::cout << "Transferred tile16 ID " << tile16_id_int
|
||||
<< " to dest rom." << std::endl;
|
||||
} else {
|
||||
std::cout << "Skipped transferring tile16 ID " << tile16_id_int << "."
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_IF_ERROR(dest_rom.SaveToFile(/*backup=*/true, arg_vec[1]))
|
||||
|
||||
std::cout << "Successfully transferred tile16" << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
344
src/cli/command_handler.h
Normal file
344
src/cli/command_handler.h
Normal file
@@ -0,0 +1,344 @@
|
||||
#ifndef YAZE_CLI_COMMAND_HANDLER_H
|
||||
#define YAZE_CLI_COMMAND_HANDLER_H
|
||||
|
||||
#include <cstdint> // for uint8_t, uint32_t
|
||||
#include <iostream> // for operator<<, string, ostream, basic_...
|
||||
#include <memory> // for make_shared, shared_ptr
|
||||
#include <sstream>
|
||||
#include <string> // for char_traits, basic_string, hash
|
||||
#include <string_view>
|
||||
#include <unordered_map> // for unordered_map
|
||||
#include <vector> // for vector, vector<>::value_type
|
||||
|
||||
#include "absl/status/status.h" // for OkStatus, Status
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "app/core/common.h" // for PcToSnes, SnesToPc
|
||||
#include "app/core/constants.h" // for RETURN_IF_ERROR
|
||||
#include "app/emu/snes.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/compression.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/rom.h" // for ROM
|
||||
#include "app/zelda3/overworld.h"
|
||||
#include "cli/patch.h" // for ApplyBpsPatch, CreateBpsPatch
|
||||
|
||||
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
|
||||
|
||||
class CommandHandler {
|
||||
public:
|
||||
CommandHandler() = default;
|
||||
virtual ~CommandHandler() = default;
|
||||
virtual absl::Status handle(const std::vector<std::string>& arg_vec) = 0;
|
||||
|
||||
app::ROM rom_;
|
||||
};
|
||||
|
||||
class ApplyPatch : public CommandHandler {
|
||||
public:
|
||||
absl::Status handle(const std::vector<std::string>& arg_vec) override {
|
||||
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.resize(rom_.size());
|
||||
patch_file.read((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((char*)patched.data(), patched.size());
|
||||
patched_rom.close();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
class CreatePatch : public CommandHandler {
|
||||
public:
|
||||
absl::Status handle(const std::vector<std::string>& arg_vec) 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(const std::vector<std::string>& arg_vec) override {
|
||||
Color::Modifier green(Color::FG_GREEN);
|
||||
Color::Modifier blue(Color::FG_BLUE);
|
||||
Color::Modifier reset(Color::FG_RESET);
|
||||
auto const& arg = arg_vec[0];
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
// Backup ROM
|
||||
class Backup : public CommandHandler {
|
||||
public:
|
||||
absl::Status handle(const std::vector<std::string>& arg_vec) override {
|
||||
RETURN_IF_ERROR(rom_.LoadFromFile(arg_vec[0]))
|
||||
if (arg_vec.size() == 2) {
|
||||
// Optional filename added
|
||||
RETURN_IF_ERROR(rom_.SaveToFile(/*backup=*/true, arg_vec[1]))
|
||||
} else {
|
||||
RETURN_IF_ERROR(rom_.SaveToFile(/*backup=*/true))
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
// Compress Graphics
|
||||
class Compress : public CommandHandler {
|
||||
public:
|
||||
absl::Status handle(const std::vector<std::string>& arg_vec) override {
|
||||
std::cout << "Compress selected with argument: " << arg_vec[0] << std::endl;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
// Decompress (Export) Graphics
|
||||
//
|
||||
// -e <rom_file> <bin_file> --mode=<optional:mode>
|
||||
//
|
||||
// mode:
|
||||
class Decompress : public CommandHandler {
|
||||
public:
|
||||
absl::Status handle(const std::vector<std::string>& arg_vec) override {
|
||||
Color::Modifier underline(Color::FG_UNDERLINE);
|
||||
Color::Modifier reset(Color::FG_RESET);
|
||||
std::cout << "Please specify the tilesheets you want to export\n";
|
||||
std::cout << "You can input an individual sheet, a range X-Y, or comma "
|
||||
"separate values.\n\n";
|
||||
std::cout << underline << "Tilesheets\n" << reset;
|
||||
std::cout << "0-112 -> compressed 3bpp bgr \n";
|
||||
std::cout << "113-114 -> compressed 2bpp\n";
|
||||
std::cout << "115-126 -> uncompressed 3bpp sprites\n";
|
||||
std::cout << "127-217 -> compressed 3bpp sprites\n";
|
||||
std::cout << "218-222 -> compressed 2bpp\n";
|
||||
|
||||
std::cout << "Enter tilesheets: ";
|
||||
std::string sheet_input;
|
||||
std::cin >> sheet_input;
|
||||
|
||||
// Batch Mode
|
||||
// if (arg_vec.size() == 1) {
|
||||
// auto rom_filename = arg_vec[1];
|
||||
// RETURN_IF_ERROR(rom_.LoadFromFile(arg, true))
|
||||
// RETURN_IF_ERROR(rom_.LoadAllGraphicsData())
|
||||
// for (auto& graphic_sheet : rom_.graphics_bin()) {
|
||||
// const auto filename =
|
||||
// absl::StrCat(rom_.filename(), graphic_sheet.first);
|
||||
// graphic_sheet.second.SaveSurfaceToFile(filename);
|
||||
// }
|
||||
// }
|
||||
|
||||
std::cout << "Decompress selected with argument: " << arg_vec[0]
|
||||
<< std::endl;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
// SnesToPc Conversion
|
||||
// -s <address>
|
||||
class SnesToPc : public CommandHandler {
|
||||
public:
|
||||
absl::Status handle(const std::vector<std::string>& arg_vec) override {
|
||||
auto arg = arg_vec[0];
|
||||
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(const std::vector<std::string>& arg_vec) override {
|
||||
auto arg = arg_vec[0];
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
// -r <rom_file> <address> <optional:length, default: 0x01>
|
||||
class ReadFromRom : public CommandHandler {
|
||||
public:
|
||||
absl::Status handle(const std::vector<std::string>& arg_vec) override {
|
||||
RETURN_IF_ERROR(rom_.LoadFromFile(arg_vec[0]))
|
||||
|
||||
std::stringstream ss(arg_vec[1].data());
|
||||
uint32_t offset;
|
||||
ss >> std::hex >> offset;
|
||||
uint32_t length = 0x01;
|
||||
if (!arg_vec[2].empty()) {
|
||||
length = std::stoi(arg_vec[2]);
|
||||
}
|
||||
|
||||
if (length > 1) {
|
||||
auto returned_bytes_status = rom_.ReadByteVector(offset, length);
|
||||
if (!returned_bytes_status.ok()) {
|
||||
return returned_bytes_status.status();
|
||||
}
|
||||
auto returned_bytes = returned_bytes_status.value();
|
||||
for (const auto& each : returned_bytes) {
|
||||
std::cout << each;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
} else {
|
||||
auto byte = rom_.ReadByte(offset);
|
||||
std::cout << std::hex << byte.value() << std::endl;
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
// Transfer tile 16 data from one rom to another
|
||||
// -t <src_rom> <dest_rom> "<tile32_id_list:csv>"
|
||||
class Tile16Transfer : public CommandHandler {
|
||||
public:
|
||||
absl::Status handle(const std::vector<std::string>& arg_vec) override;
|
||||
};
|
||||
|
||||
class Expand : public CommandHandler {
|
||||
public:
|
||||
absl::Status handle(const std::vector<std::string>& arg_vec) override {
|
||||
RETURN_IF_ERROR(rom_.LoadFromFile(arg_vec[0]))
|
||||
|
||||
std::stringstream ss(arg_vec[1].data());
|
||||
uint32_t size;
|
||||
ss >> std::hex >> size;
|
||||
|
||||
rom_.Expand(size);
|
||||
|
||||
std::cout << "Successfully expanded ROM to " << std::hex << size
|
||||
<< std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
// Start Emulator on a SNES rom file
|
||||
// -emu <rom_file> <optional:num_cpu_cycles>
|
||||
class Emulator : public CommandHandler {
|
||||
public:
|
||||
absl::Status handle(const std::vector<std::string>& arg_vec) override {
|
||||
std::string filename = arg_vec[0];
|
||||
RETURN_IF_ERROR(rom_.LoadFromFile(filename))
|
||||
|
||||
bool step = false;
|
||||
if (arg_vec[1].empty()) {
|
||||
snes.SetCpuMode(0);
|
||||
} else {
|
||||
snes.SetCpuMode(1);
|
||||
step = true;
|
||||
}
|
||||
|
||||
snes.SetupMemory(rom_);
|
||||
snes.Init(rom_);
|
||||
|
||||
if (!step) {
|
||||
int i = 0;
|
||||
while (i < 80000) {
|
||||
snes.Run();
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
// This loop should take in input from the keyboard, such as pressing
|
||||
// space to step through the loop and pressing x to end the execution.
|
||||
bool stepping = true;
|
||||
std::cout << "Press space to step, x to exit" << std::endl;
|
||||
while (stepping) {
|
||||
char input;
|
||||
std::cin.get(input);
|
||||
if (input == 'x') {
|
||||
break;
|
||||
} else {
|
||||
snes.StepRun();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
app::emu::SNES snes;
|
||||
};
|
||||
|
||||
struct Commands {
|
||||
std::unordered_map<std::string, std::shared_ptr<CommandHandler>> handlers = {
|
||||
{"-emu", std::make_shared<Emulator>()},
|
||||
{"-a", std::make_shared<ApplyPatch>()},
|
||||
{"-c", std::make_shared<CreatePatch>()},
|
||||
{"-o", std::make_shared<Open>()},
|
||||
{"-b", std::make_shared<Backup>()},
|
||||
{"-x", std::make_shared<Expand>()},
|
||||
{"-i", std::make_shared<Compress>()}, // Import
|
||||
{"-e", std::make_shared<Decompress>()}, // Export
|
||||
{"-s", std::make_shared<SnesToPc>()},
|
||||
{"-p", std::make_shared<PcToSnes>()},
|
||||
{"-t", std::make_shared<Tile16Transfer>()},
|
||||
{"-r", std::make_shared<ReadFromRom>()} // Read from ROM
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
205
src/cli/patch.cc
Normal file
205
src/cli/patch.cc
Normal 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
29
src/cli/patch.h
Normal 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
|
||||
86
src/cli/z3ed.cc
Normal file
86
src/cli/z3ed.cc
Normal file
@@ -0,0 +1,86 @@
|
||||
#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 << "Transfer Tile16 -t <src_rom> <dest_rom> "
|
||||
"<tile32_id_list:csv>\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;
|
||||
}
|
||||
|
||||
std::vector<std::string> arguments;
|
||||
for (int i = 2; i < argc; i++) { // Skip the arg mode (argv[1])
|
||||
std::cout << "argv[" << i << "] = " << argv[i] << std::endl;
|
||||
arguments.emplace_back(argv[i]);
|
||||
}
|
||||
|
||||
Commands commands;
|
||||
std::string mode = argv[1];
|
||||
if (commands.handlers.find(mode) != commands.handlers.end()) {
|
||||
PRINT_IF_ERROR(commands.handlers[mode]->handle(arguments))
|
||||
} 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);
|
||||
}
|
||||
Reference in New Issue
Block a user