8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_join.h"
15namespace fs = std::filesystem;
26 if (!fs::exists(patches_dir)) {
27 return absl::NotFoundError(
28 absl::StrCat(
"Patches directory not found: ", patches_dir));
32 std::vector<std::string> default_folders = {
33 "Misc",
"Hex Edits",
"Sprites",
"Items",
"Npcs"};
35 for (
const auto& folder : default_folders) {
36 fs::path folder_path = fs::path(patches_dir) / folder;
37 if (!fs::exists(folder_path)) {
39 fs::create_directories(folder_path, ec);
44 for (
const auto& entry : fs::directory_iterator(patches_dir)) {
45 if (entry.is_directory()) {
46 std::string folder_name = entry.path().filename().string();
49 if (folder_name ==
"UNPATCHED") {
62 return absl::OkStatus();
67 return absl::FailedPreconditionError(
"No patches directory set");
73 const std::string& folder_name) {
74 for (
const auto& entry : fs::directory_iterator(dir_path)) {
75 if (entry.is_regular_file() && entry.path().extension() ==
".asm") {
77 std::make_unique<AsmPatch>(entry.path().string(), folder_name);
78 if (patch->is_valid()) {
79 patches_.push_back(std::move(patch));
86 const std::string& folder) {
87 std::vector<AsmPatch*> result;
89 if (patch->folder() == folder) {
90 result.push_back(patch.get());
94 std::sort(result.begin(), result.end(),
100 const std::string& filename) {
101 for (
const auto& patch :
patches_) {
102 if (patch->folder() == folder && patch->filename() == filename) {
111 for (
const auto& patch :
patches_) {
112 if (patch->enabled()) {
121 return absl::FailedPreconditionError(
"ROM not loaded");
125 if (enabled_count == 0) {
126 return absl::OkStatus();
130 std::string temp_dir = std::filesystem::temp_directory_path().string();
131 std::string temp_path = temp_dir +
"/yaze_combined_patches.asm";
138#ifdef YAZE_ENABLE_ASAR
142 if (!init_status.ok()) {
153 std::filesystem::remove(temp_path);
155 if (!result_or_error.ok()) {
156 return result_or_error.status();
159 const auto& result = *result_or_error;
161 if (!result.success) {
162 return absl::InternalError(
163 absl::StrCat(
"Failed to apply patches:\n",
164 absl::StrJoin(result.errors,
"\n")));
167 return absl::OkStatus();
170 std::filesystem::remove(temp_path);
171 return absl::UnimplementedError(
172 "Asar support not enabled. Build with YAZE_ENABLE_ASAR=ON");
177 const std::string& output_path) {
178 std::ostringstream combined;
180 combined <<
"; =============================================================================\n";
181 combined <<
"; Combined Patch File\n";
182 combined <<
"; Generated by yaze - Yet Another Zelda3 Editor\n";
183 combined <<
"; =============================================================================\n";
184 combined <<
"; This file includes all enabled patches.\n";
185 combined <<
"; Do not edit this file directly - modify individual patches instead.\n";
186 combined <<
"; =============================================================================\n\n";
188 combined <<
"lorom\n\n";
192 for (
const auto& patch :
patches_) {
193 if (patch->enabled()) {
195 std::string relative_path = patch->folder() +
"/" + patch->filename();
196 combined <<
"incsrc \"" << relative_path <<
"\"\n";
201 if (patch_count == 0) {
202 combined <<
"; No patches enabled\n";
204 combined <<
"\n; Total patches: " << patch_count <<
"\n";
208 std::ofstream file(output_path);
209 if (!file.is_open()) {
210 return absl::InternalError(
211 absl::StrCat(
"Failed to create combined patch file: ", output_path));
214 file << combined.str();
218 return absl::InternalError(
219 absl::StrCat(
"Failed to write combined patch file: ", output_path));
222 return absl::OkStatus();
226 std::vector<std::string> errors;
228 for (
const auto& patch :
patches_) {
229 auto status = patch->Save();
232 absl::StrCat(patch->filename(),
": ", status.message()));
236 if (!errors.empty()) {
237 return absl::InternalError(
238 absl::StrCat(
"Failed to save some patches:\n",
239 absl::StrJoin(errors,
"\n")));
242 return absl::OkStatus();
247 return absl::FailedPreconditionError(
"No patches directory set");
251 for (
const auto& folder :
folders_) {
252 if (folder == folder_name) {
253 return absl::AlreadyExistsError(
254 absl::StrCat(
"Folder already exists: ", folder_name));
261 fs::create_directories(folder_path, ec);
263 return absl::InternalError(
264 absl::StrCat(
"Failed to create folder: ", ec.message()));
270 return absl::OkStatus();
275 return absl::FailedPreconditionError(
"No patches directory set");
280 return absl::FailedPreconditionError(
281 "Cannot remove the last remaining folder");
287 return absl::NotFoundError(
288 absl::StrCat(
"Folder not found: ", folder_name));
294 [&folder_name](
const std::unique_ptr<AsmPatch>& patch) {
295 return patch->folder() == folder_name;
302 fs::remove_all(folder_path, ec);
304 return absl::InternalError(
305 absl::StrCat(
"Failed to remove folder: ", ec.message()));
310 return absl::OkStatus();
314 const std::string& target_folder) {
316 return absl::FailedPreconditionError(
"No patches directory set");
320 if (!fs::exists(source_path)) {
321 return absl::NotFoundError(
322 absl::StrCat(
"Source file not found: ", source_path));
328 return absl::NotFoundError(
329 absl::StrCat(
"Target folder not found: ", target_folder));
333 std::string filename = fs::path(source_path).filename().string();
338 fs::copy_file(source_path, target_path,
339 fs::copy_options::overwrite_existing, ec);
341 return absl::InternalError(
342 absl::StrCat(
"Failed to copy file: ", ec.message()));
346 auto patch = std::make_unique<AsmPatch>(target_path.string(), target_folder);
347 if (patch->is_valid()) {
348 patches_.push_back(std::move(patch));
351 return absl::OkStatus();
355 const std::string& filename) {
357 return absl::FailedPreconditionError(
"No patches directory set");
361 auto it = std::find_if(
363 [&folder, &filename](
const std::unique_ptr<AsmPatch>& patch) {
364 return patch->folder() == folder && patch->filename() == filename;
368 return absl::NotFoundError(
369 absl::StrCat(
"Patch not found: ", folder,
"/", filename));
373 std::string file_path = (*it)->file_path();
380 fs::remove(file_path, ec);
382 return absl::InternalError(
383 absl::StrCat(
"Failed to delete file: ", ec.message()));
386 return absl::OkStatus();
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Modern C++ wrapper for Asar 65816 assembler integration.
absl::StatusOr< AsarPatchResult > ApplyPatch(const std::string &patch_path, std::vector< uint8_t > &rom_data, const std::vector< std::string > &include_paths={})
absl::Status Initialize()
Represents a ZScream-compatible ASM patch file.
absl::Status ApplyEnabledPatches(Rom *rom)
Apply all enabled patches to a ROM.
absl::Status RemovePatchFile(const std::string &folder, const std::string &filename)
Remove a patch file.
std::vector< std::string > folders_
void ScanDirectory(const std::string &dir_path, const std::string &folder_name)
Scan a directory for .asm files.
absl::Status AddPatchFile(const std::string &source_path, const std::string &target_folder)
Add a patch file from an external source.
AsmPatch * GetPatch(const std::string &folder, const std::string &filename)
Get a specific patch by folder and filename.
absl::Status SaveAllPatches()
Save all patches to their files.
int GetEnabledPatchCount() const
Get count of enabled patches.
std::vector< AsmPatch * > GetPatchesInFolder(const std::string &folder)
Get all patches in a specific folder.
absl::Status CreatePatchFolder(const std::string &folder_name)
Create a new patch folder.
absl::Status ReloadPatches()
Reload patches from the current directory.
std::string patches_directory_
std::vector< std::unique_ptr< AsmPatch > > patches_
absl::Status LoadPatches(const std::string &patches_dir)
Load all patches from a directory structure.
absl::Status RemovePatchFolder(const std::string &folder_name)
Remove a patch folder and all its contents.
absl::Status GenerateCombinedPatch(const std::string &output_path)
Generate a combined .asm file that includes all enabled patches.