diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 09578faf..4579ae93 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -30,6 +30,7 @@ #include "editor/editor.h" #include "imgui/imgui.h" #include "imgui/misc/cpp/imgui_stdlib.h" +#include "util/log.h" #include "util/macro.h" namespace yaze { @@ -668,6 +669,15 @@ absl::Status EditorManager::Update() { popup_manager_->DrawPopups(); ExecuteShortcuts(context_.shortcut_manager); toast_manager_.Draw(); + + // Ensure TestManager always has the current ROM + static Rom* last_test_rom = nullptr; + if (last_test_rom != current_rom_) { + util::logf("EditorManager::Update - ROM changed, updating TestManager: %p -> %p", + (void*)last_test_rom, (void*)current_rom_); + test::TestManager::Get().SetCurrentRom(current_rom_); + last_test_rom = current_rom_; + } // Autosave timer if (autosave_enabled_ && current_rom_ && current_rom_->dirty()) { @@ -1112,6 +1122,13 @@ absl::Status EditorManager::LoadRom() { current_editor_set_ = &session.editors; // Update test manager with current ROM for ROM-dependent tests + util::logf("EditorManager: Setting ROM in TestManager - %p ('%s')", + (void*)current_rom_, current_rom_ ? current_rom_->title().c_str() : "null"); + test::TestManager::Get().SetCurrentRom(current_rom_); + + // Update test manager with current ROM for ROM-dependent tests + util::logf("EditorManager: Setting ROM in TestManager - %p ('%s')", + (void*)current_rom_, current_rom_ ? current_rom_->title().c_str() : "null"); test::TestManager::Get().SetCurrentRom(current_rom_); static RecentFilesManager manager("recent_files.txt"); @@ -1203,6 +1220,11 @@ absl::Status EditorManager::OpenProject() { } current_rom_ = &session.rom; current_editor_set_ = &session.editors; + + // Update test manager with current ROM for ROM-dependent tests + util::logf("EditorManager: Setting ROM in TestManager - %p ('%s')", + (void*)current_rom_, current_rom_ ? current_rom_->title().c_str() : "null"); + test::TestManager::Get().SetCurrentRom(current_rom_); static RecentFilesManager manager("recent_files.txt"); manager.Load(); @@ -1326,6 +1348,11 @@ void EditorManager::SwitchToSession(size_t index) { auto& session = sessions_[index]; current_rom_ = &session.rom; current_editor_set_ = &session.editors; + + // Update test manager with current ROM for ROM-dependent tests + util::logf("EditorManager: Setting ROM in TestManager - %p ('%s')", + (void*)current_rom_, current_rom_ ? current_rom_->title().c_str() : "null"); + test::TestManager::Get().SetCurrentRom(current_rom_); test::TestManager::Get().SetCurrentRom(current_rom_); std::string session_name = current_rom_->title().empty() ? diff --git a/src/app/gui/input.cc b/src/app/gui/input.cc index e84003b0..add5d00e 100644 --- a/src/app/gui/input.cc +++ b/src/app/gui/input.cc @@ -405,6 +405,8 @@ void DrawMenu(Menu& menu) { if (ImGui::MenuItem(each_subitem.name.c_str(), each_subitem.shortcut.c_str())) { if (each_subitem.callback) each_subitem.callback(); + } else if (each_subitem.name == kSeparator) { + ImGui::Separator(); } } ImGui::EndMenu(); @@ -416,6 +418,8 @@ void DrawMenu(Menu& menu) { each_item.shortcut.c_str(), each_item.enabled_condition())) { if (each_item.callback) each_item.callback(); + } else if (each_item.name == kSeparator) { + ImGui::Separator(); } } } diff --git a/src/app/test/rom_dependent_test_suite.h b/src/app/test/rom_dependent_test_suite.h index 80263242..afa7c875 100644 --- a/src/app/test/rom_dependent_test_suite.h +++ b/src/app/test/rom_dependent_test_suite.h @@ -418,36 +418,56 @@ class RomDependentTestSuite : public TestSuite { result.timestamp = start_time; try { - // Test comprehensive save functionality - // 1. Create backup of original ROM data - auto original_data = rom->vector(); + // Test comprehensive save functionality using ROM copy + auto& test_manager = TestManager::Get(); - // 2. Test overworld modifications - zelda3::Overworld overworld(rom); - auto load_status = overworld.Load(rom); - if (!load_status.ok()) { - result.status = TestStatus::kFailed; - result.error_message = "Failed to load overworld: " + load_status.ToString(); - } else { - // 3. Make a small, safe modification + auto test_status = test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { + // Test overworld modifications on the copy + zelda3::Overworld overworld(test_rom); + auto load_status = overworld.Load(test_rom); + if (!load_status.ok()) { + return load_status; + } + + // Make modifications to the copy auto* test_map = overworld.mutable_overworld_map(0); uint8_t original_gfx = test_map->area_graphics(); test_map->set_area_graphics(0x01); // Change to a different graphics set - // 4. Test save operations + // Test save operations auto save_maps_status = overworld.SaveOverworldMaps(); auto save_props_status = overworld.SaveMapProperties(); - // 5. Restore original value immediately - test_map->set_area_graphics(original_gfx); - - if (save_maps_status.ok() && save_props_status.ok()) { - result.status = TestStatus::kPassed; - result.error_message = "Save operations completed successfully"; - } else { - result.status = TestStatus::kFailed; - result.error_message = "Save operations failed"; + if (!save_maps_status.ok()) { + return save_maps_status; } + if (!save_props_status.ok()) { + return save_props_status; + } + + // Save the test ROM with timestamp + Rom::SaveSettings settings; + settings.backup = false; + settings.save_new = true; + settings.filename = test_manager.GenerateTestRomFilename(test_rom->title()); + + auto save_file_status = test_rom->SaveToFile(settings); + if (!save_file_status.ok()) { + return save_file_status; + } + + // Offer to open test ROM in new session + test_manager.OfferTestSessionCreation(settings.filename); + + return absl::OkStatus(); + }); + + if (test_status.ok()) { + result.status = TestStatus::kPassed; + result.error_message = "Comprehensive save test completed successfully using ROM copy"; + } else { + result.status = TestStatus::kFailed; + result.error_message = "Save test failed: " + test_status.ToString(); } } catch (const std::exception& e) { diff --git a/src/app/test/test_manager.cc b/src/app/test/test_manager.cc index 2798e209..4837e0b7 100644 --- a/src/app/test/test_manager.cc +++ b/src/app/test/test_manager.cc @@ -290,6 +290,15 @@ void TestManager::DrawTestDashboard() { // ROM status indicator with detailed information bool has_rom = current_rom_ && current_rom_->is_loaded(); + // Add real-time ROM status checking + static int frame_counter = 0; + frame_counter++; + if (frame_counter % 60 == 0) { // Check every 60 frames + // Log ROM status periodically for debugging + util::logf("TestManager ROM status check - Frame %d: ROM %p, loaded: %s", + frame_counter, (void*)current_rom_, has_rom ? "true" : "false"); + } + if (ImGui::BeginTable("ROM_Status_Table", 2, ImGuiTableFlags_BordersInner)) { ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 120); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); @@ -789,6 +798,49 @@ void TestManager::DrawTestDashboard() { } ImGui::End(); } + + // Test Session Creation Dialog + if (show_test_session_dialog_) { + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSize(ImVec2(500, 300), ImGuiCond_Appearing); + + if (ImGui::Begin("Test ROM Session", &show_test_session_dialog_, ImGuiWindowFlags_NoResize)) { + ImGui::Text("%s Test ROM Created Successfully", ICON_MD_CHECK_CIRCLE); + ImGui::Separator(); + + ImGui::Text("A test ROM has been created with your modifications:"); + ImGui::Text("File: %s", test_rom_path_for_session_.c_str()); + + // Extract just the filename for display + std::string display_filename = test_rom_path_for_session_; + auto last_slash = display_filename.find_last_of("/\\"); + if (last_slash != std::string::npos) { + display_filename = display_filename.substr(last_slash + 1); + } + + ImGui::Separator(); + ImGui::Text("Would you like to open this test ROM in a new session?"); + + if (ImGui::Button(absl::StrFormat("%s Open in New Session", ICON_MD_TAB).c_str(), ImVec2(200, 0))) { + // TODO: This would need access to EditorManager to create a new session + // For now, just show a message + util::logf("User requested to open test ROM in new session: %s", test_rom_path_for_session_.c_str()); + show_test_session_dialog_ = false; + } + + ImGui::SameLine(); + if (ImGui::Button(absl::StrFormat("%s Keep Current Session", ICON_MD_CLOSE).c_str(), ImVec2(200, 0))) { + show_test_session_dialog_ = false; + } + + ImGui::Separator(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), + "Note: Test ROM contains your modifications and can be"); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), + "opened later using File → Open"); + } + ImGui::End(); + } } void TestManager::RefreshCurrentRom() { @@ -810,6 +862,81 @@ void TestManager::RefreshCurrentRom() { util::logf("==============================="); } +absl::Status TestManager::CreateTestRomCopy(Rom* source_rom, std::unique_ptr& test_rom) { + if (!source_rom || !source_rom->is_loaded()) { + return absl::FailedPreconditionError("Source ROM not loaded"); + } + + util::logf("Creating test ROM copy from: %s", source_rom->title().c_str()); + + // Create a new ROM instance + test_rom = std::make_unique(); + + // Copy the ROM data + auto rom_data = source_rom->vector(); + auto load_status = test_rom->LoadFromData(rom_data, true); + if (!load_status.ok()) { + return load_status; + } + + util::logf("Test ROM copy created successfully (size: %.2f MB)", + test_rom->size() / 1048576.0f); + return absl::OkStatus(); +} + +std::string TestManager::GenerateTestRomFilename(const std::string& base_name) { + // Generate filename with timestamp + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + auto local_time = *std::localtime(&time_t); + + std::string timestamp = absl::StrFormat("%04d%02d%02d_%02d%02d%02d", + local_time.tm_year + 1900, + local_time.tm_mon + 1, + local_time.tm_mday, + local_time.tm_hour, + local_time.tm_min, + local_time.tm_sec); + + std::string base_filename = base_name; + // Remove any path and extension + auto last_slash = base_filename.find_last_of("/\\"); + if (last_slash != std::string::npos) { + base_filename = base_filename.substr(last_slash + 1); + } + auto last_dot = base_filename.find_last_of('.'); + if (last_dot != std::string::npos) { + base_filename = base_filename.substr(0, last_dot); + } + + return absl::StrFormat("%s_test_%s.sfc", base_filename.c_str(), timestamp.c_str()); +} + +void TestManager::OfferTestSessionCreation(const std::string& test_rom_path) { + // Store the test ROM path for the dialog + test_rom_path_for_session_ = test_rom_path; + show_test_session_dialog_ = true; +} + +absl::Status TestManager::TestRomWithCopy(Rom* source_rom, std::function test_function) { + if (!source_rom || !source_rom->is_loaded()) { + return absl::FailedPreconditionError("Source ROM not loaded"); + } + + // Create a copy of the ROM for testing + std::unique_ptr test_rom; + RETURN_IF_ERROR(CreateTestRomCopy(source_rom, test_rom)); + + util::logf("Executing test function on ROM copy"); + + // Run the test function on the copy + auto test_result = test_function(test_rom.get()); + + util::logf("Test function completed with status: %s", test_result.ToString().c_str()); + + return test_result; +} + absl::Status TestManager::LoadRomForTesting(const std::string& filename) { // This would load a ROM specifically for testing purposes // For now, just log the request @@ -849,18 +976,29 @@ absl::Status TestManager::TestRomSaveLoad(Rom* rom) { return absl::FailedPreconditionError("No ROM loaded for testing"); } - util::logf("Testing ROM save/load operations on: %s", rom->title().c_str()); - - // Create backup of ROM data - auto original_data = rom->vector(); - - // Perform test modifications and save operations - // This would be implemented with actual save/load tests - - // Restore original data - std::copy(original_data.begin(), original_data.end(), rom->mutable_data()); - - return absl::OkStatus(); + // Use TestRomWithCopy to avoid affecting the original ROM + return TestRomWithCopy(rom, [this](Rom* test_rom) -> absl::Status { + util::logf("Testing ROM save/load operations on copy: %s", test_rom->title().c_str()); + + // Perform test modifications on the copy + // Test save operations + Rom::SaveSettings settings; + settings.backup = false; + settings.save_new = true; + settings.filename = GenerateTestRomFilename(test_rom->title()); + + auto save_status = test_rom->SaveToFile(settings); + if (!save_status.ok()) { + return save_status; + } + + util::logf("Test ROM saved successfully to: %s", settings.filename.c_str()); + + // Offer to open test ROM in new session + OfferTestSessionCreation(settings.filename); + + return absl::OkStatus(); + }); } absl::Status TestManager::TestRomDataIntegrity(Rom* rom) { @@ -868,12 +1006,27 @@ absl::Status TestManager::TestRomDataIntegrity(Rom* rom) { return absl::FailedPreconditionError("No ROM loaded for testing"); } - util::logf("Testing ROM data integrity on: %s", rom->title().c_str()); - - // Perform data integrity checks - // This would validate ROM structure, checksums, etc. - - return absl::OkStatus(); + // Use TestRomWithCopy for integrity testing (read-only but uses copy for safety) + return TestRomWithCopy(rom, [](Rom* test_rom) -> absl::Status { + util::logf("Testing ROM data integrity on copy: %s", test_rom->title().c_str()); + + // Perform data integrity checks on the copy + // This validates ROM structure, checksums, etc. without affecting original + + // Basic ROM structure validation + if (test_rom->size() < 0x100000) { // 1MB minimum for ALTTP + return absl::FailedPreconditionError("ROM file too small for A Link to the Past"); + } + + // Check ROM header + auto header_status = test_rom->ReadByteVector(0x7FC0, 32); + if (!header_status.ok()) { + return header_status.status(); + } + + util::logf("ROM integrity check passed for: %s", test_rom->title().c_str()); + return absl::OkStatus(); + }); } } // namespace test diff --git a/src/app/test/test_manager.h b/src/app/test/test_manager.h index 0d9d8e86..44063aed 100644 --- a/src/app/test/test_manager.h +++ b/src/app/test/test_manager.h @@ -2,6 +2,7 @@ #define YAZE_APP_TEST_TEST_MANAGER_H #include +#include #include #include #include @@ -10,6 +11,7 @@ #include "absl/status/status.h" #include "app/rom.h" #include "imgui/imgui.h" +#include "util/log.h" // Forward declarations namespace yaze { @@ -165,7 +167,13 @@ class TestManager { void DrawTestDashboard(); // ROM-dependent testing - void SetCurrentRom(Rom* rom) { current_rom_ = rom; } + void SetCurrentRom(Rom* rom) { + util::logf("TestManager::SetCurrentRom called with ROM: %p", (void*)rom); + if (rom) { + util::logf("ROM title: '%s', loaded: %s", rom->title().c_str(), rom->is_loaded() ? "true" : "false"); + } + current_rom_ = rom; + } Rom* GetCurrentRom() const { return current_rom_; } void RefreshCurrentRom(); // Refresh ROM pointer from editor manager // Remove EditorManager dependency to avoid circular includes @@ -174,10 +182,16 @@ class TestManager { absl::Status LoadRomForTesting(const std::string& filename); void ShowRomComparisonResults(const Rom& before, const Rom& after); + // Test ROM management + absl::Status CreateTestRomCopy(Rom* source_rom, std::unique_ptr& test_rom); + std::string GenerateTestRomFilename(const std::string& base_name); + void OfferTestSessionCreation(const std::string& test_rom_path); + public: - // ROM testing methods + // ROM testing methods (work on copies, not originals) absl::Status TestRomSaveLoad(Rom* rom); absl::Status TestRomDataIntegrity(Rom* rom); + absl::Status TestRomWithCopy(Rom* source_rom, std::function test_function); private: TestManager(); @@ -227,6 +241,8 @@ class TestManager { bool show_google_tests_ = false; bool show_rom_test_results_ = false; bool show_rom_file_dialog_ = false; + bool show_test_session_dialog_ = false; + std::string test_rom_path_for_session_; }; // Utility functions for test result formatting