feat: Enhance gRPC configuration and add dungeon test harness
- Updated gRPC configuration to use version 1.67.1, improving compatibility with modern compilers and fixing MSVC template issues. - Enhanced warning messages for missing vcpkg gRPC installation, providing clearer instructions for faster builds. - Introduced a new dungeon test harness tool to capture and dump the state of WRAM and CPU/PPU registers, aiding in emulator testing and development. - Organized source groups in CMake for better structure and clarity in the input and UI systems.
This commit is contained in:
@@ -27,11 +27,25 @@ set(CMAKE_DISABLE_FIND_PACKAGE_gRPC TRUE)
|
|||||||
# Also prevent pkg-config from finding system packages
|
# Also prevent pkg-config from finding system packages
|
||||||
set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH FALSE)
|
set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH FALSE)
|
||||||
|
|
||||||
# Add compiler flags for Clang 15+ compatibility
|
# Add compiler flags for modern compiler compatibility
|
||||||
# gRPC v1.62.0 requires C++17 (std::result_of removed in C++20)
|
# These flags are scoped to gRPC and its dependencies only
|
||||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
add_compile_options(-Wno-error=missing-template-arg-list-after-template-kw)
|
# Clang 15+ compatibility for gRPC
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=missing-template-arg-list-after-template-kw")
|
||||||
add_compile_definitions(_LIBCPP_ENABLE_CXX20_REMOVED_TYPE_TRAITS)
|
add_compile_definitions(_LIBCPP_ENABLE_CXX20_REMOVED_TYPE_TRAITS)
|
||||||
|
elseif(MSVC)
|
||||||
|
# MSVC/Visual Studio compatibility for gRPC templates
|
||||||
|
# v1.67.1 fixes most issues, but these flags help with large template instantiations
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") # Large object files
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /permissive-") # Standards conformance
|
||||||
|
|
||||||
|
# Suppress common gRPC warnings on MSVC (don't use add_compile_options to avoid affecting user code)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267 /wd4244")
|
||||||
|
|
||||||
|
# Increase template instantiation depth for complex promise chains (MSVC 2019+)
|
||||||
|
if(MSVC_VERSION GREATER_EQUAL 1920)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /constexpr:depth2048")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Save YAZE's C++ standard and temporarily set to C++17 for gRPC
|
# Save YAZE's C++ standard and temporarily set to C++17 for gRPC
|
||||||
@@ -77,12 +91,15 @@ set(ABSL_PROPAGATE_CXX_STD ON CACHE BOOL "" FORCE)
|
|||||||
set(ABSL_ENABLE_INSTALL ON CACHE BOOL "" FORCE)
|
set(ABSL_ENABLE_INSTALL ON CACHE BOOL "" FORCE)
|
||||||
set(ABSL_BUILD_TESTING OFF CACHE BOOL "" FORCE)
|
set(ABSL_BUILD_TESTING OFF CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
# Declare gRPC - use v1.62.0 which fixes health_check_client incomplete type bug
|
# Declare gRPC - use v1.67.1 which fixes MSVC template issues and is compatible with modern compilers
|
||||||
# and is compatible with Clang 18
|
# v1.67.1 includes:
|
||||||
|
# - MSVC/Visual Studio compatibility fixes (template instantiation errors)
|
||||||
|
# - Clang 18+ compatibility
|
||||||
|
# - Abseil compatibility updates
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
grpc
|
grpc
|
||||||
GIT_REPOSITORY https://github.com/grpc/grpc.git
|
GIT_REPOSITORY https://github.com/grpc/grpc.git
|
||||||
GIT_TAG v1.62.0
|
GIT_TAG v1.67.1
|
||||||
GIT_PROGRESS TRUE
|
GIT_PROGRESS TRUE
|
||||||
GIT_SHALLOW TRUE
|
GIT_SHALLOW TRUE
|
||||||
USES_TERMINAL_DOWNLOAD TRUE
|
USES_TERMINAL_DOWNLOAD TRUE
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# Windows-optimized gRPC configuration using vcpkg
|
# Windows-optimized gRPC configuration using vcpkg
|
||||||
# This file provides fast gRPC builds on Windows using pre-compiled packages
|
# This file provides fast gRPC builds on Windows using pre-compiled packages
|
||||||
|
#
|
||||||
|
# Benefits:
|
||||||
|
# - vcpkg build: ~5 minutes (pre-compiled)
|
||||||
|
# - FetchContent build: ~45 minutes (compile from source)
|
||||||
|
#
|
||||||
|
# To use vcpkg (recommended):
|
||||||
|
# vcpkg install grpc:x64-windows
|
||||||
|
# cmake -DCMAKE_TOOLCHAIN_FILE=<vcpkg-root>/scripts/buildsystems/vcpkg.cmake ..
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
@@ -53,8 +61,18 @@ if(WIN32 AND YAZE_USE_VCPKG_GRPC)
|
|||||||
set(YAZE_GRPC_CONFIGURED TRUE PARENT_SCOPE)
|
set(YAZE_GRPC_CONFIGURED TRUE PARENT_SCOPE)
|
||||||
return()
|
return()
|
||||||
else()
|
else()
|
||||||
message(WARNING "vcpkg gRPC not found. Install with: vcpkg install grpc:x64-windows")
|
message(WARNING "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||||
message(STATUS "Falling back to FetchContent build (this will be slow on first build)")
|
message(WARNING " vcpkg gRPC not found")
|
||||||
|
message(WARNING " For faster builds (5 min vs 45 min), install:")
|
||||||
|
message(WARNING " ")
|
||||||
|
message(WARNING " vcpkg install grpc:x64-windows")
|
||||||
|
message(WARNING " ")
|
||||||
|
message(WARNING " Then configure with:")
|
||||||
|
message(WARNING " cmake -DCMAKE_TOOLCHAIN_FILE=<vcpkg>/scripts/buildsystems/vcpkg.cmake ..")
|
||||||
|
message(WARNING " ")
|
||||||
|
message(WARNING " Falling back to FetchContent (slow but works)")
|
||||||
|
message(WARNING " Using gRPC v1.67.1 (MSVC-compatible)")
|
||||||
|
message(WARNING "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ set(
|
|||||||
app/emu/debug/disassembly_viewer.cc
|
app/emu/debug/disassembly_viewer.cc
|
||||||
app/emu/debug/breakpoint_manager.cc
|
app/emu/debug/breakpoint_manager.cc
|
||||||
app/emu/debug/watchpoint_manager.cc
|
app/emu/debug/watchpoint_manager.cc
|
||||||
|
app/emu/input/input_backend.cc
|
||||||
|
app/emu/input/input_manager.cc
|
||||||
|
app/emu/ui/input_handler.cc
|
||||||
app/emu/cpu/cpu.cc
|
app/emu/cpu/cpu.cc
|
||||||
app/emu/video/ppu.cc
|
app/emu/video/ppu.cc
|
||||||
app/emu/memory/dma.cc
|
app/emu/memory/dma.cc
|
||||||
@@ -924,6 +927,22 @@ source_group("Application\\Emulator\\Video" FILES
|
|||||||
app/emu/video/ppu_registers.h
|
app/emu/video/ppu_registers.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Input System (abstracted for SDL2/SDL3)
|
||||||
|
source_group("Application\\Emulator\\Input" FILES
|
||||||
|
app/emu/input/input_backend.cc
|
||||||
|
app/emu/input/input_backend.h
|
||||||
|
app/emu/input/input_manager.cc
|
||||||
|
app/emu/input/input_manager.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# Emulator UI System
|
||||||
|
source_group("Application\\Emulator\\UI" FILES
|
||||||
|
app/emu/ui/input_handler.cc
|
||||||
|
app/emu/ui/input_handler.h
|
||||||
|
app/emu/ui/emulator_ui.h
|
||||||
|
app/emu/ui/debugger_ui.h
|
||||||
|
)
|
||||||
|
|
||||||
# Debug System
|
# Debug System
|
||||||
source_group("Application\\Emulator\\Debug" FILES
|
source_group("Application\\Emulator\\Debug" FILES
|
||||||
app/emu/debug/apu_debugger.cc
|
app/emu/debug/apu_debugger.cc
|
||||||
|
|||||||
@@ -28,11 +28,22 @@ target_link_libraries(rom_patch_utility
|
|||||||
${CMAKE_THREAD_LIBS_INIT}
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add dungeon_test_harness tool
|
||||||
|
add_executable(dungeon_test_harness
|
||||||
|
dungeon_test_harness.cc
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(dungeon_test_harness
|
||||||
|
yaze_core
|
||||||
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
|
)
|
||||||
|
|
||||||
# Windows stack size configuration for helper tools
|
# Windows stack size configuration for helper tools
|
||||||
set(HELPER_TOOLS
|
set(HELPER_TOOLS
|
||||||
overworld_golden_data_extractor
|
overworld_golden_data_extractor
|
||||||
extract_vanilla_values
|
extract_vanilla_values
|
||||||
rom_patch_utility
|
rom_patch_utility
|
||||||
|
dungeon_test_harness
|
||||||
)
|
)
|
||||||
|
|
||||||
foreach(TOOL ${HELPER_TOOLS})
|
foreach(TOOL ${HELPER_TOOLS})
|
||||||
@@ -48,6 +59,6 @@ foreach(TOOL ${HELPER_TOOLS})
|
|||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# Install tools to bin directory
|
# Install tools to bin directory
|
||||||
install(TARGETS overworld_golden_data_extractor extract_vanilla_values rom_patch_utility
|
install(TARGETS overworld_golden_data_extractor extract_vanilla_values rom_patch_utility dungeon_test_harness
|
||||||
DESTINATION bin
|
DESTINATION bin
|
||||||
)
|
)
|
||||||
|
|||||||
138
tools/test_helpers/dungeon_test_harness.cc
Normal file
138
tools/test_helpers/dungeon_test_harness.cc
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "app/emu/snes.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
using namespace yaze;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Dumps the state of WRAM and CPU/PPU registers after initialization.
|
||||||
|
*
|
||||||
|
* This tool runs the SNES emulator from the Reset vector until it hits the
|
||||||
|
* main game loop. It then dumps the entire state of WRAM and relevant registers
|
||||||
|
* to a header file. This captured state can be used to initialize the emulator
|
||||||
|
* for test harnesses, providing a realistic environment for executing native
|
||||||
|
* game code.
|
||||||
|
*/
|
||||||
|
class DungeonTestHarness {
|
||||||
|
public:
|
||||||
|
explicit DungeonTestHarness(const std::string& rom_path)
|
||||||
|
: rom_path_(rom_path) {}
|
||||||
|
|
||||||
|
absl::Status GenerateHarnessState(const std::string& output_path) {
|
||||||
|
// Load ROM
|
||||||
|
Rom rom;
|
||||||
|
RETURN_IF_ERROR(rom.LoadFromFile(rom_path_));
|
||||||
|
auto rom_data = rom.vector();
|
||||||
|
|
||||||
|
// Initialize SNES
|
||||||
|
emu::Snes snes;
|
||||||
|
snes.Init(rom_data);
|
||||||
|
snes.Reset(false);
|
||||||
|
|
||||||
|
auto& cpu = snes.cpu();
|
||||||
|
auto& ppu = snes.ppu();
|
||||||
|
|
||||||
|
// Run emulator until the main game loop is reached
|
||||||
|
int max_cycles = 5000000; // 5 million cycles should be plenty
|
||||||
|
int cycles = 0;
|
||||||
|
while (cycles < max_cycles) {
|
||||||
|
snes.RunCycle();
|
||||||
|
cycles++;
|
||||||
|
if (cpu.PB == 0x00 && cpu.PC == 0x8034) {
|
||||||
|
break; // Reached MainGameLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cycles >= max_cycles) {
|
||||||
|
return absl::InternalError("Emulator timed out; did not reach main game loop.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream out_file(output_path);
|
||||||
|
if (!out_file.is_open()) {
|
||||||
|
return absl::InternalError("Failed to open output file: " + output_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write header
|
||||||
|
out_file << "// =============================================================================" << std::endl;
|
||||||
|
out_file << "// YAZE Dungeon Test Harness State - Generated from: " << rom_path_ << std::endl;
|
||||||
|
out_file << "// Generated on: " << __DATE__ << " " << __TIME__ << std::endl;
|
||||||
|
out_file << "// =============================================================================" << std::endl;
|
||||||
|
out_file << std::endl;
|
||||||
|
out_file << "#pragma once" << std::endl;
|
||||||
|
out_file << std::endl;
|
||||||
|
out_file << "#include <cstdint>" << std::endl;
|
||||||
|
out_file << "#include <array>" << std::endl;
|
||||||
|
out_file << std::endl;
|
||||||
|
out_file << "namespace yaze {" << std::endl;
|
||||||
|
out_file << "namespace emu {" << std::endl;
|
||||||
|
out_file << std::endl;
|
||||||
|
|
||||||
|
// Write WRAM state
|
||||||
|
out_file << "constexpr std::array<uint8_t, 0x20000> kInitialWRAMState = {{" << std::endl;
|
||||||
|
for (int i = 0; i < 0x20000; ++i) {
|
||||||
|
if (i % 16 == 0) out_file << " ";
|
||||||
|
out_file << "0x" << std::hex << std::setw(2) << std::setfill('0')
|
||||||
|
<< static_cast<int>(snes.Read(0x7E0000 + i));
|
||||||
|
if (i < 0x1FFFF) out_file << ", ";
|
||||||
|
if (i % 16 == 15) out_file << std::endl;
|
||||||
|
}
|
||||||
|
out_file << "}};" << std::endl << std::endl;
|
||||||
|
|
||||||
|
// Write CPU/PPU register state
|
||||||
|
out_file << "// =============================================================================" << std::endl;
|
||||||
|
out_file << "// Initial Register States" << std::endl;
|
||||||
|
out_file << "// =============================================================================" << std::endl;
|
||||||
|
out_file << std::endl;
|
||||||
|
|
||||||
|
out_file << "struct InitialPpuState {" << std::endl;
|
||||||
|
out_file << " uint8_t inidisp = 0x" << std::hex << ppu.Read(0x2100, false) << ";" << std::endl;
|
||||||
|
out_file << " uint8_t objsel = 0x" << std::hex << ppu.Read(0x2101, false) << ";" << std::endl;
|
||||||
|
out_file << " uint8_t bgmode = 0x" << std::hex << ppu.Read(0x2105, false) << ";" << std::endl;
|
||||||
|
out_file << " uint8_t mosaic = 0x" << std::hex << ppu.Read(0x2106, false) << ";" << std::endl;
|
||||||
|
out_file << " uint8_t tm = 0x" << std::hex << ppu.Read(0x212C, false) << ";" << std::endl;
|
||||||
|
out_file << " uint8_t ts = 0x" << std::hex << ppu.Read(0x212D, false) << ";" << std::endl;
|
||||||
|
out_file << " uint8_t cgwsel = 0x" << std::hex << ppu.Read(0x2130, false) << ";" << std::endl;
|
||||||
|
out_file << " uint8_t cgadsub = 0x" << std::hex << ppu.Read(0x2131, false) << ";" << std::endl;
|
||||||
|
out_file << " uint8_t setini = 0x" << std::hex << ppu.Read(0x2133, false) << ";" << std::endl;
|
||||||
|
out_file << "};" << std::endl << std::endl;
|
||||||
|
|
||||||
|
out_file << "} // namespace emu" << std::endl;
|
||||||
|
out_file << "} // namespace yaze" << std::endl;
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string rom_path_;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (argc != 3) {
|
||||||
|
std::cerr << "Usage: " << argv[0] << " <rom_path> <output_path>" << 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
DungeonTestHarness harness(rom_path);
|
||||||
|
auto status = harness.GenerateHarnessState(output_path);
|
||||||
|
|
||||||
|
if (status.ok()) {
|
||||||
|
std::cout << "Successfully generated dungeon harness state from " << rom_path
|
||||||
|
<< " to " << output_path << std::endl;
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
std::cerr << "Error generating harness state: " << status.message() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user