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) out_file << " ";
104 out_file << "0x" << std::hex << std::setw(2) << std::setfill('0')
105 << static_cast<int>(snes.Read(0x7E0000 + i));
106 if (i < 0x1FFFF) out_file << ", ";
107 if (i % 16 == 15) out_file << "\n";
108 }
109 out_file << "}};\n\n";
110
111 // Write CPU/PPU register state
112 out_file << "// ================================================"
113 "=========================\n";
114 out_file << "// Initial Register States\n";
115 out_file << "// ================================================"
116 "=========================\n\n";
117
118 out_file << "struct InitialPpuState {\n";
119 out_file << " uint8_t inidisp = 0x" << std::hex << ppu.Read(0x2100, false)
120 << ";\n";
121 out_file << " uint8_t objsel = 0x" << std::hex << ppu.Read(0x2101, false)
122 << ";\n";
123 out_file << " uint8_t bgmode = 0x" << std::hex << ppu.Read(0x2105, false)
124 << ";\n";
125 out_file << " uint8_t mosaic = 0x" << std::hex << ppu.Read(0x2106, false)
126 << ";\n";
127 out_file << " uint8_t tm = 0x" << std::hex << ppu.Read(0x212C, false)
128 << ";\n";
129 out_file << " uint8_t ts = 0x" << std::hex << ppu.Read(0x212D, false)
130 << ";\n";
131 out_file << " uint8_t cgwsel = 0x" << std::hex << ppu.Read(0x2130, false)
132 << ";\n";
133 out_file << " uint8_t cgadsub = 0x" << std::hex << ppu.Read(0x2131, false)
134 << ";\n";
135 out_file << " uint8_t setini = 0x" << std::hex << ppu.Read(0x2133, false)
136 << ";\n";
137 out_file << "};\n\n";
138
139 out_file << "} // namespace emu\n";
140 out_file << "} // namespace yaze\n";
141
142 return absl::OkStatus();
143}
144
145// =============================================================================
146// ToolsExtractValuesCommandHandler
147// =============================================================================
148
150 Rom* rom, const resources::ArgumentParser& parser,
151 resources::OutputFormatter& formatter) {
152 std::string rom_path = parser.GetString("rom").value_or("");
153 std::string format = parser.GetString("format").value_or("cpp");
154
155 // Load the ROM if not already loaded
156 if (!rom || rom->size() == 0) {
157 Rom loaded_rom;
158 RETURN_IF_ERROR(loaded_rom.LoadFromFile(rom_path));
159 rom = &loaded_rom;
160 }
161
162 std::ostringstream output;
163
164 if (format == "json") {
165 output << "{\n";
166 output << " \"asm_version\": \"0x" << std::hex << std::setw(2)
167 << std::setfill('0')
168 << static_cast<int>((*rom)[OverworldCustomASMHasBeenApplied])
169 << "\",\n";
170 output << " \"area_graphics\": [";
171 for (int i = 0; i < 10; i++) {
172 output << "\"0x" << std::hex << std::setw(2) << std::setfill('0')
173 << static_cast<int>((*rom)[kAreaGfxIdPtr + i]) << "\"";
174 if (i < 9) output << ", ";
175 }
176 output << "],\n";
177 output << " \"area_palettes\": [";
178 for (int i = 0; i < 10; i++) {
179 output << "\"0x" << std::hex << std::setw(2) << std::setfill('0')
180 << static_cast<int>((*rom)[kOverworldMapPaletteIds + i]) << "\"";
181 if (i < 9) output << ", ";
182 }
183 output << "],\n";
184 output << " \"screen_sizes\": [";
185 for (int i = 0; i < 10; i++) {
186 output << "\"0x" << std::hex << std::setw(2) << std::setfill('0')
187 << static_cast<int>((*rom)[kOverworldScreenSize + i]) << "\"";
188 if (i < 9) output << ", ";
189 }
190 output << "]\n";
191 output << "}\n";
192 } else {
193 // C++ format
194 output << "// Vanilla ROM values extracted\n\n";
195
196 output << "constexpr uint8_t kVanillaASMVersion = 0x" << std::hex
197 << std::setw(2) << std::setfill('0')
198 << static_cast<int>((*rom)[OverworldCustomASMHasBeenApplied])
199 << ";\n\n";
200
201 output << "// Area graphics for first 10 maps\n";
202 for (int i = 0; i < 10; i++) {
203 output << "constexpr uint8_t kVanillaAreaGraphics" << std::dec << i
204 << " = 0x" << std::hex << std::setw(2) << std::setfill('0')
205 << static_cast<int>((*rom)[kAreaGfxIdPtr + i]) << ";\n";
206 }
207 output << "\n";
208
209 output << "// Area palettes for first 10 maps\n";
210 for (int i = 0; i < 10; i++) {
211 output << "constexpr uint8_t kVanillaAreaPalette" << std::dec << i
212 << " = 0x" << std::hex << std::setw(2) << std::setfill('0')
213 << static_cast<int>((*rom)[kOverworldMapPaletteIds + i]) << ";\n";
214 }
215 output << "\n";
216
217 output << "// Screen sizes for first 10 maps\n";
218 for (int i = 0; i < 10; i++) {
219 output << "constexpr uint8_t kVanillaScreenSize" << std::dec << i
220 << " = 0x" << std::hex << std::setw(2) << std::setfill('0')
221 << static_cast<int>((*rom)[kOverworldScreenSize + i]) << ";\n";
222 }
223 output << "\n";
224
225 output << "// Sprite sets for first 10 maps\n";
226 for (int i = 0; i < 10; i++) {
227 output << "constexpr uint8_t kVanillaSpriteSet" << std::dec << i
228 << " = 0x" << std::hex << std::setw(2) << std::setfill('0')
229 << static_cast<int>((*rom)[kOverworldSpriteset + i]) << ";\n";
230 }
231 }
232
233 std::cout << output.str();
234 formatter.AddField("status", "success");
235 return absl::OkStatus();
236}
237
238// =============================================================================
239// ToolsExtractGoldenCommandHandler
240// =============================================================================
241
243 Rom* rom, const resources::ArgumentParser& parser,
244 resources::OutputFormatter& formatter) {
245 std::string rom_path = parser.GetString("rom").value_or("");
246 std::string output_path = parser.GetString("output").value_or("");
247
248 // Load ROM
249 Rom loaded_rom;
250 RETURN_IF_ERROR(loaded_rom.LoadFromFile(rom_path));
251
252 // Load overworld data
253 Overworld overworld(&loaded_rom);
254 RETURN_IF_ERROR(overworld.Load(&loaded_rom));
255
256 std::ofstream out_file(output_path);
257 if (!out_file.is_open()) {
258 return absl::InternalError("Failed to open output file: " + output_path);
259 }
260
261 WriteHeader(out_file, rom_path);
262 WriteBasicROMInfo(out_file, loaded_rom);
263 WriteASMVersionInfo(out_file, loaded_rom);
264 WriteOverworldMapsData(out_file, overworld);
265 WriteTileData(out_file, overworld);
266 WriteEntranceData(out_file, overworld);
267 WriteFooter(out_file);
268
269 formatter.AddField("status", "success");
270 formatter.AddField("output_file", output_path);
271 formatter.AddField("message",
272 "Successfully extracted golden data from " + rom_path);
273 return absl::OkStatus();
274}
275
277 const std::string& rom_path) {
278 out << "// =============================================="
279 "===========================\n";
280 out << "// YAZE Overworld Golden Data - Generated from: " << rom_path << "\n";
281 out << "// =============================================="
282 "===========================\n\n";
283 out << "#pragma once\n\n";
284 out << "#include <cstdint>\n";
285 out << "#include <array>\n";
286 out << "#include <vector>\n";
287 out << "#include \"zelda3/overworld/overworld_map.h\"\n\n";
288 out << "namespace yaze {\n";
289 out << "namespace test {\n\n";
290}
291
293 Rom& rom) {
294 out << "// =============================================="
295 "===========================\n";
296 out << "// Basic ROM Information\n";
297 out << "// =============================================="
298 "===========================\n\n";
299
300 out << "constexpr std::string_view kGoldenROMTitle = \"" << rom.title()
301 << "\";\n";
302 out << "constexpr size_t kGoldenROMSize = " << std::dec << rom.size()
303 << ";\n\n";
304}
305
307 Rom& rom) {
308 out << "// =============================================="
309 "===========================\n";
310 out << "// ASM Version Information\n";
311 out << "// =============================================="
312 "===========================\n\n";
313
314 auto asm_version = rom.ReadByte(0x140145);
315 if (asm_version.ok()) {
316 out << "constexpr uint8_t kGoldenASMVersion = 0x" << std::hex << std::setw(2)
317 << std::setfill('0') << static_cast<int>(*asm_version) << ";\n";
318
319 if (*asm_version == 0xFF) {
320 out << "constexpr bool kGoldenIsVanillaROM = true;\n";
321 out << "constexpr bool kGoldenHasZSCustomOverworld = false;\n";
322 } else {
323 out << "constexpr bool kGoldenIsVanillaROM = false;\n";
324 out << "constexpr bool kGoldenHasZSCustomOverworld = true;\n";
325 out << "constexpr uint8_t kGoldenZSCustomOverworldVersion = "
326 << std::dec << static_cast<int>(*asm_version) << ";\n";
327 }
328 out << "\n";
329 }
330}
331
333 std::ofstream& out, Overworld& overworld) {
334 out << "// =============================================="
335 "===========================\n";
336 out << "// Overworld Maps Data\n";
337 out << "// =============================================="
338 "===========================\n\n";
339
340 const auto& maps = overworld.overworld_maps();
341 out << "constexpr size_t kGoldenNumOverworldMaps = " << std::dec
342 << maps.size() << ";\n\n";
343
344 out << "// Map properties for first 20 maps\n";
345 out << "constexpr std::array<uint8_t, 20> kGoldenMapAreaGraphics = {{\n";
346 for (int i = 0; i < std::min(20, static_cast<int>(maps.size())); i++) {
347 out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
348 << static_cast<int>(maps[i].area_graphics());
349 if (i < 19) out << ",";
350 out << " // Map " << std::dec << i << "\n";
351 }
352 out << "}};\n\n";
353}
354
356 Overworld& overworld) {
357 out << "// =============================================="
358 "===========================\n";
359 out << "// Tile Data Information\n";
360 out << "// =============================================="
361 "===========================\n\n";
362
363 out << "constexpr bool kGoldenExpandedTile16 = "
364 << (overworld.expanded_tile16() ? "true" : "false") << ";\n";
365 out << "constexpr bool kGoldenExpandedTile32 = "
366 << (overworld.expanded_tile32() ? "true" : "false") << ";\n\n";
367
368 const auto& tiles16 = overworld.tiles16();
369 const auto& tiles32 = overworld.tiles32_unique();
370
371 out << "constexpr size_t kGoldenNumTiles16 = " << std::dec << tiles16.size()
372 << ";\n";
373 out << "constexpr size_t kGoldenNumTiles32 = " << std::dec << tiles32.size()
374 << ";\n\n";
375}
376
378 std::ofstream& out, Overworld& overworld) {
379 out << "// =============================================="
380 "===========================\n";
381 out << "// Entrance Data\n";
382 out << "// =============================================="
383 "===========================\n\n";
384
385 const auto& entrances = overworld.entrances();
386 out << "constexpr size_t kGoldenNumEntrances = " << std::dec
387 << entrances.size() << ";\n\n";
388
389 out << "// Sample entrance data (first 10 entrances)\n";
390 out << "constexpr std::array<uint16_t, 10> kGoldenEntranceMapPos = {{\n";
391 for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
392 out << " 0x" << std::hex << std::setw(4) << std::setfill('0')
393 << entrances[i].map_pos_;
394 if (i < 9) out << ",";
395 out << " // Entrance " << std::dec << i << "\n";
396 }
397 out << "}};\n\n";
398}
399
401 out << "\n";
402 out << "} // namespace test\n";
403 out << "} // namespace yaze\n";
404}
405
406// =============================================================================
407// ToolsPatchV3CommandHandler
408// =============================================================================
409
411 Rom* /*rom*/, const resources::ArgumentParser& parser,
412 resources::OutputFormatter& formatter) {
413 std::string input_path = parser.GetString("rom").value_or("");
414 std::string output_path = parser.GetString("output").value_or("");
415
416 // Load the vanilla ROM
417 Rom rom;
418 RETURN_IF_ERROR(rom.LoadFromFile(input_path));
419
420 // Apply v3 patch
422
423 // Save the patched ROM
424 RETURN_IF_ERROR(rom.SaveToFile(Rom::SaveSettings{.filename = output_path}));
425
426 formatter.AddField("status", "success");
427 formatter.AddField("output_file", output_path);
428 formatter.AddField("message",
429 "Successfully created v3 patched ROM: " + output_path);
430 return absl::OkStatus();
431}
432
434 // Set ASM version to v3
436
437 // Enable v3 features
444
445 // Apply v3 settings to first 10 maps for testing
446 for (int i = 0; i < 10; i++) {
447 // Set area sizes (mix of different sizes)
448 auto area_size = static_cast<AreaSizeEnum>(i % 4);
449 rom.WriteByte(kOverworldScreenSize + i, static_cast<uint8_t>(area_size));
450
451 // Set main palettes
453
454 // Set area-specific background colors
455 uint16_t bg_color = 0x0000 + (i * 0x1000);
457 bg_color & 0xFF);
459 (bg_color >> 8) & 0xFF);
460
461 // Set subscreen overlays
462 uint16_t overlay = 0x0090 + i;
464 overlay & 0xFF);
466 (overlay >> 8) & 0xFF);
467
468 // Set animated GFX
470
471 // Set custom tile GFX groups (8 bytes per map)
472 for (int j = 0; j < 8; j++) {
474 0x20 + j + i);
475 }
476
477 // Set mosaic settings
478 rom.WriteByte(OverworldCustomMosaicArray + i, i % 16);
479
480 // Set expanded message IDs
481 uint16_t message_id = 0x1000 + i;
482 rom.WriteByte(kOverworldMessagesExpanded + (i * 2), message_id & 0xFF);
483 rom.WriteByte(kOverworldMessagesExpanded + (i * 2) + 1,
484 (message_id >> 8) & 0xFF);
485 }
486
487 return absl::OkStatus();
488}
489
490// =============================================================================
491// ToolsListCommandHandler
492// =============================================================================
493
495 Rom* /*rom*/, const resources::ArgumentParser& /*parser*/,
496 resources::OutputFormatter& formatter) {
497 std::cout << "Available Test Helper Tools:\n\n";
498 std::cout << " tools-harness-state Generate WRAM state for test harnesses\n";
499 std::cout << " tools-extract-values Extract vanilla ROM values\n";
500 std::cout << " tools-extract-golden Extract comprehensive golden data\n";
501 std::cout << " tools-patch-v3 Create v3 ZSCustomOverworld patched ROM\n";
502 std::cout << "\nUsage: z3ed <tool-name> --help\n";
503
504 formatter.AddField("status", "success");
505 return absl::OkStatus();
506}
507
508} // namespace handlers
509} // namespace cli
510} // namespace yaze
511
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
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:74
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:286
const auto & vector() const
Definition rom.h:139
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:164
auto size() const
Definition rom.h:134
absl::StatusOr< uint8_t > ReadByte(int offset)
Definition rom.cc:221
auto title() const
Definition rom.h:133
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:620
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
Represents the full Overworld data, light and dark world.
Definition overworld.h:217
absl::Status Load(Rom *rom)
Load all overworld data from ROM.
Definition overworld.cc:36
auto expanded_tile32() const
Definition overworld.h:530
std::vector< gfx::Tile16 > tiles16() const
Definition overworld.h:487
auto expanded_tile16() const
Definition overworld.h:529
auto tiles32_unique() const
Definition overworld.h:488
const std::vector< OverworldEntrance > & entrances() const
Definition overworld.h:502
auto overworld_maps() const
Definition overworld.h:472
constexpr int kAreaGfxIdPtr
Definition overworld.h:117
constexpr int OverworldCustomTileGFXGroupEnabled
constexpr int OverworldCustomAreaSpecificBGEnabled
constexpr int kOverworldSpriteset
Definition overworld.h:110
constexpr int kOverworldScreenSize
Definition overworld.h:143
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:89
constexpr int kOverworldMessagesExpanded
constexpr int OverworldCustomAnimatedGFXArray
constexpr int OverworldCustomMosaicEnabled
constexpr int OverworldCustomTileGFXGroupArray
constexpr int OverworldCustomSubscreenOverlayEnabled
constexpr int OverworldCustomAreaSpecificBGPalette
constexpr int kOverworldMapPaletteIds
Definition overworld.h:107
constexpr int OverworldCustomSubscreenOverlayArray
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
CLI command handlers for test helper tools.