feat: Organize and enhance test structure with new tools and integration tests

- Added a new `tools` directory to house various utility tools, including the `overworld_golden_data_extractor`, `extract_vanilla_values`, and `rom_patch_utility`.
- Introduced comprehensive integration tests for dungeon and overworld functionalities, ensuring compatibility with existing ROM data.
- Refactored existing test files to improve organization and maintainability, moving deprecated tests to a dedicated directory.
- Updated CMake configuration to include new tools and tests, enhancing the build process for development and CI environments.
- Improved test coverage for dungeon object rendering and room integration, validating core functionalities against expected behaviors.
This commit is contained in:
scawful
2025-10-04 12:21:18 -04:00
parent 4b61b213c0
commit 38ece34894
16 changed files with 1241 additions and 163 deletions

View File

@@ -43,7 +43,7 @@ if(YAZE_BUILD_TESTS AND NOT YAZE_BUILD_TESTS STREQUAL "OFF")
unit/zelda3/test_dungeon_objects.cc
unit/zelda3/dungeon_component_unit_test.cc
zelda3/dungeon/room_object_encoding_test.cc
zelda3/dungeon/room_integration_test.cc
integration/zelda3/room_integration_test.cc
zelda3/dungeon/room_manipulation_test.cc
# CLI Services (for catalog serialization tests)
@@ -62,12 +62,17 @@ if(YAZE_BUILD_TESTS AND NOT YAZE_BUILD_TESTS STREQUAL "OFF")
e2e/rom_dependent/e2e_rom_test.cc
e2e/zscustomoverworld/zscustomoverworld_upgrade_test.cc
# Legacy Integration Tests (to be migrated)
unit/zelda3/comprehensive_integration_test.cc
unit/zelda3/overworld_integration_test.cc
unit/zelda3/dungeon_integration_test.cc
unit/zelda3/dungeon_editor_system_integration_test.cc
unit/zelda3/dungeon_object_renderer_integration_test.cc
# Deprecated Tests (formerly legacy)
deprecated/comprehensive_integration_test.cc
deprecated/dungeon_integration_test.cc
# Integration Tests (Zelda3)
integration/zelda3/overworld_integration_test.cc
integration/zelda3/dungeon_editor_system_integration_test.cc
integration/zelda3/dungeon_object_renderer_integration_test.cc
integration/zelda3/room_integration_test.cc
# Mock/Unit Tests for Zelda3
unit/zelda3/dungeon_object_renderer_mock_test.cc
unit/zelda3/dungeon_object_rendering_tests.cc
unit/zelda3/dungeon_room_test.cc
@@ -100,7 +105,6 @@ if(YAZE_BUILD_TESTS AND NOT YAZE_BUILD_TESTS STREQUAL "OFF")
unit/zelda3/test_dungeon_objects.cc
unit/zelda3/dungeon_component_unit_test.cc
zelda3/dungeon/room_object_encoding_test.cc
zelda3/dungeon/room_integration_test.cc
zelda3/dungeon/room_manipulation_test.cc
# CLI Services (for catalog serialization tests)
@@ -121,110 +125,27 @@ if(YAZE_BUILD_TESTS AND NOT YAZE_BUILD_TESTS STREQUAL "OFF")
e2e/rom_dependent/e2e_rom_test.cc
e2e/zscustomoverworld/zscustomoverworld_upgrade_test.cc
# Legacy Integration Tests (to be migrated)
unit/zelda3/comprehensive_integration_test.cc
unit/zelda3/overworld_integration_test.cc
unit/zelda3/dungeon_integration_test.cc
unit/zelda3/dungeon_editor_system_integration_test.cc
unit/zelda3/dungeon_object_renderer_integration_test.cc
# Deprecated Tests (formerly legacy)
deprecated/comprehensive_integration_test.cc
deprecated/dungeon_integration_test.cc
# Integration Tests (Zelda3)
integration/zelda3/overworld_integration_test.cc
integration/zelda3/dungeon_editor_system_integration_test.cc
integration/zelda3/dungeon_object_renderer_integration_test.cc
integration/zelda3/room_integration_test.cc
# Mock/Unit Tests for Zelda3
unit/zelda3/dungeon_object_renderer_mock_test.cc
unit/zelda3/dungeon_object_rendering_tests.cc
unit/zelda3/dungeon_room_test.cc
# Benchmarks
benchmarks/gfx_optimization_benchmarks.cc
)
endif()
# Add vanilla value extraction utility (only for local development with ROM access)
# IMPORTANT: Do not build in CI/release - this is a development-only utility
if(NOT YAZE_MINIMAL_BUILD AND YAZE_ENABLE_ROM_TESTS AND NOT DEFINED ENV{GITHUB_ACTIONS})
add_executable(
extract_vanilla_values
unit/zelda3/extract_vanilla_values.cc
${YAZE_SRC_FILES}
)
target_include_directories(
extract_vanilla_values PUBLIC
${CMAKE_SOURCE_DIR}/src/app/
${CMAKE_SOURCE_DIR}/src/lib/
${CMAKE_SOURCE_DIR}/incl/
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${CMAKE_SOURCE_DIR}/src/lib/asar/src
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c
${SDL2_INCLUDE_DIR}
${PNG_INCLUDE_DIRS}
${PROJECT_BINARY_DIR}
)
target_link_libraries(
extract_vanilla_values
yaze_core
${SDL_TARGETS}
asar-static
${ABSL_TARGETS}
${PNG_LIBRARIES}
${OPENGL_LIBRARIES}
${CMAKE_DL_LIBS}
ImGui
)
# Windows stack size configuration for extract_vanilla_values
if(WIN32)
if(MSVC)
target_link_options(extract_vanilla_values PRIVATE /STACK:16777216)
elseif(MINGW OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_link_options(extract_vanilla_values PRIVATE -Wl,--stack,16777216)
else()
target_link_options(extract_vanilla_values PRIVATE -Wl,--stack,16777216)
endif()
endif()
# Add rom_patch_utility as a separate executable
add_executable(
rom_patch_utility
unit/zelda3/rom_patch_utility.cc
${YAZE_SRC_FILES}
)
target_include_directories(
rom_patch_utility PUBLIC
${CMAKE_SOURCE_DIR}/src/app/
${CMAKE_SOURCE_DIR}/src/lib/
${CMAKE_SOURCE_DIR}/incl/
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${CMAKE_SOURCE_DIR}/src/lib/asar/src
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c
${SDL2_INCLUDE_DIR}
${PNG_INCLUDE_DIRS}
${PROJECT_BINARY_DIR}
)
target_link_libraries(
rom_patch_utility
yaze_core
${SDL_TARGETS}
asar-static
${ABSL_TARGETS}
${PNG_LIBRARIES}
${OPENGL_LIBRARIES}
${CMAKE_DL_LIBS}
ImGui
)
# Windows stack size configuration for rom_patch_utility
if(WIN32)
if(MSVC)
target_link_options(rom_patch_utility PRIVATE /STACK:16777216)
elseif(MINGW OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_link_options(rom_patch_utility PRIVATE -Wl,--stack,16777216)
else()
target_link_options(rom_patch_utility PRIVATE -Wl,--stack,16777216)
endif()
endif()
endif()
# Configure test executable only when tests are enabled
target_include_directories(
@@ -386,60 +307,81 @@ source_group("Tests\\Framework" FILES
testing.h
yaze_test.cc
yaze_test_ci.cc
test_editor.cc
test_editor.h
)
# Unit Tests
source_group("Tests\\Unit" FILES
unit/test_asar_wrapper.cc
unit/test_rom_loading.cc
unit/test_snes_tiles.cc
unit/test_palettes.cc
unit/test_hex_utils.cc
unit/test_flag_utils.cc
unit/test_bps_utils.cc
unit/test_color_conversion.cc
unit/test_tile_compression.cc
unit/test_memory_management.cc
unit/test_project_structure.cc
unit/test_editor_basic.cc
unit/test_dungeon_data.cc
unit/test_overworld_data.cc
unit/test_sprite_data.cc
unit/test_music_data.cc
unit/test_graphics_rendering.cc
unit/test_gui_components.cc
unit/test_emulator_core.cc
unit/test_cpu_instructions.cc
unit/test_ppu_rendering.cc
unit/test_audio_processing.cc
unit/test_compression_algorithms.cc
unit/test_hex_editor.cc
unit/core/asar_wrapper_test.cc
unit/core/hex_test.cc
unit/cli/resource_catalog_test.cc
unit/rom/rom_test.cc
unit/gfx/snes_tile_test.cc
unit/gfx/compression_test.cc
unit/gfx/snes_palette_test.cc
unit/zelda3/message_test.cc
unit/zelda3/overworld_test.cc
unit/zelda3/object_parser_test.cc
unit/zelda3/object_parser_structs_test.cc
unit/zelda3/sprite_builder_test.cc
unit/zelda3/sprite_position_test.cc
unit/zelda3/test_dungeon_objects.cc
unit/zelda3/dungeon_component_unit_test.cc
zelda3/dungeon/room_object_encoding_test.cc
zelda3/dungeon/room_manipulation_test.cc
unit/zelda3/dungeon_object_renderer_mock_test.cc
unit/zelda3/dungeon_object_rendering_tests.cc
unit/zelda3/dungeon_room_test.cc
)
# Integration Tests
source_group("Tests\\Integration" FILES
integration/test_editor_integration.cc
integration/test_rom_integration.cc
integration/test_project_workflow.cc
integration/test_asar_integration.cc
integration/test_graphics_pipeline.cc
integration/test_emulator_integration.cc
integration/asar_integration_test.cc
integration/asar_rom_test.cc
integration/dungeon_editor_test.cc
integration/dungeon_editor_test.h
integration/editor/tile16_editor_test.cc
integration/editor/editor_integration_test.cc
integration/editor/editor_integration_test.h
)
# Integration Tests (Zelda3)
source_group("Tests\\Integration\\Zelda3" FILES
integration/zelda3/overworld_integration_test.cc
integration/zelda3/dungeon_editor_system_integration_test.cc
integration/zelda3/dungeon_object_renderer_integration_test.cc
integration/zelda3/room_integration_test.cc
)
# End-to-End Tests
source_group("Tests\\E2E" FILES
e2e/test_full_workflow.cc
e2e/test_user_scenarios.cc
e2e/canvas_selection_test.cc
e2e/framework_smoke_test.cc
e2e/rom_dependent/e2e_rom_test.cc
e2e/zscustomoverworld/zscustomoverworld_upgrade_test.cc
)
# Deprecated Tests
source_group("Tests\\Deprecated" FILES
deprecated/comprehensive_integration_test.cc
deprecated/dungeon_integration_test.cc
)
# Benchmarks
source_group("Tests\\Benchmarks" FILES
benchmarks/gfx_optimization_benchmarks.cc
)
# Test Utilities and Mocks
source_group("Tests\\Utilities" FILES
test_utils.h
test_utils.cc
mocks/mock_rom.h
mocks/mock_editor.h
mocks/mock_memory.h
)
# Test Assets
source_group("Tests\\Assets" FILES
assets/test_rom.asm
assets/test_patch.asm
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +0,0 @@
# Add golden data extractor tool
add_executable(overworld_golden_data_extractor
overworld_golden_data_extractor.cc
)
target_link_libraries(overworld_golden_data_extractor
yaze_core
${CMAKE_THREAD_LIBS_INIT}
)
# Add vanilla values extractor tool
add_executable(extract_vanilla_values
extract_vanilla_values.cc
)
target_link_libraries(extract_vanilla_values
yaze_core
${CMAKE_THREAD_LIBS_INIT}
)
# Install tools to bin directory
install(TARGETS overworld_golden_data_extractor extract_vanilla_values
DESTINATION bin
)

View File

@@ -1,96 +0,0 @@
#include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>
#include "app/rom.h"
#include "app/zelda3/overworld/overworld_map.h"
#include "app/zelda3/overworld/overworld.h"
using namespace yaze::zelda3;
using namespace yaze;
int main() {
// Load the vanilla ROM
Rom rom;
if (!rom.LoadFromFile("zelda3.sfc").ok()) {
std::cerr << "Failed to load ROM file" << std::endl;
return 1;
}
std::cout << "// Vanilla ROM values extracted from zelda3.sfc" << std::endl;
std::cout << "// Generated on " << __DATE__ << " " << __TIME__ << std::endl;
std::cout << std::endl;
// Extract ASM version
uint8_t asm_version = rom[OverworldCustomASMHasBeenApplied];
std::cout << "constexpr uint8_t kVanillaASMVersion = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)asm_version << ";" << std::endl;
std::cout << std::endl;
// Extract area graphics for first 10 maps
std::cout << "// Area graphics for first 10 maps" << std::endl;
for (int i = 0; i < 10; i++) {
uint8_t area_gfx = rom[kAreaGfxIdPtr + i];
std::cout << "constexpr uint8_t kVanillaAreaGraphics" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)area_gfx << ";" << std::endl;
}
std::cout << std::endl;
// Extract area palettes for first 10 maps
std::cout << "// Area palettes for first 10 maps" << std::endl;
for (int i = 0; i < 10; i++) {
uint8_t area_pal = rom[kOverworldMapPaletteIds + i];
std::cout << "constexpr uint8_t kVanillaAreaPalette" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)area_pal << ";" << std::endl;
}
std::cout << std::endl;
// Extract message IDs for first 10 maps
std::cout << "// Message IDs for first 10 maps" << std::endl;
for (int i = 0; i < 10; i++) {
uint16_t message_id = rom[kOverworldMessageIds + (i * 2)] | (rom[kOverworldMessageIds + (i * 2) + 1] << 8);
std::cout << "constexpr uint16_t kVanillaMessageId" << i << " = 0x" << std::hex << std::setw(4) << std::setfill('0') << message_id << ";" << std::endl;
}
std::cout << std::endl;
// Extract screen sizes for first 10 maps
std::cout << "// Screen sizes for first 10 maps" << std::endl;
for (int i = 0; i < 10; i++) {
uint8_t screen_size = rom[kOverworldScreenSize + i];
std::cout << "constexpr uint8_t kVanillaScreenSize" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)screen_size << ";" << std::endl;
}
std::cout << std::endl;
// Extract sprite sets for first 10 maps
std::cout << "// Sprite sets for first 10 maps" << std::endl;
for (int i = 0; i < 10; i++) {
uint8_t sprite_set = rom[kOverworldSpriteset + i];
std::cout << "constexpr uint8_t kVanillaSpriteSet" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)sprite_set << ";" << std::endl;
}
std::cout << std::endl;
// Extract sprite palettes for first 10 maps
std::cout << "// Sprite palettes for first 10 maps" << std::endl;
for (int i = 0; i < 10; i++) {
uint8_t sprite_pal = rom[kOverworldSpritePaletteIds + i];
std::cout << "constexpr uint8_t kVanillaSpritePalette" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)sprite_pal << ";" << std::endl;
}
std::cout << std::endl;
// Extract music for first 10 maps
std::cout << "// Music for first 10 maps" << std::endl;
for (int i = 0; i < 10; i++) {
uint8_t music = rom[kOverworldMusicBeginning + i];
std::cout << "constexpr uint8_t kVanillaMusic" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)music << ";" << std::endl;
}
std::cout << std::endl;
// Extract some special world values
std::cout << "// Special world graphics and palettes" << std::endl;
for (int i = 0; i < 5; i++) {
uint8_t special_gfx = rom[kOverworldSpecialGfxGroup + i];
uint8_t special_pal = rom[kOverworldSpecialPalGroup + i];
std::cout << "constexpr uint8_t kVanillaSpecialGfx" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)special_gfx << ";" << std::endl;
std::cout << "constexpr uint8_t kVanillaSpecialPal" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)special_pal << ";" << std::endl;
}
return 0;
}

