yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
asar_rom_test.cc
Go to the documentation of this file.
1// Must define before any ImGui includes
2#ifndef IMGUI_DEFINE_MATH_OPERATORS
3#define IMGUI_DEFINE_MATH_OPERATORS
4#endif
5
6#include <gtest/gtest.h>
7#include <filesystem>
8#include <fstream>
9
10#include "core/asar_wrapper.h"
11#include "app/rom.h"
12#include "test_utils.h"
13#include "testing.h"
14
15namespace yaze {
16namespace test {
17namespace integration {
18
24 protected:
25 void SetUp() override {
27
28 wrapper_ = std::make_unique<core::AsarWrapper>();
29 ASSERT_OK(wrapper_->Initialize());
30
31 // Create test directory
32 test_dir_ = std::filesystem::temp_directory_path() / "yaze_asar_rom_test";
33 std::filesystem::create_directories(test_dir_);
34
36 }
37
38 void TearDown() override {
39 try {
40 if (std::filesystem::exists(test_dir_)) {
41 std::filesystem::remove_all(test_dir_);
42 }
43 } catch (const std::exception& e) {
44 // Ignore cleanup errors
45 }
46 }
47
49 // Create a simple test patch
50 simple_patch_path_ = test_dir_ / "simple_test.asm";
51 std::ofstream simple_file(simple_patch_path_);
52 simple_file << R"(
53; Simple Asar patch for real ROM testing
54org $008000
55yaze_test_entry:
56 sei ; Disable interrupts
57 clc ; Clear carry
58 xce ; Switch to native mode
59
60 rep #$30 ; 16-bit A and X/Y
61 ldx #$1FFF
62 txs ; Set stack pointer
63
64 ; Test data writing
65 lda #$CAFE
66 sta $7E0000 ; Write test value to RAM
67
68 ; Set a custom value that we can verify
69 lda #$BEEF
70 sta $7E0002
72 sep #$20 ; 8-bit A
73 lda #$42
74 sta $7E0004 ; Another test value
75
76 rep #$20 ; Back to 16-bit A
77 rts
78
79; Subroutine for testing
80yaze_test_subroutine:
81 pha
82 lda #$1337
83 sta $7E0010
84 pla
85 rts
86
87; Data for testing
88yaze_test_data:
89 db "YAZE", $00
90 dw $1234, $5678, $9ABC, $DEF0
91)";
92 simple_file.close();
93
94 // Create a patch that modifies game behavior
95 gameplay_patch_path_ = test_dir_ / "gameplay_test.asm";
96 std::ofstream gameplay_file(gameplay_patch_path_);
97 gameplay_file << R"(
98; Gameplay modification patch for testing
99; This modifies Link's starting health and magic
100
101; Increase Link's maximum health
102org $7EF36C
103 db $A0 ; 160/8 = 20 hearts (was usually $60 = 12 hearts)
104
105; Increase Link's maximum magic
106org $7EF36E
107 db $80 ; Full magic meter
108
109; Custom routine for health restoration
110org $00C000
111yaze_health_restore:
112 sep #$20 ; 8-bit A
113 lda #$A0 ; Full health
114 sta $7EF36C ; Current health
115
116 lda #$80 ; Full magic
117 sta $7EF36E ; Current magic
118
119 rep #$20 ; 16-bit A
120 rtl
121
122; Hook into the game's main loop (example address)
123org $008012
124 jsl yaze_health_restore
125 nop ; Pad if needed
126)";
127 gameplay_file.close();
128
129 // Create a symbol extraction test patch
130 symbols_patch_path_ = test_dir_ / "symbols_test.asm";
131 std::ofstream symbols_file(symbols_patch_path_);
132 symbols_file << R"(
133; Comprehensive symbol test for Asar integration
134
135; Define some constants
136!player_x = $7E0020
137!player_y = $7E0022
138!player_health = $7EF36C
139!player_magic = $7EF36E
140
141; Main code section
142org $008000
143main_routine:
144 jsr init_player
145 jsr game_loop
146 rts
147
148; Player initialization
149init_player:
150 rep #$30 ; 16-bit A and X/Y
151
152 ; Set initial position
153 lda #$0080
154 sta !player_x
155 lda #$0070
156 sta !player_y
157
158 ; Set initial stats
159 sep #$20 ; 8-bit A
160 lda #$A0
161 sta !player_health
162 lda #$80
163 sta !player_magic
164
165 rep #$30 ; Back to 16-bit
166 rts
167
168; Main game loop
169game_loop:
170 jsr update_player
171 jsr update_enemies
172 jsr update_graphics
173 rts
174
175; Player update routine
176update_player:
177 ; Read controller input
178 sep #$20
179 lda $4016 ; Controller 1
180
181 ; Process movement
182 bit #$08 ; Up
183 beq +
184 dec !player_y
185+ bit #$04 ; Down
186 beq +
187 inc !player_y
188+ bit #$02 ; Left
189 beq +
190 dec !player_x
191+ bit #$01 ; Right
192 beq +
193 inc !player_x
194+
195 rep #$20
196 rts
197
198; Enemy update routine
199update_enemies:
200 ; Placeholder for enemy logic
201 rts
202
203; Graphics update routine
204update_graphics:
205 ; Placeholder for graphics updates
206 rts
207
208; Utility functions
209multiply_by_two:
210 asl a
211 rts
212
213divide_by_two:
214 lsr a
215 rts
216
217; Data tables
218enemy_data_table:
219 dw enemy_goomba, enemy_koopa, enemy_shell
220 dw $0000 ; End marker
221
222enemy_goomba:
223 dw $0010, $0020, $0001 ; x, y, type
224
225enemy_koopa:
226 dw $0050, $0030, $0002 ; x, y, type
227
228enemy_shell:
229 dw $0080, $0040, $0003 ; x, y, type
230)";
231 symbols_file.close();
232 }
233
234 std::unique_ptr<core::AsarWrapper> wrapper_;
235 std::filesystem::path test_dir_;
236 std::filesystem::path simple_patch_path_;
237 std::filesystem::path gameplay_patch_path_;
238 std::filesystem::path symbols_patch_path_;
239};
240
241TEST_F(AsarRomIntegrationTest, SimplePatchOnRealRom) {
242 // Make a copy of the ROM for testing
243 std::vector<uint8_t> rom_copy = test_rom_;
244 size_t original_size = rom_copy.size();
245
246 // Apply simple patch
247 auto patch_result =
248 wrapper_->ApplyPatch(simple_patch_path_.string(), rom_copy);
249 ASSERT_OK(patch_result.status());
250
251 const auto& result = patch_result.value();
252 EXPECT_TRUE(result.success)
253 << "Patch failed: " << testing::PrintToString(result.errors);
254
255 // Verify ROM was modified
256 EXPECT_NE(rom_copy, test_rom_); // Should be different
257 EXPECT_GE(rom_copy.size(), original_size); // Size may have grown
258
259 // Check for expected symbols
260 bool found_entry = false;
261 bool found_subroutine = false;
262
263 for (const auto& symbol : result.symbols) {
264 if (symbol.name == "yaze_test_entry") {
265 found_entry = true;
266 EXPECT_EQ(symbol.address, 0x008000);
267 } else if (symbol.name == "yaze_test_subroutine") {
268 found_subroutine = true;
269 }
270 }
271
272 EXPECT_TRUE(found_entry) << "yaze_test_entry symbol not found";
273 EXPECT_TRUE(found_subroutine) << "yaze_test_subroutine symbol not found";
274}
275
276TEST_F(AsarRomIntegrationTest, SymbolExtractionFromRealRom) {
277 // Extract symbols from comprehensive test
278 auto symbols_result = wrapper_->ExtractSymbols(symbols_patch_path_.string());
279 ASSERT_OK(symbols_result.status());
280
281 const auto& symbols = symbols_result.value();
282 EXPECT_GT(symbols.size(), 0);
283
284 // Check for specific symbols we expect
285 std::vector<std::string> expected_symbols = {
286 "main_routine", "init_player", "game_loop", "update_player",
287 "update_enemies", "update_graphics", "multiply_by_two", "divide_by_two"};
288
289 for (const auto& expected_symbol : expected_symbols) {
290 bool found = false;
291 for (const auto& symbol : symbols) {
292 if (symbol.name == expected_symbol) {
293 found = true;
294 EXPECT_GT(symbol.address, 0)
295 << "Symbol " << expected_symbol << " has invalid address";
296 break;
297 }
298 }
299 EXPECT_TRUE(found) << "Expected symbol not found: " << expected_symbol;
300 }
301
302 // Test symbol lookup functionality
303 auto symbol_table = wrapper_->GetSymbolTable();
304 EXPECT_GT(symbol_table.size(), 0);
305
306 auto main_symbol = wrapper_->FindSymbol("main_routine");
307 EXPECT_TRUE(main_symbol.has_value());
308 if (main_symbol) {
309 EXPECT_EQ(main_symbol->name, "main_routine");
310 EXPECT_EQ(main_symbol->address, 0x008000);
311 }
312}
313
314TEST_F(AsarRomIntegrationTest, GameplayModificationPatch) {
315 // Make a copy of the ROM
316 std::vector<uint8_t> rom_copy = test_rom_;
317
318 // Apply gameplay modification patch
319 auto patch_result =
320 wrapper_->ApplyPatch(gameplay_patch_path_.string(), rom_copy);
321 ASSERT_OK(patch_result.status());
322
323 const auto& result = patch_result.value();
324 EXPECT_TRUE(result.success)
325 << "Gameplay patch failed: " << testing::PrintToString(result.errors);
326
327 // Verify specific memory locations were modified
328 // Note: These addresses are based on the patch content
329
330 // Check health modification at 0x7EF36C -> ROM offset would need calculation
331 // For a proper test, we'd need to convert SNES addresses to ROM offsets
332
333 // Check if custom routine was inserted at 0xC000 -> ROM offset 0x18000 (in LoROM)
334 const uint32_t rom_offset = 0x18000; // Bank $00:C000 in LoROM
335 if (rom_offset < rom_copy.size()) {
336 // Check for SEP #$20 instruction (0xE2 0x20)
337 EXPECT_EQ(rom_copy[rom_offset], 0xE2);
338 EXPECT_EQ(rom_copy[rom_offset + 1], 0x20);
339 }
340}
341
342TEST_F(AsarRomIntegrationTest, LargeRomPatchingStability) {
343 // Test with the actual ROM which might be larger
344 std::vector<uint8_t> rom_copy = test_rom_;
345 size_t original_size = rom_copy.size();
346
347 // Apply multiple patches in sequence
348 auto result1 = wrapper_->ApplyPatch(simple_patch_path_.string(), rom_copy);
349 ASSERT_OK(result1.status());
350 EXPECT_TRUE(result1->success);
351
352 // Reset and apply another patch
353 wrapper_->Reset();
354 auto result2 = wrapper_->ApplyPatch(symbols_patch_path_.string(), rom_copy);
355 ASSERT_OK(result2.status());
356 EXPECT_TRUE(result2->success);
357
358 // Verify stability
359 EXPECT_GE(rom_copy.size(), original_size);
360 EXPECT_GT(result2->symbols.size(), 0);
361}
362
363TEST_F(AsarRomIntegrationTest, ErrorHandlingWithRealRom) {
364 // Create an intentionally broken patch
365 auto broken_patch_path = test_dir_ / "broken_test.asm";
366 std::ofstream broken_file(broken_patch_path);
367 broken_file << R"(
368; Broken patch for error testing
369org $008000
370broken_routine:
371 invalid_opcode ; This will cause an error
372 lda unknown_symbol ; This will cause an error
373 sta $FFFFFF ; Invalid address
374)";
375 broken_file.close();
376
377 std::vector<uint8_t> rom_copy = test_rom_;
378 auto patch_result =
379 wrapper_->ApplyPatch(broken_patch_path.string(), rom_copy);
380
381 // Should fail with proper error messages
382 EXPECT_FALSE(patch_result.ok());
383 EXPECT_THAT(patch_result.status().message(),
384 testing::AnyOf(testing::HasSubstr("invalid"),
385 testing::HasSubstr("unknown"),
386 testing::HasSubstr("error")));
387}
388
389TEST_F(AsarRomIntegrationTest, PatchValidationWorkflow) {
390 // Test the complete workflow: validate -> patch -> verify
391
392 // Step 1: Validate assembly
393 auto validation_result =
394 wrapper_->ValidateAssembly(simple_patch_path_.string());
395 EXPECT_OK(validation_result);
396
397 // Step 2: Apply patch
398 std::vector<uint8_t> rom_copy = test_rom_;
399 auto patch_result =
400 wrapper_->ApplyPatch(simple_patch_path_.string(), rom_copy);
401 ASSERT_OK(patch_result.status());
402 EXPECT_TRUE(patch_result->success);
403
404 // Step 3: Verify results
405 EXPECT_GT(patch_result->symbols.size(), 0);
406 EXPECT_GT(patch_result->rom_size, 0);
407
408 // Step 4: Test symbol operations
409 auto entry_symbol = wrapper_->FindSymbol("yaze_test_entry");
410 EXPECT_TRUE(entry_symbol.has_value());
411
412 if (entry_symbol) {
413 auto symbols_at_address =
414 wrapper_->GetSymbolsAtAddress(entry_symbol->address);
415 EXPECT_GT(symbols_at_address.size(), 0);
416 }
417}
418
419} // namespace integration
420} // namespace test
421} // namespace yaze
Test fixture for ROM-dependent tests.
Definition test_utils.h:183
Test class for Asar integration with real ROM files These tests are only run when ROM testing is enab...
std::unique_ptr< core::AsarWrapper > wrapper_
TEST_F(AsarIntegrationTest, FullWorkflowIntegration)
Main namespace for the application.
Definition controller.cc:20
#define EXPECT_OK(expr)
Definition testing.h:10
#define ASSERT_OK(expr)
Definition testing.h:12