yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
test_helpers_commands.cc
Go to the documentation of this file.
1
7
8#include <filesystem>
9#include <fstream>
10#include <iomanip>
11#include <iostream>
12#include <sstream>
13#include <string>
14#include <vector>
15
16#include "absl/status/status.h"
17#include "absl/strings/str_cat.h"
18#include "app/emu/snes.h"
19#include "rom/rom.h"
22
23namespace yaze {
24namespace cli {
25namespace handlers {
26
27using namespace zelda3;
28
29// =============================================================================
30// ToolsHarnessStateCommandHandler
31// =============================================================================
32
34 Rom* /*rom*/, const resources::ArgumentParser& parser,
35 resources::OutputFormatter& formatter) {
36 std::string rom_path = parser.GetString("rom").value_or("");
37 std::string output_path = parser.GetString("output").value_or("");
38
39 auto status = GenerateHarnessState(rom_path, output_path);
40 if (!status.ok()) {
41 return status;
42 }
43
44 formatter.AddField("status", "success");
45 formatter.AddField("output_file", output_path);
46 formatter.AddField("message",
47 "Successfully generated harness state from " + rom_path);
48 return absl::OkStatus();
49}
50
52 const std::string& rom_path, const std::string& output_path) {
53 // Load ROM
54 Rom rom;
55 RETURN_IF_ERROR(rom.LoadFromFile(rom_path));
56 auto rom_data = rom.vector();
57
58 // Initialize SNES
59 emu::Snes snes;
60 snes.Init(rom_data);
61 snes.Reset(false);
62
63 auto& cpu = snes.cpu();
64 auto& ppu = snes.ppu();
65
66 // Run emulator until the main game loop is reached
67 int max_cycles = 15000000; // 15 million cycles
68 int cycles = 0;
69 while (cycles < max_cycles) {
70 snes.RunCycle();
71 cycles++;
72 if (cpu.PB == 0x00 && cpu.PC == 0x8034) {
73 break; // Reached MainGameLoop
74 }
75 }
76
77 if (cycles >= max_cycles) {
78 return absl::InternalError(
79 "Emulator timed out; did not reach main game loop.");
80 }
81
82 std::ofstream out_file(output_path);
83 if (!out_file.is_open()) {
84 return absl::InternalError("Failed to open output file: " + output_path);
85 }
86
87 // Write header
88 out_file << "// ================================================"
89 "=========================\n";
90 out_file << "// YAZE Dungeon Test Harness State - Generated from: "
91 << rom_path << "\n";
92 out_file << "// ================================================"
93 "=========================\n\n";
94 out_file << "#pragma once\n\n";
95 out_file << "#include <cstdint>\n";
96 out_file << "#include <array>\n\n";
97 out_file << "namespace yaze {\n";
98 out_file << "namespace emu {\n\n";
99
100 // Write WRAM state
101 out_file << "constexpr std::array<uint8_t, 0x20000> kInitialWRAMState = {{\n";
102 for (int i = 0; i < 0x20000; ++i) {
103 if (i % 16 == 0)
104 out_file << " ";
105 out_file << "0x" << std::hex << std::setw(2) << std::setfill('0')
106 << static_cast<int>(snes.Read(0x7E0000 + i));
107 if (i < 0x1FFFF)
108 out_file << ", ";
109 if (i % 16 == 15)
110 out_file << "\n";
111 }
112 out_file << "}};\n\n";
113
114 // Write CPU/PPU register state
115 out_file << "// ================================================"
116 "=========================\n";
117 out_file << "// Initial Register States\n";
118 out_file << "// ================================================"
119 "=========================\n\n";
120
121 out_file << "struct InitialPpuState {\n";
122 out_file << " uint8_t inidisp = 0x" << std::hex << ppu.Read(0x2100, false)
123 << ";\n";
124 out_file << " uint8_t objsel = 0x" << std::hex << ppu.Read(0x2101, false)
125 << ";\n";
126 out_file << " uint8_t bgmode = 0x" << std::hex << ppu.Read(0x2105, false)
127 << ";\n";
128 out_file << " uint8_t mosaic = 0x" << std::hex << ppu.Read(0x2106, false)
129 << ";\n";
130 out_file << " uint8_t tm = 0x" << std::hex << ppu.Read(0x212C, false)
131 << ";\n";
132 out_file << " uint8_t ts = 0x" << std::hex << ppu.Read(0x212D, false)
133 << ";\n";
134 out_file << " uint8_t cgwsel = 0x" << std::hex << ppu.Read(0x2130, false)
135 << ";\n";
136 out_file << " uint8_t cgadsub = 0x" << std::hex << ppu.Read(0x2131, false)
137 << ";\n";
138 out_file << " uint8_t setini = 0x" << std::hex << ppu.Read(0x2133, false)
139 << ";\n";
140 out_file << "};\n\n";
141
142 out_file << "} // namespace emu\n";
143 out_file << "} // namespace yaze\n";
144
145 return absl::OkStatus();
146}
147
148// =============================================================================
149// ToolsExtractValuesCommandHandler
150// =============================================================================
151
153 Rom* rom, const resources::ArgumentParser& parser,
154 resources::OutputFormatter& formatter) {
155 std::string rom_path = parser.GetString("rom").value_or("");
156 std::string format = parser.GetString("format").value_or("cpp");
157
158 // Load the ROM if not already loaded
159 if (!rom || rom->size() == 0) {
160 Rom loaded_rom;
161 RETURN_IF_ERROR(loaded_rom.LoadFromFile(rom_path));
162 rom = &loaded_rom;
163 }
164
165 std::ostringstream output;
166
167 if (format == "json") {
168 output << "{\n";
169 output << " \"asm_version\": \"0x" << std::hex << std::setw(2)
170 << std::setfill('0')
171 << static_cast<int>((*rom)[OverworldCustomASMHasBeenApplied])
172 << "\",\n";
173 output << " \"area_graphics\": [";
174 for (int i = 0; i < 10; i++) {
175 output << "\"0x" << std::hex << std::setw(2) << std::setfill('0')
176 << static_cast<int>((*rom)[kAreaGfxIdPtr + i]) << "\"";
177 if (i < 9)
178 output << ", ";
179 }
180 output << "],\n";
181 output << " \"area_palettes\": [";
182 for (int i = 0; i < 10; i++) {
183 output << "\"0x" << std::hex << std::setw(2) << std::setfill('0')
184 << static_cast<int>((*rom)[kOverworldMapPaletteIds + i]) << "\"";
185 if (i < 9)
186 output << ", ";
187 }
188 output << "],\n";
189 output << " \"screen_sizes\": [";
190 for (int i = 0; i < 10; i++) {
191 output << "\"0x" << std::hex << std::setw(2) << std::setfill('0')
192 << static_cast<int>((*rom)[kOverworldScreenSize + i]) << "\"";
193 if (i < 9)
194 output << ", ";
195 }
196 output << "]\n";
197 output << "}\n";
198 } else {
199 // C++ format
200 output << "// Vanilla ROM values extracted\n\n";
201
202 output << "constexpr uint8_t kVanillaASMVersion = 0x" << std::hex
203 << std::setw(2) << std::setfill('0')
204 << static_cast<int>((*rom)[OverworldCustomASMHasBeenApplied])
205 << ";\n\n";
206
207 output << "// Area graphics for first 10 maps\n";
208 for (int i = 0; i < 10; i++) {
209 output << "constexpr uint8_t kVanillaAreaGraphics" << std::dec << i
210 << " = 0x" << std::hex << std::setw(2) << std::setfill('0')
211 << static_cast<int>((*rom)[kAreaGfxIdPtr + i]) << ";\n";
212 }
213 output << "\n";
214
215 output << "// Area palettes for first 10 maps\n";
216 for (int i = 0; i < 10; i++) {
217 output << "constexpr uint8_t kVanillaAreaPalette" << std::dec << i
218 << " = 0x" << std::hex << std::setw(2) << std::setfill('0')
219 << static_cast<int>((*rom)[kOverworldMapPaletteIds + i]) << ";\n";
220 }
221 output << "\n";
222
223 output << "// Screen sizes for first 10 maps\n";
224 for (int i = 0; i < 10; i++) {
225 output << "constexpr uint8_t kVanillaScreenSize" << std::dec << i
226 << " = 0x" << std::hex << std::setw(2) << std::setfill('0')
227 << static_cast<int>((*rom)[kOverworldScreenSize + i]) << ";\n";
228 }
229 output << "\n";
230
231 output << "// Sprite sets for first 10 maps\n";
232 for (int i = 0; i < 10; i++) {
233 output << "constexpr uint8_t kVanillaSpriteSet" << std::dec << i
234 << " = 0x" << std::hex << std::setw(2) << std::setfill('0')
235 << static_cast<int>((*rom)[kOverworldSpriteset + i]) << ";\n";
236 }
237 }
238
239 std::cout << output.str();
240 formatter.AddField("status", "success");
241 return absl::OkStatus();
242}
243
244// =============================================================================
245// ToolsExtractGoldenCommandHandler
246// =============================================================================
247
249 Rom* rom, const resources::ArgumentParser& parser,
250 resources::OutputFormatter& formatter) {
251 std::string rom_path = parser.GetString("rom").value_or("");
252 std::string output_path = parser.GetString("output").value_or("");
253
254 // Load ROM
255 Rom loaded_rom;
256 RETURN_IF_ERROR(loaded_rom.LoadFromFile(rom_path));
257
258 // Load overworld data
259 Overworld overworld(&loaded_rom);
260 RETURN_IF_ERROR(overworld.Load(&loaded_rom));
261
262 std::ofstream out_file(output_path);
263 if (!out_file.is_open()) {
264 return absl::InternalError("Failed to open output file: " + output_path);
265 }
266
267 WriteHeader(out_file, rom_path);
268 WriteBasicROMInfo(out_file, loaded_rom);
269 WriteASMVersionInfo(out_file, loaded_rom);
270 WriteOverworldMapsData(out_file, overworld);
271 WriteTileData(out_file, overworld);
272 WriteEntranceData(out_file, overworld);
273 WriteFooter(out_file);
274
275 formatter.AddField("status", "success");
276 formatter.AddField("output_file", output_path);
277 formatter.AddField("message",
278 "Successfully extracted golden data from " + rom_path);
279 return absl::OkStatus();
280}
281
283 std::ofstream& out, const std::string& rom_path) {
284 out << "// =============================================="
285 "===========================\n";
286 out << "// YAZE Overworld Golden Data - Generated from: " << rom_path << "\n";
287 out << "// =============================================="
288 "===========================\n\n";
289 out << "#pragma once\n\n";
290 out << "#include <cstdint>\n";
291 out << "#include <array>\n";
292 out << "#include <vector>\n";
293 out << "#include \"zelda3/overworld/overworld_map.h\"\n\n";
294 out << "namespace yaze {\n";
295 out << "namespace test {\n\n";
296}
297
299 Rom& rom) {
300 out << "// =============================================="
301 "===========================\n";
302 out << "// Basic ROM Information\n";
303 out << "// =============================================="
304 "===========================\n\n";
305
306 out << "constexpr std::string_view kGoldenROMTitle = \"" << rom.title()
307 << "\";\n";
308 out << "constexpr size_t kGoldenROMSize = " << std::dec << rom.size()
309 << ";\n\n";
310}
311
313 Rom& rom) {
314 out << "// =============================================="
315 "===========================\n";
316 out << "// ASM Version Information\n";
317 out << "// =============================================="
318 "===========================\n\n";
319
320 auto asm_version = rom.ReadByte(0x140145);
321 if (asm_version.ok()) {
322 out << "constexpr uint8_t kGoldenASMVersion = 0x" << std::hex
323 << std::setw(2) << std::setfill('0') << static_cast<int>(*asm_version)
324 << ";\n";
325
326 if (*asm_version == 0xFF) {
327 out << "constexpr bool kGoldenIsVanillaROM = true;\n";
328 out << "constexpr bool kGoldenHasZSCustomOverworld = false;\n";
329 } else {
330 out << "constexpr bool kGoldenIsVanillaROM = false;\n";
331 out << "constexpr bool kGoldenHasZSCustomOverworld = true;\n";
332 out << "constexpr uint8_t kGoldenZSCustomOverworldVersion = " << std::dec
333 << static_cast<int>(*asm_version) << ";\n";
334 }
335 out << "\n";
336 }
337}
338
340 std::ofstream& out, Overworld& overworld) {
341 out << "// =============================================="
342 "===========================\n";
343 out << "// Overworld Maps Data\n";
344 out << "// =============================================="
345 "===========================\n\n";
346
347 const auto& maps = overworld.overworld_maps();
348 out << "constexpr size_t kGoldenNumOverworldMaps = " << std::dec
349 << maps.size() << ";\n\n";
350
351 out << "// Map properties for first 20 maps\n";
352 out << "constexpr std::array<uint8_t, 20> kGoldenMapAreaGraphics = {{\n";
353 for (int i = 0; i < std::min(20, static_cast<int>(maps.size())); i++) {
354 out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
355 << static_cast<int>(maps[i].area_graphics());
356 if (i < 19)
357 out << ",";
358 out << " // Map " << std::dec << i << "\n";
359 }
360 out << "}};\n\n";
361}
362
364 Overworld& overworld) {
365 out << "// =============================================="
366 "===========================\n";
367 out << "// Tile Data Information\n";
368 out << "// =============================================="
369 "===========================\n\n";
370
371 out << "constexpr bool kGoldenExpandedTile16 = "
372 << (overworld.expanded_tile16() ? "true" : "false") << ";\n";
373 out << "constexpr bool kGoldenExpandedTile32 = "
374 << (overworld.expanded_tile32() ? "true" : "false") << ";\n\n";
375
376 const auto& tiles16 = overworld.tiles16();
377 const auto& tiles32 = overworld.tiles32_unique();
378
379 out << "constexpr size_t kGoldenNumTiles16 = " << std::dec << tiles16.size()
380 << ";\n";
381 out << "constexpr size_t kGoldenNumTiles32 = " << std::dec << tiles32.size()
382 << ";\n\n";
383}
384
386 Overworld& overworld) {
387 out << "// =============================================="
388 "===========================\n";
389 out << "// Entrance Data\n";
390 out << "// =============================================="
391 "===========================\n\n";
392
393 const auto& entrances = overworld.entrances();
394 out << "constexpr size_t kGoldenNumEntrances = " << std::dec
395 << entrances.size() << ";\n\n";
396
397 out << "// Sample entrance data (first 10 entrances)\n";
398 out << "constexpr std::array<uint16_t, 10> kGoldenEntranceMapPos = {{\n";
399 for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
400 out << " 0x" << std::hex << std::setw(4) << std::setfill('0')
401 << entrances[i].map_pos_;
402 if (i < 9)
403 out << ",";
404 out << " // Entrance " << std::dec << i << "\n";
405 }
406 out << "}};\n\n";
407}
408
410 out << "\n";
411 out << "} // namespace test\n";
412 out << "} // namespace yaze\n";
413}
414
415// =============================================================================
416// ToolsPatchV3CommandHandler
417// =============================================================================
418
420 Rom* /*rom*/, const resources::ArgumentParser& parser,
421 resources::OutputFormatter& formatter) {
422 // This utility loads an independent ROM rather than the guarded active one.
423 if (project_ != nullptr && project_->rom_metadata.write_policy ==
425 return absl::PermissionDeniedError(
426 "ROM patching is disabled by the project's read-only policy");
427 }
428 std::string input_path = parser.GetString("rom").value_or("");
429 std::string output_path = parser.GetString("output").value_or("");
430
431 // Load the vanilla ROM
432 Rom rom;
433 RETURN_IF_ERROR(rom.LoadFromFile(input_path));
434
435 // Apply v3 patch
437
438 // Save the patched ROM
439 RETURN_IF_ERROR(rom.SaveToFile(Rom::SaveSettings{.filename = output_path}));
440
441 formatter.AddField("status", "success");
442 formatter.AddField("output_file", output_path);
443 formatter.AddField("message",
444 "Successfully created v3 patched ROM: " + output_path);
445 return absl::OkStatus();
446}
447
449 // Set ASM version to v3
451
452 // Enable v3 features
459
460 // Apply v3 settings to first 10 maps for testing
461 for (int i = 0; i < 10; i++) {
462 // Set area sizes (mix of different sizes)
463 auto area_size = static_cast<AreaSizeEnum>(i % 4);
464 rom.WriteByte(kOverworldScreenSize + i, static_cast<uint8_t>(area_size));
465
466 // Set main palettes
468
469 // Set area-specific background colors
470 uint16_t bg_color = 0x0000 + (i * 0x1000);
472 bg_color & 0xFF);
474 (bg_color >> 8) & 0xFF);
475
476 // Set subscreen overlays
477 uint16_t overlay = 0x0090 + i;
479 overlay & 0xFF);
481 (overlay >> 8) & 0xFF);
482
483 // Set animated GFX
485
486 // Set custom tile GFX groups (8 bytes per map)
487 for (int j = 0; j < 8; j++) {
489 0x20 + j + i);
490 }
491
492 // Set mosaic settings
493 rom.WriteByte(OverworldCustomMosaicArray + i, i % 16);
494
495 // Set expanded message IDs
496 uint16_t message_id = 0x1000 + i;
497 rom.WriteByte(kOverworldMessagesExpanded + (i * 2), message_id & 0xFF);
498 rom.WriteByte(kOverworldMessagesExpanded + (i * 2) + 1,
499 (message_id >> 8) & 0xFF);
500 }
501
502 return absl::OkStatus();
503}
504
505// =============================================================================
506// ToolsListCommandHandler
507// =============================================================================
508
510 Rom* /*rom*/, const resources::ArgumentParser& /*parser*/,
511 resources::OutputFormatter& formatter) {
512 std::cout << "Available Test Helper Tools:\n\n";
513 std::cout
514 << " tools-harness-state Generate WRAM state for test harnesses\n";
515 std::cout << " tools-extract-values Extract vanilla ROM values\n";
516 std::cout << " tools-extract-golden Extract comprehensive golden data\n";
517 std::cout
518 << " tools-patch-v3 Create v3 ZSCustomOverworld patched ROM\n";
519 std::cout << "\nUsage: z3ed <tool-name> --help\n";
520
521 formatter.AddField("status", "success");
522 return absl::OkStatus();
523}
524
525} // namespace handlers
526} // namespace cli
527} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:52
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:632
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:1104
absl::StatusOr< uint8_t > ReadByte(int offset) const
Definition rom.cc:1036
const auto & vector() const
Definition rom.h:217
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:893
auto size() const
Definition rom.h:212
auto title() const
Definition rom.h:211
void WriteOverworldMapsData(std::ofstream &out, zelda3::Overworld &overworld)
void WriteTileData(std::ofstream &out, zelda3::Overworld &overworld)
void WriteHeader(std::ofstream &out, const std::string &rom_path)
void WriteEntranceData(std::ofstream &out, zelda3::Overworld &overworld)
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status GenerateHarnessState(const std::string &rom_path, const std::string &output_path)
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
Utility for parsing common CLI argument patterns.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
Utility for consistent output formatting across commands.
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.
auto ppu() -> Ppu &
Definition snes.h:85
uint8_t Read(uint32_t adr)
Definition snes.cc:629
void Reset(bool hard=false)
Definition snes.cc:181
auto cpu() -> Cpu &
Definition snes.h:84
void Init(const std::vector< uint8_t > &rom_data)
Definition snes.cc:162
void RunCycle()
Definition snes.cc:315
absl::Status Load(Rom *rom)
Load all overworld data from ROM.
Definition overworld.cc:158
auto expanded_tile32() const
Definition overworld.h:838
const std::vector< gfx::Tile16 > & tiles16() const
Definition overworld.h:784
auto expanded_tile16() const
Definition overworld.h:837
const std::vector< OverworldMap > & overworld_maps() const
Definition overworld.h:767
auto tiles32_unique() const
Definition overworld.h:785
const std::vector< OverworldEntrance > & entrances() const
Definition overworld.h:803
constexpr int kAreaGfxIdPtr
Definition overworld.h:125
constexpr int OverworldCustomTileGFXGroupEnabled
constexpr int OverworldCustomAreaSpecificBGEnabled
constexpr int kOverworldSpriteset
Definition overworld.h:118
constexpr int kOverworldScreenSize
Definition overworld.h:151
constexpr int OverworldCustomMosaicArray
constexpr int OverworldCustomAnimatedGFXEnabled
constexpr int OverworldCustomMainPaletteEnabled
constexpr int OverworldCustomMainPaletteArray
AreaSizeEnum
Area size enumeration for v3+ ROMs.
constexpr int OverworldCustomASMHasBeenApplied
Definition common.h:90
constexpr int kOverworldMessagesExpanded
constexpr int OverworldCustomAnimatedGFXArray
constexpr int OverworldCustomMosaicEnabled
constexpr int OverworldCustomTileGFXGroupArray
constexpr int OverworldCustomSubscreenOverlayEnabled
constexpr int OverworldCustomAreaSpecificBGPalette
constexpr int kOverworldMapPaletteIds
Definition overworld.h:115
constexpr int OverworldCustomSubscreenOverlayArray
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
RomWritePolicy write_policy
Definition project.h:229
CLI command handlers for test helper tools.