View File

@@ -1,553 +0,0 @@
#include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <filesystem>
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "app/zelda3/overworld/overworld_map.h"
using namespace yaze::zelda3;
using namespace yaze;
/**
* @brief Comprehensive ROM value extraction tool for golden data testing
*
* This tool extracts all overworld-related values from a ROM to create
* "golden" reference data for before/after edit validation and comprehensive
* E2E testing. It supports both vanilla and ZSCustomOverworld ROMs.
*/
class OverworldGoldenDataExtractor {
public:
explicit OverworldGoldenDataExtractor(const std::string& rom_path)
: rom_path_(rom_path) {}
absl::Status ExtractAllData(const std::string& output_path) {
// Load ROM
Rom rom;
RETURN_IF_ERROR(rom.LoadFromFile(rom_path_));
// Load overworld data
Overworld overworld(&rom);
RETURN_IF_ERROR(overworld.Load(&rom));
std::ofstream out_file(output_path);
if (!out_file.is_open()) {
return absl::InternalError("Failed to open output file: " + output_path);
}
// Write header
WriteHeader(out_file);
// Extract basic ROM info
WriteBasicROMInfo(out_file, rom);
// Extract ASM version info
WriteASMVersionInfo(out_file, rom);
// Extract overworld maps data
WriteOverworldMapsData(out_file, overworld);
// Extract tile data
WriteTileData(out_file, overworld);
// Extract entrance/hole/exit data
WriteEntranceData(out_file, overworld);
WriteHoleData(out_file, overworld);
WriteExitData(out_file, overworld);
// Extract item data
WriteItemData(out_file, overworld);
// Extract sprite data
WriteSpriteData(out_file, overworld);
// Extract map tiles (compressed data)
WriteMapTilesData(out_file, overworld);
// Extract palette data
WritePaletteData(out_file, rom);
// Extract music data
WriteMusicData(out_file, rom);
// Extract overlay data
WriteOverlayData(out_file, rom);
// Write footer
WriteFooter(out_file);
return absl::OkStatus();
}
private:
void WriteHeader(std::ofstream& out) {
out << "// =============================================================================" << std::endl;
out << "// YAZE Overworld Golden Data - Generated from: " << rom_path_ << std::endl;
out << "// Generated on: " << __DATE__ << " " << __TIME__ << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
out << "#pragma once" << std::endl;
out << std::endl;
out << "#include <cstdint>" << std::endl;
out << "#include <array>" << std::endl;
out << "#include <vector>" << std::endl;
out << "#include \"app/zelda3/overworld/overworld_map.h\"" << std::endl;
out << std::endl;
out << "namespace yaze {" << std::endl;
out << "namespace test {" << std::endl;
out << std::endl;
}
void WriteFooter(std::ofstream& out) {
out << std::endl;
out << "} // namespace test" << std::endl;
out << "} // namespace yaze" << std::endl;
}
void WriteBasicROMInfo(std::ofstream& out, Rom& rom) {
out << "// =============================================================================" << std::endl;
out << "// Basic ROM Information" << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
out << "constexpr std::string_view kGoldenROMTitle = \"" << rom.title() << "\";" << std::endl;
out << "constexpr size_t kGoldenROMSize = " << rom.size() << ";" << std::endl;
out << std::endl;
// ROM header validation
auto header_checksum = rom.ReadWord(0x7FDC);
auto header_checksum_complement = rom.ReadWord(0x7FDE);
if (header_checksum.ok() && header_checksum_complement.ok()) {
out << "constexpr uint16_t kGoldenHeaderChecksum = 0x"
<< std::hex << std::setw(4) << std::setfill('0')
<< *header_checksum << ";" << std::endl;
out << "constexpr uint16_t kGoldenHeaderChecksumComplement = 0x"
<< std::hex << std::setw(4) << std::setfill('0')
<< *header_checksum_complement << ";" << std::endl;
out << std::endl;
}
}
void WriteASMVersionInfo(std::ofstream& out, Rom& rom) {
out << "// =============================================================================" << std::endl;
out << "// ASM Version Information" << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
auto asm_version = rom.ReadByte(0x140145);
if (asm_version.ok()) {
out << "constexpr uint8_t kGoldenASMVersion = 0x"
<< std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(*asm_version) << ";" << std::endl;
if (*asm_version == 0xFF) {
out << "constexpr bool kGoldenIsVanillaROM = true;" << std::endl;
out << "constexpr bool kGoldenHasZSCustomOverworld = false;" << std::endl;
} else {
out << "constexpr bool kGoldenIsVanillaROM = false;" << std::endl;
out << "constexpr bool kGoldenHasZSCustomOverworld = true;" << std::endl;
out << "constexpr uint8_t kGoldenZSCustomOverworldVersion = "
<< static_cast<int>(*asm_version) << ";" << std::endl;
}
out << std::endl;
}
// Feature flags for v3
if (asm_version.ok() && *asm_version >= 0x03) {
out << "// v3 Feature Flags" << std::endl;
auto main_palettes = rom.ReadByte(0x140146);
auto area_bg = rom.ReadByte(0x140147);
auto subscreen_overlay = rom.ReadByte(0x140148);
auto animated_gfx = rom.ReadByte(0x140149);
auto custom_tiles = rom.ReadByte(0x14014A);
auto mosaic = rom.ReadByte(0x14014B);
if (main_palettes.ok()) {
out << "constexpr bool kGoldenEnableMainPalettes = "
<< (*main_palettes != 0 ? "true" : "false") << ";" << std::endl;
}
if (area_bg.ok()) {
out << "constexpr bool kGoldenEnableAreaSpecificBG = "
<< (*area_bg != 0 ? "true" : "false") << ";" << std::endl;
}
if (subscreen_overlay.ok()) {
out << "constexpr bool kGoldenEnableSubscreenOverlay = "
<< (*subscreen_overlay != 0 ? "true" : "false") << ";" << std::endl;
}
if (animated_gfx.ok()) {
out << "constexpr bool kGoldenEnableAnimatedGFX = "
<< (*animated_gfx != 0 ? "true" : "false") << ";" << std::endl;
}
if (custom_tiles.ok()) {
out << "constexpr bool kGoldenEnableCustomTiles = "
<< (*custom_tiles != 0 ? "true" : "false") << ";" << std::endl;
}
if (mosaic.ok()) {
out << "constexpr bool kGoldenEnableMosaic = "
<< (*mosaic != 0 ? "true" : "false") << ";" << std::endl;
}
out << std::endl;
}
}
void WriteOverworldMapsData(std::ofstream& out, Overworld& overworld) {
out << "// =============================================================================" << std::endl;
out << "// Overworld Maps Data" << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
const auto& maps = overworld.overworld_maps();
out << "constexpr size_t kGoldenNumOverworldMaps = " << maps.size() << ";" << std::endl;
out << std::endl;
// Extract map properties for first 20 maps (to keep file size manageable)
out << "// Map properties for first 20 maps" << std::endl;
out << "constexpr std::array<uint8_t, 20> kGoldenMapAreaGraphics = {{" << std::endl;
for (int i = 0; i < std::min(20, static_cast<int>(maps.size())); i++) {
out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(maps[i].area_graphics());
if (i < 19) out << ",";
out << " // Map " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
out << "constexpr std::array<uint8_t, 20> kGoldenMapMainPalettes = {{" << std::endl;
for (int i = 0; i < std::min(20, static_cast<int>(maps.size())); i++) {
out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(maps[i].main_palette());
if (i < 19) out << ",";
out << " // Map " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
out << "constexpr std::array<AreaSizeEnum, 20> kGoldenMapAreaSizes = {{" << std::endl;
for (int i = 0; i < std::min(20, static_cast<int>(maps.size())); i++) {
out << " AreaSizeEnum::";
switch (maps[i].area_size()) {
case AreaSizeEnum::SmallArea: out << "SmallArea"; break;
case AreaSizeEnum::LargeArea: out << "LargeArea"; break;
case AreaSizeEnum::WideArea: out << "WideArea"; break;
case AreaSizeEnum::TallArea: out << "TallArea"; break;
}
if (i < 19) out << ",";
out << " // Map " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
}
void WriteTileData(std::ofstream& out, Overworld& overworld) {
out << "// =============================================================================" << std::endl;
out << "// Tile Data Information" << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
out << "constexpr bool kGoldenExpandedTile16 = "
<< (overworld.expanded_tile16() ? "true" : "false") << ";" << std::endl;
out << "constexpr bool kGoldenExpandedTile32 = "
<< (overworld.expanded_tile32() ? "true" : "false") << ";" << std::endl;
out << std::endl;
const auto& tiles16 = overworld.tiles16();
const auto& tiles32 = overworld.tiles32_unique();
out << "constexpr size_t kGoldenNumTiles16 = " << tiles16.size() << ";" << std::endl;
out << "constexpr size_t kGoldenNumTiles32 = " << tiles32.size() << ";" << std::endl;
out << std::endl;
// Sample some tile data for validation
out << "// Sample Tile16 data (first 10 tiles)" << std::endl;
out << "constexpr std::array<uint32_t, 10> kGoldenTile16Sample = {{" << std::endl;
for (int i = 0; i < std::min(10, static_cast<int>(tiles16.size())); i++) {
// Extract tile data as uint32_t for sample using TileInfo values
const auto& tile16 = tiles16[i];
uint32_t sample = tile16.tile0_.id_ | (tile16.tile1_.id_ << 8) |
(tile16.tile2_.id_ << 16) | (tile16.tile3_.id_ << 24);
out << " 0x" << std::hex << std::setw(8) << std::setfill('0') << sample;
if (i < 9) out << ",";
out << " // Tile16 " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
}
void WriteEntranceData(std::ofstream& out, Overworld& overworld) {
out << "// =============================================================================" << std::endl;
out << "// Entrance Data" << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
const auto& entrances = overworld.entrances();
out << "constexpr size_t kGoldenNumEntrances = " << entrances.size() << ";" << std::endl;
out << std::endl;
// Sample entrance data for validation
out << "// Sample entrance data (first 10 entrances)" << std::endl;
out << "constexpr std::array<uint16_t, 10> kGoldenEntranceMapPos = {{" << std::endl;
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
out << " 0x" << std::hex << std::setw(4) << std::setfill('0')
<< entrances[i].map_pos_;
if (i < 9) out << ",";
out << " // Entrance " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
out << "constexpr std::array<uint16_t, 10> kGoldenEntranceMapId = {{" << std::endl;
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
out << " 0x" << std::hex << std::setw(4) << std::setfill('0')
<< entrances[i].map_id_;
if (i < 9) out << ",";
out << " // Entrance " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
out << "constexpr std::array<int, 10> kGoldenEntranceX = {{" << std::endl;
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
out << " " << std::dec << entrances[i].x_;
if (i < 9) out << ",";
out << " // Entrance " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
out << "constexpr std::array<int, 10> kGoldenEntranceY = {{" << std::endl;
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
out << " " << std::dec << entrances[i].y_;
if (i < 9) out << ",";
out << " // Entrance " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
}
void WriteHoleData(std::ofstream& out, Overworld& overworld) {
out << "// =============================================================================" << std::endl;
out << "// Hole Data" << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
const auto& holes = overworld.holes();
out << "constexpr size_t kGoldenNumHoles = " << holes.size() << ";" << std::endl;
out << std::endl;
// Sample hole data for validation
out << "// Sample hole data (first 5 holes)" << std::endl;
out << "constexpr std::array<uint16_t, 5> kGoldenHoleMapPos = {{" << std::endl;
for (int i = 0; i < std::min(5, static_cast<int>(holes.size())); i++) {
out << " 0x" << std::hex << std::setw(4) << std::setfill('0')
<< holes[i].map_pos_;
if (i < 4) out << ",";
out << " // Hole " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
}
void WriteExitData(std::ofstream& out, Overworld& overworld) {
out << "// =============================================================================" << std::endl;
out << "// Exit Data" << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
const auto& exits = overworld.exits();
out << "constexpr size_t kGoldenNumExits = " << exits->size() << ";" << std::endl;
out << std::endl;
// Sample exit data for validation
out << "// Sample exit data (first 10 exits)" << std::endl;
out << "constexpr std::array<uint16_t, 10> kGoldenExitRoomId = {{" << std::endl;
for (int i = 0; i < std::min(10, static_cast<int>(exits->size())); i++) {
out << " 0x" << std::hex << std::setw(4) << std::setfill('0')
<< (*exits)[i].room_id_;
if (i < 9) out << ",";
out << " // Exit " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
}
void WriteItemData(std::ofstream& out, Overworld& overworld) {
out << "// =============================================================================" << std::endl;
out << "// Item Data" << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
const auto& items = overworld.all_items();
out << "constexpr size_t kGoldenNumItems = " << items.size() << ";" << std::endl;
out << std::endl;
// Sample item data for validation
if (!items.empty()) {
out << "// Sample item data (first 10 items)" << std::endl;
out << "constexpr std::array<uint8_t, 10> kGoldenItemIds = {{" << std::endl;
for (int i = 0; i < std::min(10, static_cast<int>(items.size())); i++) {
out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(items[i].id_);
if (i < 9) out << ",";
out << " // Item " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
}
}
void WriteSpriteData(std::ofstream& out, Overworld& overworld) {
out << "// =============================================================================" << std::endl;
out << "// Sprite Data" << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
const auto& sprites = overworld.all_sprites();
out << "constexpr size_t kGoldenNumSpriteStates = " << sprites.size() << ";" << std::endl;
out << std::endl;
// Sample sprite data for validation
out << "// Sample sprite data (first 5 sprites from each state)" << std::endl;
for (int state = 0; state < std::min(3, static_cast<int>(sprites.size())); state++) {
out << "constexpr size_t kGoldenNumSpritesState" << state << " = "
<< sprites[state].size() << ";" << std::endl;
}
out << std::endl;
}
void WriteMapTilesData(std::ofstream& out, Overworld& overworld) {
out << "// =============================================================================" << std::endl;
out << "// Map Tiles Data" << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
const auto& map_tiles = overworld.map_tiles();
out << "// Map tile dimensions" << std::endl;
out << "constexpr size_t kGoldenMapTileWidth = " << map_tiles.light_world[0].size() << ";" << std::endl;
out << "constexpr size_t kGoldenMapTileHeight = " << map_tiles.light_world.size() << ";" << std::endl;
out << std::endl;
// Sample map tile data for validation
out << "// Sample map tile data (top-left 10x10 corner of Light World)" << std::endl;
out << "constexpr std::array<std::array<uint16_t, 10>, 10> kGoldenMapTilesSample = {{" << std::endl;
for (int row = 0; row < 10; row++) {
out << " {{";
for (int col = 0; col < 10; col++) {
out << "0x" << std::hex << std::setw(4) << std::setfill('0')
<< map_tiles.light_world[row][col];
if (col < 9) out << ", ";
}
out << "}}";
if (row < 9) out << ",";
out << " // Row " << row << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
}
void WritePaletteData(std::ofstream& out, Rom& rom) {
out << "// =============================================================================" << std::endl;
out << "// Palette Data" << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
// Sample palette data from ROM
out << "// Sample palette data (first 10 bytes from overworld palette table)" << std::endl;
out << "constexpr std::array<uint8_t, 10> kGoldenPaletteSample = {{" << std::endl;
for (int i = 0; i < 10; i++) {
auto palette_byte = rom.ReadByte(0x7D1C + i); // overworldMapPalette
if (palette_byte.ok()) {
out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(*palette_byte);
} else {
out << " 0x00";
}
if (i < 9) out << ",";
out << " // Palette " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
}
void WriteMusicData(std::ofstream& out, Rom& rom) {
out << "// =============================================================================" << std::endl;
out << "// Music Data" << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
// Sample music data from ROM
out << "// Sample music data (first 10 bytes from overworld music table)" << std::endl;
out << "constexpr std::array<uint8_t, 10> kGoldenMusicSample = {{" << std::endl;
for (int i = 0; i < 10; i++) {
auto music_byte = rom.ReadByte(0x14303 + i); // overworldMusicBegining
if (music_byte.ok()) {
out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(*music_byte);
} else {
out << " 0x00";
}
if (i < 9) out << ",";
out << " // Music " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
}
void WriteOverlayData(std::ofstream& out, Rom& rom) {
out << "// =============================================================================" << std::endl;
out << "// Overlay Data" << std::endl;
out << "// =============================================================================" << std::endl;
out << std::endl;
// Sample overlay data from ROM
out << "// Sample overlay data (first 10 bytes from overlay pointers)" << std::endl;
out << "constexpr std::array<uint8_t, 10> kGoldenOverlaySample = {{" << std::endl;
for (int i = 0; i < 10; i++) {
auto overlay_byte = rom.ReadByte(0x77664 + i); // overlayPointers
if (overlay_byte.ok()) {
out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(*overlay_byte);
} else {
out << " 0x00";
}
if (i < 9) out << ",";
out << " // Overlay " << i << std::endl;
}
out << "}};" << std::endl;
out << std::endl;
}
std::string rom_path_;
};
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <rom_path> <output_path>" << std::endl;
std::cerr << "Example: " << argv[0] << " zelda3.sfc golden_data.h" << std::endl;
return 1;
}
std::string rom_path = argv[1];
std::string output_path = argv[2];
if (!std::filesystem::exists(rom_path)) {
std::cerr << "Error: ROM file not found: " << rom_path << std::endl;
return 1;
}
OverworldGoldenDataExtractor extractor(rom_path);
auto status = extractor.ExtractAllData(output_path);
if (status.ok()) {
std::cout << "Successfully extracted golden data from " << rom_path
<< " to " << output_path << std::endl;
return 0;
} else {
std::cerr << "Error extracting golden data: " << status.message() << std::endl;
return 1;
}
}

View File

@@ -1,108 +0,0 @@
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <vector>
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "app/zelda3/overworld/overworld_map.h"
using namespace yaze::zelda3;
using namespace yaze;
class ROMPatchUtility {
public:
static absl::Status CreateV3PatchedROM(const std::string& input_rom_path,
const std::string& output_rom_path) {
// Load the vanilla ROM
Rom rom;
RETURN_IF_ERROR(rom.LoadFromFile(input_rom_path));
// Apply ZSCustomOverworld v3 settings
RETURN_IF_ERROR(ApplyV3Patch(rom));
// Save the patched ROM
return rom.SaveToFile(Rom::SaveSettings{.filename = output_rom_path});
}
private:
static absl::Status ApplyV3Patch(Rom& rom) {
// Set ASM version to v3
rom.WriteByte(OverworldCustomASMHasBeenApplied, 0x03);
// Enable v3 features
rom.WriteByte(OverworldCustomAreaSpecificBGEnabled, 0x01);
rom.WriteByte(OverworldCustomSubscreenOverlayEnabled, 0x01);
rom.WriteByte(OverworldCustomAnimatedGFXEnabled, 0x01);
rom.WriteByte(OverworldCustomTileGFXGroupEnabled, 0x01);
rom.WriteByte(OverworldCustomMosaicEnabled, 0x01);
rom.WriteByte(OverworldCustomMainPaletteEnabled, 0x01);
// Apply v3 settings to first 10 maps for testing
for (int i = 0; i < 10; i++) {
// Set area sizes (mix of different sizes)
AreaSizeEnum area_size = static_cast<AreaSizeEnum>(i % 4);
rom.WriteByte(kOverworldScreenSize + i, static_cast<uint8_t>(area_size));
// Set main palettes
rom.WriteByte(OverworldCustomMainPaletteArray + i, i % 8);
// Set area-specific background colors
uint16_t bg_color = 0x0000 + (i * 0x1000);
rom.WriteByte(OverworldCustomAreaSpecificBGPalette + (i * 2),
bg_color & 0xFF);
rom.WriteByte(OverworldCustomAreaSpecificBGPalette + (i * 2) + 1,
(bg_color >> 8) & 0xFF);
// Set subscreen overlays
uint16_t overlay = 0x0090 + i;
rom.WriteByte(OverworldCustomSubscreenOverlayArray + (i * 2),
overlay & 0xFF);
rom.WriteByte(OverworldCustomSubscreenOverlayArray + (i * 2) + 1,
(overlay >> 8) & 0xFF);
// Set animated GFX
rom.WriteByte(OverworldCustomAnimatedGFXArray + i, 0x50 + i);
// Set custom tile GFX groups (8 bytes per map)
for (int j = 0; j < 8; j++) {
rom.WriteByte(OverworldCustomTileGFXGroupArray + (i * 8) + j,
0x20 + j + i);
}
// Set mosaic settings
rom.WriteByte(OverworldCustomMosaicArray + i, i % 16);
// Set expanded message IDs
uint16_t message_id = 0x1000 + i;
rom.WriteByte(kOverworldMessagesExpanded + (i * 2), message_id & 0xFF);
rom.WriteByte(kOverworldMessagesExpanded + (i * 2) + 1,
(message_id >> 8) & 0xFF);
}
return absl::OkStatus();
}
};
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <input_rom> <output_rom>"
<< std::endl;
return 1;
}
std::string input_rom = argv[1];
std::string output_rom = argv[2];
auto status = ROMPatchUtility::CreateV3PatchedROM(input_rom, output_rom);
if (!status.ok()) {
std::cerr << "Failed to create patched ROM: " << status.message()
<< std::endl;
return 1;
}
std::cout << "Successfully created v3 patched ROM: " << output_rom
<< std::endl;
return 0;
}