8#include "absl/strings/match.h"
9#include "absl/strings/str_cat.h"
10#include "absl/strings/str_join.h"
20namespace fs = std::filesystem;
31 if (!fs::exists(patches_dir)) {
32 return absl::NotFoundError(
33 absl::StrCat(
"Patches directory not found: ", patches_dir));
37 std::vector<std::string> default_folders = {
"Misc",
"Hex Edits",
"Sprites",
40 for (
const auto& folder : default_folders) {
41 fs::path folder_path = fs::path(patches_dir) / folder;
42 if (!fs::exists(folder_path)) {
44 fs::create_directories(folder_path, ec);
49 for (
const auto& entry : fs::directory_iterator(patches_dir)) {
50 if (entry.is_directory()) {
51 std::string folder_name = entry.path().filename().string();
54 if (folder_name ==
"UNPATCHED") {
67 return absl::OkStatus();
72 return absl::FailedPreconditionError(
"No patches directory set");
78 const std::string& folder_name) {
79 for (
const auto& entry : fs::directory_iterator(dir_path)) {
80 if (entry.is_regular_file() && entry.path().extension() ==
".asm") {
82 std::make_unique<AsmPatch>(entry.path().string(), folder_name);
83 if (patch->is_valid()) {
84 patches_.push_back(std::move(patch));
91 const std::string& folder) {
92 std::vector<AsmPatch*> result;
94 if (patch->folder() == folder) {
95 result.push_back(patch.get());
99 std::sort(result.begin(), result.end(),
105 const std::string& filename) {
106 for (
const auto& patch :
patches_) {
107 if (patch->folder() == folder && patch->filename() == filename) {
116 for (
const auto& patch :
patches_) {
117 if (patch->enabled()) {
126 return absl::FailedPreconditionError(
"ROM not loaded");
130 if (enabled_count == 0) {
131 return absl::OkStatus();
136 for (
const auto& patch :
patches_) {
137 if (patch->enabled() &&
138 absl::EqualsIgnoreCase(patch->filename(),
"TailMapExpansion.asm")) {
146 std::string temp_dir = std::filesystem::temp_directory_path().string();
147 std::string temp_path = temp_dir +
"/yaze_combined_patches.asm";
154#if defined(YAZE_WITH_Z3DK)
157 if (
auto init_status =
z3dk.Initialize(); !init_status.ok()) {
158 std::filesystem::remove(temp_path);
168 auto result_or_error =
z3dk.ApplyPatch(temp_path, rom_data, options);
170 std::filesystem::remove(temp_path);
172 if (!result_or_error.ok()) {
173 return result_or_error.status();
175 const auto& result = *result_or_error;
176 if (!result.success) {
177 return absl::InternalError(absl::StrCat(
178 "Failed to apply patches:\n", absl::StrJoin(result.errors,
"\n")));
180 return absl::OkStatus();
181#elif defined(YAZE_ENABLE_ASAR)
185 if (!init_status.ok()) {
193 auto result_or_error =
197 std::filesystem::remove(temp_path);
199 if (!result_or_error.ok()) {
200 return result_or_error.status();
203 const auto& result = *result_or_error;
205 if (!result.success) {
206 return absl::InternalError(absl::StrCat(
207 "Failed to apply patches:\n", absl::StrJoin(result.errors,
"\n")));
210 return absl::OkStatus();
213 std::filesystem::remove(temp_path);
214 return absl::UnimplementedError(
215 "No assembler backend enabled. Build with YAZE_ENABLE_ASAR=ON or "
216 "YAZE_ENABLE_Z3DK=ON.");
221 const std::string& output_path) {
222 std::ostringstream combined;
225 "================================================================"
227 combined <<
"; Combined Patch File\n";
228 combined <<
"; Generated by yaze - Yet Another Zelda3 Editor\n";
230 "================================================================"
232 combined <<
"; This file includes all enabled patches.\n";
233 combined <<
"; Do not edit this file directly - modify individual patches "
236 "================================================================"
239 combined <<
"lorom\n\n";
243 for (
const auto& patch :
patches_) {
244 if (patch->enabled()) {
246 std::string relative_path = patch->folder() +
"/" + patch->filename();
247 combined <<
"incsrc \"" << relative_path <<
"\"\n";
252 if (patch_count == 0) {
253 combined <<
"; No patches enabled\n";
255 combined <<
"\n; Total patches: " << patch_count <<
"\n";
259 std::ofstream file(output_path);
260 if (!file.is_open()) {
261 return absl::InternalError(
262 absl::StrCat(
"Failed to create combined patch file: ", output_path));
265 file << combined.str();
269 return absl::InternalError(
270 absl::StrCat(
"Failed to write combined patch file: ", output_path));
273 return absl::OkStatus();
277 std::vector<std::string> errors;
279 for (
const auto& patch :
patches_) {
280 auto status = patch->Save();
282 errors.push_back(absl::StrCat(patch->filename(),
": ", status.message()));
286 if (!errors.empty()) {
287 return absl::InternalError(absl::StrCat(
"Failed to save some patches:\n",
288 absl::StrJoin(errors,
"\n")));
291 return absl::OkStatus();
296 return absl::FailedPreconditionError(
"No patches directory set");
300 for (
const auto& folder :
folders_) {
301 if (folder == folder_name) {
302 return absl::AlreadyExistsError(
303 absl::StrCat(
"Folder already exists: ", folder_name));
310 fs::create_directories(folder_path, ec);
312 return absl::InternalError(
313 absl::StrCat(
"Failed to create folder: ", ec.message()));
319 return absl::OkStatus();
324 return absl::FailedPreconditionError(
"No patches directory set");
329 return absl::FailedPreconditionError(
330 "Cannot remove the last remaining folder");
336 return absl::NotFoundError(absl::StrCat(
"Folder not found: ", folder_name));
342 [&folder_name](
const std::unique_ptr<AsmPatch>& patch) {
343 return patch->folder() == folder_name;
350 fs::remove_all(folder_path, ec);
352 return absl::InternalError(
353 absl::StrCat(
"Failed to remove folder: ", ec.message()));
358 return absl::OkStatus();
362 const std::string& target_folder) {
364 return absl::FailedPreconditionError(
"No patches directory set");
368 if (!fs::exists(source_path)) {
369 return absl::NotFoundError(
370 absl::StrCat(
"Source file not found: ", source_path));
376 return absl::NotFoundError(
377 absl::StrCat(
"Target folder not found: ", target_folder));
381 std::string filename = fs::path(source_path).filename().string();
382 fs::path target_path =
387 fs::copy_file(source_path, target_path, fs::copy_options::overwrite_existing,
390 return absl::InternalError(
391 absl::StrCat(
"Failed to copy file: ", ec.message()));
395 auto patch = std::make_unique<AsmPatch>(target_path.string(), target_folder);
396 if (patch->is_valid()) {
397 patches_.push_back(std::move(patch));
400 return absl::OkStatus();
404 const std::string& filename) {
406 return absl::FailedPreconditionError(
"No patches directory set");
410 auto it = std::find_if(
412 [&folder, &filename](
const std::unique_ptr<AsmPatch>& patch) {
413 return patch->folder() == folder && patch->filename() == filename;
417 return absl::NotFoundError(
418 absl::StrCat(
"Patch not found: ", folder,
"/", filename));
422 std::string file_path = (*it)->file_path();
429 fs::remove(file_path, ec);
431 return absl::InternalError(
432 absl::StrCat(
"Failed to delete file: ", ec.message()));
435 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.
absl::Status TailMapExpansionApplyGuard(const Rom &rom)
Read-only map of overworld area IDs $00-$BF: which IDs are used, which per-area tables cover each ID,...
std::vector< std::string > include_paths