yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
patch_manager.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <filesystem>
5#include <fstream>
6#include <sstream>
7
8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_join.h"
10#include "rom/rom.h"
11#include "core/asar_wrapper.h"
12
13namespace yaze::core {
14
15namespace fs = std::filesystem;
16
17absl::Status PatchManager::LoadPatches(const std::string& patches_dir) {
18 // Clear existing data
19 patches_.clear();
20 folders_.clear();
21 is_loaded_ = false;
22
23 patches_directory_ = patches_dir;
24
25 // Check if directory exists
26 if (!fs::exists(patches_dir)) {
27 return absl::NotFoundError(
28 absl::StrCat("Patches directory not found: ", patches_dir));
29 }
30
31 // Create default folders if they don't exist
32 std::vector<std::string> default_folders = {
33 "Misc", "Hex Edits", "Sprites", "Items", "Npcs"};
34
35 for (const auto& folder : default_folders) {
36 fs::path folder_path = fs::path(patches_dir) / folder;
37 if (!fs::exists(folder_path)) {
38 std::error_code ec;
39 fs::create_directories(folder_path, ec);
40 }
41 }
42
43 // Scan all subdirectories
44 for (const auto& entry : fs::directory_iterator(patches_dir)) {
45 if (entry.is_directory()) {
46 std::string folder_name = entry.path().filename().string();
47
48 // Skip UNPATCHED folder
49 if (folder_name == "UNPATCHED") {
50 continue;
51 }
52
53 folders_.push_back(folder_name);
54 ScanDirectory(entry.path().string(), folder_name);
55 }
56 }
57
58 // Sort folders alphabetically
59 std::sort(folders_.begin(), folders_.end());
60
61 is_loaded_ = true;
62 return absl::OkStatus();
63}
64
66 if (patches_directory_.empty()) {
67 return absl::FailedPreconditionError("No patches directory set");
68 }
70}
71
72void PatchManager::ScanDirectory(const std::string& dir_path,
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") {
76 auto patch =
77 std::make_unique<AsmPatch>(entry.path().string(), folder_name);
78 if (patch->is_valid()) {
79 patches_.push_back(std::move(patch));
80 }
81 }
82 }
83}
84
85std::vector<AsmPatch*> PatchManager::GetPatchesInFolder(
86 const std::string& folder) {
87 std::vector<AsmPatch*> result;
88 for (const auto& patch : patches_) {
89 if (patch->folder() == folder) {
90 result.push_back(patch.get());
91 }
92 }
93 // Sort by name
94 std::sort(result.begin(), result.end(),
95 [](AsmPatch* a, AsmPatch* b) { return a->name() < b->name(); });
96 return result;
97}
98
99AsmPatch* PatchManager::GetPatch(const std::string& folder,
100 const std::string& filename) {
101 for (const auto& patch : patches_) {
102 if (patch->folder() == folder && patch->filename() == filename) {
103 return patch.get();
104 }
105 }
106 return nullptr;
107}
108
110 int count = 0;
111 for (const auto& patch : patches_) {
112 if (patch->enabled()) {
113 ++count;
114 }
115 }
116 return count;
117}
118
120 if (!rom || !rom->is_loaded()) {
121 return absl::FailedPreconditionError("ROM not loaded");
122 }
123
124 int enabled_count = GetEnabledPatchCount();
125 if (enabled_count == 0) {
126 return absl::OkStatus(); // Nothing to apply
127 }
128
129 // Generate temporary combined patch file
130 std::string temp_dir = std::filesystem::temp_directory_path().string();
131 std::string temp_path = temp_dir + "/yaze_combined_patches.asm";
132
133 auto status = GenerateCombinedPatch(temp_path);
134 if (!status.ok()) {
135 return status;
136 }
137
138#ifdef YAZE_ENABLE_ASAR
139 // Apply using AsarWrapper
140 AsarWrapper asar;
141 auto init_status = asar.Initialize();
142 if (!init_status.ok()) {
143 return init_status;
144 }
145
146 // Get mutable ROM data
147 auto& rom_data = rom->mutable_vector();
148
149 // Apply the combined patch
150 auto result_or_error = asar.ApplyPatch(temp_path, rom_data, {patches_directory_});
151
152 // Clean up temp file
153 std::filesystem::remove(temp_path);
154
155 if (!result_or_error.ok()) {
156 return result_or_error.status();
157 }
158
159 const auto& result = *result_or_error;
160
161 if (!result.success) {
162 return absl::InternalError(
163 absl::StrCat("Failed to apply patches:\n",
164 absl::StrJoin(result.errors, "\n")));
165 }
166
167 return absl::OkStatus();
168#else
169 // Clean up temp file
170 std::filesystem::remove(temp_path);
171 return absl::UnimplementedError(
172 "Asar support not enabled. Build with YAZE_ENABLE_ASAR=ON");
173#endif
174}
175
177 const std::string& output_path) {
178 std::ostringstream combined;
179
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";
187
188 combined << "lorom\n\n";
189
190 // Include each enabled patch
191 int patch_count = 0;
192 for (const auto& patch : patches_) {
193 if (patch->enabled()) {
194 // Use relative path from patches directory
195 std::string relative_path = patch->folder() + "/" + patch->filename();
196 combined << "incsrc \"" << relative_path << "\"\n";
197 ++patch_count;
198 }
199 }
200
201 if (patch_count == 0) {
202 combined << "; No patches enabled\n";
203 } else {
204 combined << "\n; Total patches: " << patch_count << "\n";
205 }
206
207 // Write to file
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));
212 }
213
214 file << combined.str();
215 file.close();
216
217 if (file.fail()) {
218 return absl::InternalError(
219 absl::StrCat("Failed to write combined patch file: ", output_path));
220 }
221
222 return absl::OkStatus();
223}
224
226 std::vector<std::string> errors;
227
228 for (const auto& patch : patches_) {
229 auto status = patch->Save();
230 if (!status.ok()) {
231 errors.push_back(
232 absl::StrCat(patch->filename(), ": ", status.message()));
233 }
234 }
235
236 if (!errors.empty()) {
237 return absl::InternalError(
238 absl::StrCat("Failed to save some patches:\n",
239 absl::StrJoin(errors, "\n")));
240 }
241
242 return absl::OkStatus();
243}
244
245absl::Status PatchManager::CreatePatchFolder(const std::string& folder_name) {
246 if (patches_directory_.empty()) {
247 return absl::FailedPreconditionError("No patches directory set");
248 }
249
250 // Check if folder already exists
251 for (const auto& folder : folders_) {
252 if (folder == folder_name) {
253 return absl::AlreadyExistsError(
254 absl::StrCat("Folder already exists: ", folder_name));
255 }
256 }
257
258 // Create the directory
259 fs::path folder_path = fs::path(patches_directory_) / folder_name;
260 std::error_code ec;
261 fs::create_directories(folder_path, ec);
262 if (ec) {
263 return absl::InternalError(
264 absl::StrCat("Failed to create folder: ", ec.message()));
265 }
266
267 folders_.push_back(folder_name);
268 std::sort(folders_.begin(), folders_.end());
269
270 return absl::OkStatus();
271}
272
273absl::Status PatchManager::RemovePatchFolder(const std::string& folder_name) {
274 if (patches_directory_.empty()) {
275 return absl::FailedPreconditionError("No patches directory set");
276 }
277
278 // Check if this is the last folder
279 if (folders_.size() <= 1) {
280 return absl::FailedPreconditionError(
281 "Cannot remove the last remaining folder");
282 }
283
284 // Find and remove the folder
285 auto it = std::find(folders_.begin(), folders_.end(), folder_name);
286 if (it == folders_.end()) {
287 return absl::NotFoundError(
288 absl::StrCat("Folder not found: ", folder_name));
289 }
290
291 // Remove all patches in this folder from our list
292 patches_.erase(
293 std::remove_if(patches_.begin(), patches_.end(),
294 [&folder_name](const std::unique_ptr<AsmPatch>& patch) {
295 return patch->folder() == folder_name;
296 }),
297 patches_.end());
298
299 // Remove the directory
300 fs::path folder_path = fs::path(patches_directory_) / folder_name;
301 std::error_code ec;
302 fs::remove_all(folder_path, ec);
303 if (ec) {
304 return absl::InternalError(
305 absl::StrCat("Failed to remove folder: ", ec.message()));
306 }
307
308 folders_.erase(it);
309
310 return absl::OkStatus();
311}
312
313absl::Status PatchManager::AddPatchFile(const std::string& source_path,
314 const std::string& target_folder) {
315 if (patches_directory_.empty()) {
316 return absl::FailedPreconditionError("No patches directory set");
317 }
318
319 // Check if source file exists
320 if (!fs::exists(source_path)) {
321 return absl::NotFoundError(
322 absl::StrCat("Source file not found: ", source_path));
323 }
324
325 // Check if target folder exists
326 auto it = std::find(folders_.begin(), folders_.end(), target_folder);
327 if (it == folders_.end()) {
328 return absl::NotFoundError(
329 absl::StrCat("Target folder not found: ", target_folder));
330 }
331
332 // Get filename and create target path
333 std::string filename = fs::path(source_path).filename().string();
334 fs::path target_path = fs::path(patches_directory_) / target_folder / filename;
335
336 // Copy the file
337 std::error_code ec;
338 fs::copy_file(source_path, target_path,
339 fs::copy_options::overwrite_existing, ec);
340 if (ec) {
341 return absl::InternalError(
342 absl::StrCat("Failed to copy file: ", ec.message()));
343 }
344
345 // Load the new patch
346 auto patch = std::make_unique<AsmPatch>(target_path.string(), target_folder);
347 if (patch->is_valid()) {
348 patches_.push_back(std::move(patch));
349 }
350
351 return absl::OkStatus();
352}
353
354absl::Status PatchManager::RemovePatchFile(const std::string& folder,
355 const std::string& filename) {
356 if (patches_directory_.empty()) {
357 return absl::FailedPreconditionError("No patches directory set");
358 }
359
360 // Find the patch
361 auto it = std::find_if(
362 patches_.begin(), patches_.end(),
363 [&folder, &filename](const std::unique_ptr<AsmPatch>& patch) {
364 return patch->folder() == folder && patch->filename() == filename;
365 });
366
367 if (it == patches_.end()) {
368 return absl::NotFoundError(
369 absl::StrCat("Patch not found: ", folder, "/", filename));
370 }
371
372 // Get the file path before removing from list
373 std::string file_path = (*it)->file_path();
374
375 // Remove from patches list
376 patches_.erase(it);
377
378 // Delete the file
379 std::error_code ec;
380 fs::remove(file_path, ec);
381 if (ec) {
382 return absl::InternalError(
383 absl::StrCat("Failed to delete file: ", ec.message()));
384 }
385
386 return absl::OkStatus();
387}
388
389} // namespace yaze::core
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
auto & mutable_vector()
Definition rom.h:140
bool is_loaded() const
Definition rom.h:128
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.
Definition asm_patch.h:74
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::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.