8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_join.h"
18namespace fs = std::filesystem;
29 if (!fs::exists(patches_dir)) {
30 return absl::NotFoundError(
31 absl::StrCat(
"Patches directory not found: ", patches_dir));
35 std::vector<std::string> default_folders = {
"Misc",
"Hex Edits",
"Sprites",
38 for (
const auto& folder : default_folders) {
39 fs::path folder_path = fs::path(patches_dir) / folder;
40 if (!fs::exists(folder_path)) {
42 fs::create_directories(folder_path, ec);
47 for (
const auto& entry : fs::directory_iterator(patches_dir)) {
48 if (entry.is_directory()) {
49 std::string folder_name = entry.path().filename().string();
52 if (folder_name ==
"UNPATCHED") {
65 return absl::OkStatus();
70 return absl::FailedPreconditionError(
"No patches directory set");
76 const std::string& folder_name) {
77 for (
const auto& entry : fs::directory_iterator(dir_path)) {
78 if (entry.is_regular_file() && entry.path().extension() ==
".asm") {
80 std::make_unique<AsmPatch>(entry.path().string(), folder_name);
81 if (patch->is_valid()) {
82 patches_.push_back(std::move(patch));
89 const std::string& folder) {
90 std::vector<AsmPatch*> result;
92 if (patch->folder() == folder) {
93 result.push_back(patch.get());
97 std::sort(result.begin(), result.end(),
103 const std::string& filename) {
104 for (
const auto& patch :
patches_) {
105 if (patch->folder() == folder && patch->filename() == filename) {
114 for (
const auto& patch :
patches_) {
115 if (patch->enabled()) {
124 return absl::FailedPreconditionError(
"ROM not loaded");
128 if (enabled_count == 0) {
129 return absl::OkStatus();
133 std::string temp_dir = std::filesystem::temp_directory_path().string();
134 std::string temp_path = temp_dir +
"/yaze_combined_patches.asm";
141#if defined(YAZE_WITH_Z3DK)
144 if (
auto init_status =
z3dk.Initialize(); !init_status.ok()) {
145 std::filesystem::remove(temp_path);
155 auto result_or_error =
z3dk.ApplyPatch(temp_path, rom_data, options);
157 std::filesystem::remove(temp_path);
159 if (!result_or_error.ok()) {
160 return result_or_error.status();
162 const auto& result = *result_or_error;
163 if (!result.success) {
164 return absl::InternalError(absl::StrCat(
165 "Failed to apply patches:\n", absl::StrJoin(result.errors,
"\n")));
167 return absl::OkStatus();
168#elif defined(YAZE_ENABLE_ASAR)
172 if (!init_status.ok()) {
180 auto result_or_error =
184 std::filesystem::remove(temp_path);
186 if (!result_or_error.ok()) {
187 return result_or_error.status();
190 const auto& result = *result_or_error;
192 if (!result.success) {
193 return absl::InternalError(absl::StrCat(
194 "Failed to apply patches:\n", absl::StrJoin(result.errors,
"\n")));
197 return absl::OkStatus();
200 std::filesystem::remove(temp_path);
201 return absl::UnimplementedError(
202 "No assembler backend enabled. Build with YAZE_ENABLE_ASAR=ON or "
203 "YAZE_ENABLE_Z3DK=ON.");
208 const std::string& output_path) {
209 std::ostringstream combined;
212 "================================================================"
214 combined <<
"; Combined Patch File\n";
215 combined <<
"; Generated by yaze - Yet Another Zelda3 Editor\n";
217 "================================================================"
219 combined <<
"; This file includes all enabled patches.\n";
220 combined <<
"; Do not edit this file directly - modify individual patches "
223 "================================================================"
226 combined <<
"lorom\n\n";
230 for (
const auto& patch :
patches_) {
231 if (patch->enabled()) {
233 std::string relative_path = patch->folder() +
"/" + patch->filename();
234 combined <<
"incsrc \"" << relative_path <<
"\"\n";
239 if (patch_count == 0) {
240 combined <<
"; No patches enabled\n";
242 combined <<
"\n; Total patches: " << patch_count <<
"\n";
246 std::ofstream file(output_path);
247 if (!file.is_open()) {
248 return absl::InternalError(
249 absl::StrCat(
"Failed to create combined patch file: ", output_path));
252 file << combined.str();
256 return absl::InternalError(
257 absl::StrCat(
"Failed to write combined patch file: ", output_path));
260 return absl::OkStatus();
264 std::vector<std::string> errors;
266 for (
const auto& patch :
patches_) {
267 auto status = patch->Save();
269 errors.push_back(absl::StrCat(patch->filename(),
": ", status.message()));
273 if (!errors.empty()) {
274 return absl::InternalError(absl::StrCat(
"Failed to save some patches:\n",
275 absl::StrJoin(errors,
"\n")));
278 return absl::OkStatus();
283 return absl::FailedPreconditionError(
"No patches directory set");
287 for (
const auto& folder :
folders_) {
288 if (folder == folder_name) {
289 return absl::AlreadyExistsError(
290 absl::StrCat(
"Folder already exists: ", folder_name));
297 fs::create_directories(folder_path, ec);
299 return absl::InternalError(
300 absl::StrCat(
"Failed to create folder: ", ec.message()));
306 return absl::OkStatus();
311 return absl::FailedPreconditionError(
"No patches directory set");
316 return absl::FailedPreconditionError(
317 "Cannot remove the last remaining folder");
323 return absl::NotFoundError(absl::StrCat(
"Folder not found: ", folder_name));
329 [&folder_name](
const std::unique_ptr<AsmPatch>& patch) {
330 return patch->folder() == folder_name;
337 fs::remove_all(folder_path, ec);
339 return absl::InternalError(
340 absl::StrCat(
"Failed to remove folder: ", ec.message()));
345 return absl::OkStatus();
349 const std::string& target_folder) {
351 return absl::FailedPreconditionError(
"No patches directory set");
355 if (!fs::exists(source_path)) {
356 return absl::NotFoundError(
357 absl::StrCat(
"Source file not found: ", source_path));
363 return absl::NotFoundError(
364 absl::StrCat(
"Target folder not found: ", target_folder));
368 std::string filename = fs::path(source_path).filename().string();
369 fs::path target_path =
374 fs::copy_file(source_path, target_path, fs::copy_options::overwrite_existing,
377 return absl::InternalError(
378 absl::StrCat(
"Failed to copy file: ", ec.message()));
382 auto patch = std::make_unique<AsmPatch>(target_path.string(), target_folder);
383 if (patch->is_valid()) {
384 patches_.push_back(std::move(patch));
387 return absl::OkStatus();
391 const std::string& filename) {
393 return absl::FailedPreconditionError(
"No patches directory set");
397 auto it = std::find_if(
399 [&folder, &filename](
const std::unique_ptr<AsmPatch>& patch) {
400 return patch->folder() == folder && patch->filename() == filename;
404 return absl::NotFoundError(
405 absl::StrCat(
"Patch not found: ", folder,
"/", filename));
409 std::string file_path = (*it)->file_path();
416 fs::remove(file_path, ec);
418 return absl::InternalError(
419 absl::StrCat(
"Failed to delete file: ", ec.message()));
422 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.
std::vector< std::string > include_paths