diff --git a/.gitignore b/.gitignore index 1501fabd..1bacca8c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,13 @@ cmake-build-debug .idea .vs *.pdb + +# Binary executables +quaesar +quaesar-dbg + +# ImGui layout files +*.ini +debugger_layout.ini +imgui.ini +default_layout.ini diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f5367b2..9bfe8a08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -201,11 +201,35 @@ target_link_libraries(quaesar PRIVATE ${QUAESAR_EXTRA_LIBS} ) +# Force-load the entire amDebugger static library so that auto-registered debugger +# windows (TS_BEGIN_REFLECT_CLASS static initializers) are not stripped by the linker. +# MSVC uses WHOLEARCHIVE (set in amDebugger CMakeLists.txt INTERFACE option). +# macOS uses -force_load; Linux uses --whole-archive. +if(APPLE) + target_link_options(quaesar PRIVATE "-Wl,-force_load,$") +elseif(LINUX OR UNIX) + target_link_options(quaesar PRIVATE + "-Wl,--whole-archive" "$" "-Wl,--no-whole-archive") +endif() + #if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/imgui.ini") # file(COPY_FILE "${CMAKE_CURRENT_LIST_DIR}/bin/install/default_layout.ini" "${CMAKE_CURRENT_BINARY_DIR}/imgui.ini") #endif() +# Bundle the bundled default debugger dock layout next to the binary. +# DebuggerApp::initImGui() resolves it via SDL_GetBasePath() (the dir of the running executable) +# and loads it on first launch when no user imgui.ini exists, so the debugger window +# does not come up as an empty (red) dockspace. +# Using a POST_BUILD custom command + $ ensures the layout +# lands next to the actual exe on every platform/generator (incl. multi-config VS/Xcode). +add_custom_command(TARGET quaesar POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_LIST_DIR}/resources/default_layout.ini" + "$/default_layout.ini" + VERBATIM +) + add_option_edit_and_continue(quaesar) add_option_edit_and_continue(qd) diff --git a/libs/amDebugger/CMakeLists.txt b/libs/amDebugger/CMakeLists.txt index 15898c14..e7d8b2c2 100644 --- a/libs/amDebugger/CMakeLists.txt +++ b/libs/amDebugger/CMakeLists.txt @@ -14,7 +14,11 @@ set(AM_SRC) function(add_source_group SUBDIR) set(CURDIR "${AM_SRC_INCLUDE_DIR}/amDebugger/${SUBDIR}") - file(GLOB SUB_FILES_SRC "${CURDIR}/*.cpp" "${CURDIR}/*.h") + # CONFIGURE_DEPENDS: ensures CMake re-evaluates the glob when source files + # are added/removed, so incremental builds always pick up changed sources. + # Without it, file(GLOB) is only evaluated at configure time and new/modified + # files are silently missed until a full cmake reconfigure. + file(GLOB SUB_FILES_SRC CONFIGURE_DEPENDS "${CURDIR}/*.cpp" "${CURDIR}/*.h") # Add files to local lists list(APPEND AM_SRC ${SUB_FILES_SRC}) # Pass updated lists to the global scope @@ -35,6 +39,7 @@ add_source_group("codeAnalyzer") add_library(${PROJECT_NAME} ${AM_SRC}) #Whole archive option is needed to ensure that all windows are linked in the final binary +# (debugger windows use static-initializer-based auto-registration via TS_BEGIN_REFLECT_CLASS) if(MSVC) target_link_options(${PROJECT_NAME} INTERFACE "/WHOLEARCHIVE:${PROJECT_NAME}") endif() diff --git a/libs/amDebugger/src/amDebugger/debuggerOps.h b/libs/amDebugger/src/amDebugger/debuggerOps.h index 8d2de2a5..516af050 100644 --- a/libs/amDebugger/src/amDebugger/debuggerOps.h +++ b/libs/amDebugger/src/amDebugger/debuggerOps.h @@ -171,6 +171,16 @@ struct CopperToggleBreakpoint : public amD::operation::OperationArgs { }; +struct PauseEmulation : public amD::operation::OperationArgs { + DECLARE_OPERATION_1(amD::operation::PauseEmulation); + static void setup(qd::operation::OpDesc& d) + { + d.m_name = "Pause"; + d.addShortcut(amD::shortcut::EId::PauseEmulation); + } +}; + + struct ToggleTurboEmulation : public amD::operation::OperationArgs { DECLARE_OPERATION_1(amD::operation::ToggleTurboEmulation); static void setup(qd::operation::OpDesc& d) diff --git a/libs/amDebugger/src/amDebugger/debuggerWndApp.cpp b/libs/amDebugger/src/amDebugger/debuggerWndApp.cpp index a57b41e4..d6924e9c 100644 --- a/libs/amDebugger/src/amDebugger/debuggerWndApp.cpp +++ b/libs/amDebugger/src/amDebugger/debuggerWndApp.cpp @@ -23,6 +23,20 @@ namespace amD { constexpr uint32_t g_nDebuggerWndSizeX = 1368; constexpr uint32_t g_nDebuggerWndSizeY = 800; +static int resizeEventWatcher(void* data, SDL_Event* event) +{ + if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + DebuggerApp* app = static_cast(data); + if (app && SDL_GetWindowID(app->getWindow()) == event->window.windowID) + { + app->updateAppPart(0, 0); + app->renderAppPart(); + } + } + return 0; +} + DebuggerApp::DebuggerApp() { @@ -45,6 +59,8 @@ void DebuggerApp::init() createRenderWindow(); initImGui(); + SDL_AddEventWatch(resizeEventWatcher, this); + m_pDebugger->setDbgServiceBridge(create_dummy_connection()); assert(m_pDebugger); @@ -52,6 +68,9 @@ void DebuggerApp::init() m_pGui = mk.make_(this, m_pDebugger); m_pOperationMgr = m_pGui->getOperationMgr(); assert(m_pOperationMgr); + + loadLayoutSettings(); + m_bFullyInitialized = true; } @@ -78,6 +97,11 @@ void DebuggerApp::createRenderWindow() return; } m_pWindow = window; + + // Clear framebuffer to gray immediately to avoid red/garbage flash + SDL_SetRenderDrawColor(m_pWndRenderer, 128, 128, 128, 255); + SDL_RenderClear(m_pWndRenderer); + SDL_RenderPresent(m_pWndRenderer); } @@ -88,15 +112,22 @@ void DebuggerApp::initImGui() // Setup Dear ImGui context ImGuiIO& io = m_pQimGuiCtx->getIO(); - (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io.IniFilename = "debugger_layout.ini"; // Setup Dear ImGui style qd::imGuiApplyStyleDark(); } +void DebuggerApp::loadLayoutSettings() +{ + m_pQimGuiCtx->useCurrent(); + ImGui::LoadIniSettingsFromDisk(ImGui::GetIO().IniFilename); +} + + DebuggerApp::~DebuggerApp() { assert(!m_init); @@ -105,12 +136,22 @@ DebuggerApp::~DebuggerApp() qd::EFlow DebuggerApp::applyOperationMsgProcImp(qd::operation::BaseOpArgs* p_msg) { - return m_pDebugger->applyOperationMsgProcImp(p_msg); + qd::EFlow r = m_pDebugger->applyOperationMsgProcImp(p_msg); + + // Also forward to the real emulator thread (if callback registered) + // so that emulator control operations (pause, continue, step, etc.) + // reach the actual running emulator, not just the dummy VM. + if (m_forwardOpToEmulatorCb) + m_forwardOpToEmulatorCb(p_msg); + + return r; } void DebuggerApp::destroy() { + SDL_DelEventWatch(resizeEventWatcher, this); + if (m_pOperationMgr) m_pOperationMgr->destroy(); m_pOperationMgr = nullptr; @@ -119,6 +160,12 @@ void DebuggerApp::destroy() m_pGui->destroy(); m_pGui = nullptr; + // Save layout before destroying context + if (m_pQimGuiCtx) + { + m_pQimGuiCtx->useCurrent(); + ImGui::SaveIniSettingsToDisk(ImGui::GetIO().IniFilename); + } SAFE_DESTROY(m_pQimGuiCtx); SDL_DestroyRenderer(m_pWndRenderer); m_pWndRenderer = nullptr; @@ -131,15 +178,27 @@ void DebuggerApp::destroy() void DebuggerApp::updateAppPart(float /*dt*/, float /*time*/) { - if (isWndVisible()) + if (!isWndVisible()) + { + m_pQimGuiCtx->skipFrame(); + return; + } + + uint64_t now = SDL_GetTicks64(); + if (now - m_lastRenderTimeMs < kMinFrameIntervalMs) + { + m_pQimGuiCtx->skipFrame(); + return; + } + m_lastRenderTimeMs = now; + + m_pQimGuiCtx->newFrame(); + if (m_bFullyInitialized && m_pGui && m_pDebugger) { - m_pQimGuiCtx->newFrame(); getDbg()->fetchVmState(); m_pGui->drawImGuiMainFrame(); - m_pQimGuiCtx->endFrame(); } - else - m_pQimGuiCtx->skipFrame(); + m_pQimGuiCtx->endFrame(); } @@ -163,11 +222,20 @@ void DebuggerApp::setWndVisible(bool v) { if (v) { + // Clear to gray before showing to avoid uninitialized framebuffer flash + SDL_SetRenderDrawColor(m_pWndRenderer, 128, 128, 128, 255); + SDL_RenderClear(m_pWndRenderer); + SDL_RenderPresent(m_pWndRenderer); + SDL_ShowWindow(m_pWindow); setPartRenderable(true); } else { + // Save layout before hiding + m_pQimGuiCtx->useCurrent(); + ImGui::SaveIniSettingsToDisk(ImGui::GetIO().IniFilename); + SDL_HideWindow(m_pWindow); setPartRenderable(false); } diff --git a/libs/amDebugger/src/amDebugger/debuggerWndApp.h b/libs/amDebugger/src/amDebugger/debuggerWndApp.h index 57f0b6cd..0e7b005f 100644 --- a/libs/amDebugger/src/amDebugger/debuggerWndApp.h +++ b/libs/amDebugger/src/amDebugger/debuggerWndApp.h @@ -5,7 +5,7 @@ #include "qd/base/base.h" #include "qd/base/classIdCC.h" #include "qd/qui/uiOperation.h" -#include +#include #include "qd/stl/fixed_vector.h" #include "qd/stl/string.h" @@ -49,14 +49,18 @@ class DebuggerApp qd::QImGuiContext* m_pQimGuiCtx = nullptr; uint32_t m_nCurDbgClientIdx = 0; int m_init = false; + uint64_t m_lastRenderTimeMs = 0; + static constexpr uint64_t kMinFrameIntervalMs = 66; // ~15 FPS max public: + bool m_bFullyInitialized = false; ref_ptr m_pDebugger = nullptr; // current debugger client ref_ptr m_pGui; qd::OperationsRegistry* m_pOperationMgr = nullptr; public: DebuggerApp(); + SDL_Window* getWindow() const { return m_pWindow; } SDL_Renderer* getRenderer() const { return m_pWndRenderer; } uint32_t getCurDbgClientIdx() const { return m_nCurDbgClientIdx; } @@ -77,9 +81,23 @@ class DebuggerApp qd::EFlow applyOperationMsgProcImp(qd::operation::BaseOpArgs* p_msg) override; + // Callback to forward operations to the real emulator. + // Set by the application to bridge debugger operations to the actual emulator thread. + using ForwardOpToEmulatorCb = std::function; + ForwardOpToEmulatorCb m_forwardOpToEmulatorCb; + void setForwardOpToEmulatorCb(ForwardOpToEmulatorCb cb) { m_forwardOpToEmulatorCb = std::move(cb); } + + // Forward an operation to the real emulator via the registered callback. + // Called from DebuggerDesktop before dispatching to the dummy VM. + void forwardOpToEmulator(qd::operation::BaseOpArgs* args) { + if (m_forwardOpToEmulatorCb) + m_forwardOpToEmulatorCb(args); + } + private: void createRenderWindow(); void initImGui(); + void loadLayoutSettings(); virtual ~DebuggerApp() override; }; // class DebuggerApp diff --git a/libs/amDebugger/src/amDebugger/shortcutsList.h b/libs/amDebugger/src/amDebugger/shortcutsList.h index 47e4e615..c82f97af 100644 --- a/libs/amDebugger/src/amDebugger/shortcutsList.h +++ b/libs/amDebugger/src/amDebugger/shortcutsList.h @@ -26,6 +26,7 @@ namespace shortcut { SHORTCUT(CopperTraceStep, [](qd::Shortcut& s) { s.addKey(ImGuiKey_F11).addKey(ImGuiMod_Shift).setRepeat(); }) \ SHORTCUT(ToggleTurboEmulation, [](qd::Shortcut& s) { s.addKey(ImGuiKey_NumLock); }) \ SHORTCUT(ResetAmigaEmu, [](qd::Shortcut&) {}) \ + SHORTCUT(PauseEmulation, [](qd::Shortcut& s) { s.addKey(ImGuiKey_F8).addKey(ImGuiMod_Ctrl); }) \ SHORTCUT(AlwaysOnTopEmu, [](qd::Shortcut& s) { s.addKey(ImGuiKey_T).addKey(ImGuiMod_Ctrl); }) \ SHORTCUT(ShowDebuggerWnd, [](qd::Shortcut& s) { s.addKey(ImGuiKey_F12).addKey(ImGuiMod_Shift); }) \ SHORTCUT(ShowUaeOptionsWnd, [](qd::Shortcut& s) { s.addKey(ImGuiKey_P).addKey(ImGuiMod_Ctrl); }) \ diff --git a/libs/amDebugger/src/amDebugger/ui/debuggerDesktop.cpp b/libs/amDebugger/src/amDebugger/ui/debuggerDesktop.cpp index af79445d..7763206d 100644 --- a/libs/amDebugger/src/amDebugger/ui/debuggerDesktop.cpp +++ b/libs/amDebugger/src/amDebugger/ui/debuggerDesktop.cpp @@ -35,9 +35,13 @@ qd::EFlow DebuggerDesktop::applyOperationMsgProcImp(qd::operation::BaseOpArgs* a if (auto pWnd = findChildByType_()) return pWnd->applyOperationMsgProcImp(args); } - if (m_pDbg) - return m_pDbg->applyOperationMsgProcImp(args); - return EFlow::NO_RESULT; + // Forward all operations to the real emulator thread. + // Do NOT fall through to the dummy VM — it has no real UAE backend + // and will crash on emulator-control ops (step, pause, continue, etc.). + if (m_pDbgApp) + m_pDbgApp->forwardOpToEmulator(args); + + return EFlow::STOP; } @@ -59,23 +63,29 @@ void DebuggerDesktop::_drawMainMenuBar() if (auto pm = qIm::LockMenu("Emulator")) { - qIm::menuItemFromOperationArgs_(pDbg); - qIm::menuItemFromOperationArgs_(pDbg); + qIm::menuItemFromOperationArgs_(this); + qIm::menuItemFromOperationArgs_(this); } if (auto pm = qIm::LockMenu("Debug")) { IVm::VM* vm = pDbg->getVm(); IVm::EVmDebugMode debugMode = vm ? vm->getVmDebugMode().get() : IVm::EVmDebugMode::Live; - qIm::menuItemFromOperationArgs_(pDbg, "", false, + qIm::menuItemFromOperationArgs_(this, "", false, + debugMode.isLive()); + qIm::menuItemFromOperationArgs_(this, "", false, debugMode.isBreak()); - qIm::menuItemFromOperationArgs_(pDbg, "", false, debugMode.isLive()); + qIm::menuItemFromOperationArgs_(this, "", false, debugMode.isLive()); ImGui::Separator(); - qIm::menuItemFromOperationArgs_(pDbg); - qIm::menuItemFromOperationArgs_(pDbg); + // Step/trace commands only make sense when paused (Break mode) + qIm::menuItemFromOperationArgs_(this, "", false, + debugMode.isBreak()); + qIm::menuItemFromOperationArgs_(this, "", false, + debugMode.isBreak()); qIm::menuItemFromOperationArgs_(this); ImGui::Separator(); - qIm::menuItemFromOperationArgs_(pDbg); + qIm::menuItemFromOperationArgs_(this, "", false, + debugMode.isBreak()); qIm::menuItemFromOperationArgs_(this); ImGui::Separator(); @@ -117,7 +127,7 @@ void DebuggerDesktop::onUiNodeCreated(qd::UiNodeCreator* mk) m_pOperationMgr = &qd::OperationsRegistry::get(); // createComp_()->m_pOpMgr; m_pShortcutMgr = qd::ShortcutsMgr::get(); // createComp_(); m_pShortcutMgr->createPredefinedShortcuts( - qtd::span(&amD::shortcut::g_shortcuts_list[0], (size_t)amD::shortcut::EId::MAX_COUNT)); + qtd::span(amD::shortcut::g_shortcuts_list)); // create all m_pChilds createAllUiWndows(); @@ -143,7 +153,7 @@ void DebuggerDesktop::createAllUiWndows() continue; } UiViewCreateCtx cv(this); - amD::AmDbgWindow* pCurWnd = pCreateAttr->makeInstance_(cv); + amD::AmDbgWindow* pCurWnd = pCreateAttr->makeInstance_(&cv); assert(pCurWnd); addChild(pCurWnd); } @@ -160,6 +170,40 @@ DebuggerDesktop::~DebuggerDesktop() } +void DebuggerDesktop::_buildDefaultDockLayout(ImGuiID dockspaceId) +{ + ImGuiID idLeft, idRight; + ImGui::DockBuilderSplitNode(dockspaceId, ImGuiDir_Left, 0.48f, &idLeft, &idRight); + + ImGui::DockBuilderDockWindow("Disassembly", idLeft); + ImGui::DockBuilderDockWindow("Copper debug", idLeft); + + ImGuiID idRightTop, idRightBottom; + ImGui::DockBuilderSplitNode(idRight, ImGuiDir_Up, 0.66f, &idRightTop, &idRightBottom); + + ImGui::DockBuilderDockWindow("Screen", idRightTop); + ImGui::DockBuilderDockWindow("Memory graph", idRightTop); + ImGui::DockBuilderDockWindow("Console", idRightBottom); + ImGui::DockBuilderDockWindow("Memory", idRightBottom); + + ImGuiID idLeftMain, idLeftMid; + ImGui::DockBuilderSplitNode(idLeft, ImGuiDir_Left, 0.59f, &idLeftMain, &idLeftMid); + + ImGui::DockBuilderDockWindow("Disassembly", idLeftMain); + ImGui::DockBuilderDockWindow("Copper debug", idLeftMain); + + ImGuiID idMidTop, idMidBottom; + ImGui::DockBuilderSplitNode(idLeftMid, ImGuiDir_Up, 0.5f, &idMidTop, &idMidBottom); + + ImGui::DockBuilderDockWindow("Registers", idMidTop); + ImGui::DockBuilderDockWindow("Palette", idMidTop); + ImGui::DockBuilderDockWindow("Blitter", idMidTop); + ImGui::DockBuilderDockWindow("Custom regs", idMidBottom); + + ImGui::DockBuilderFinish(dockspaceId); +} + + void DebuggerDesktop::drawImGuiMainFrame() { const ImGuiViewport* viewport = ImGui::GetMainViewport(); @@ -169,31 +213,54 @@ void DebuggerDesktop::drawImGuiMainFrame() ImGuiWindowFlags wndFlags = 0; wndFlags |= ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; - wndFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + wndFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoScrollbar; bool open = true; if (ImGui::Begin("Quaesar debugger", &open, wndFlags)) { - _drawMainMenuBar(); - _drawToolBar(); - ImGui::DockSpace(ImGui::GetID("DockSpace"), ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None); + // Only draw content when VM is fully bound - avoids layout jumps during init + if (m_pDbgApp && m_pDbgApp->m_bFullyInitialized) + { + _drawMainMenuBar(); + _drawToolBar(); + } - // draw static nodes - this->drawContentImp(); + ImGuiID dockspaceId = ImGui::GetID("DockSpace"); + ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None); - qd::OperationsRegistry* pOpMgr = &qd::OperationsRegistry::get(); - pOpMgr->testOperationsShortcuts_< - // clang-format off - amD::operation::DisasmTraceStepInto - , amD::operation::DebugWaitScanLines - , amD::operation::VmPlayerWndAlwaysOnTop - , amD::operation::DebugTraceContinue - , amD::operation::DebugTraceStart - , amD::operation::DisasmToggleBreakpoint - , amD::operation::CopperTraceStep - , amD::operation::CopperToggleBreakpoint - // clang-format on - >(this); + // Build default layout if ini was missing/empty/invalid + static int s_layoutCheckFrame = 0; + if (s_layoutCheckFrame < 2) + { + s_layoutCheckFrame++; + if (s_layoutCheckFrame == 2) + { + ImGuiDockNode* rootNode = ImGui::DockBuilderGetNode(dockspaceId); + bool hasChildren = rootNode && (rootNode->ChildNodes[0] || rootNode->ChildNodes[1]); + if (!hasChildren) + _buildDefaultDockLayout(dockspaceId); + } + } + + if (m_pDbgApp && m_pDbgApp->m_bFullyInitialized) + { + this->drawContentImp(); + + qd::OperationsRegistry* pOpMgr = &qd::OperationsRegistry::get(); + pOpMgr->testOperationsShortcuts_< + // clang-format off + amD::operation::DisasmTraceStepInto + , amD::operation::DebugWaitScanLines + , amD::operation::VmPlayerWndAlwaysOnTop + , amD::operation::DebugTraceContinue + , amD::operation::DebugTraceStart + , amD::operation::PauseEmulation + , amD::operation::DisasmToggleBreakpoint + , amD::operation::CopperTraceStep + , amD::operation::CopperToggleBreakpoint + // clang-format on + >(this); + } } ImGui::End(); } @@ -234,10 +301,13 @@ void DebuggerDesktop::_drawToolBar() const qd::operation::OpDesc* pOpDesc; pOpDesc = pOpMgr->findOpDesc(amD::operation::DisasmTraceStepInto::CID); { + bool canStep = dbg->isDebugActivated(); + if (!canStep) { ImGui::BeginDisabled(); } if (ImGui::ImageButton("##StepInto", my_tex_id, size, uv0, uv1, ImVec4(0, 0, 0, 1))) { doOperation_(); } + if (!canStep) { ImGui::EndDisabled(); } ImGui::SetItemTooltipV(CC(pOpDesc->getShortcutGuiStr()), nullptr); // diff --git a/libs/amDebugger/src/amDebugger/ui/debuggerDesktop.h b/libs/amDebugger/src/amDebugger/ui/debuggerDesktop.h index e96bfe56..d8c48dc5 100644 --- a/libs/amDebugger/src/amDebugger/ui/debuggerDesktop.h +++ b/libs/amDebugger/src/amDebugger/ui/debuggerDesktop.h @@ -47,6 +47,7 @@ class DebuggerDesktop : public qd::UiDesktop, public qd::IOperationEnvironment void createAllUiWndows(); void _drawMainMenuBar(); void _drawToolBar(); + void _buildDefaultDockLayout(ImGuiID dockspaceId); }; // class GUIManager diff --git a/libs/amDebugger/src/amDebugger/ui/uiView.h b/libs/amDebugger/src/amDebugger/ui/uiView.h index dee5460c..897c0bde 100644 --- a/libs/amDebugger/src/amDebugger/ui/uiView.h +++ b/libs/amDebugger/src/amDebugger/ui/uiView.h @@ -1,7 +1,7 @@ #pragma once #include "amDebugger/ui/uiDefs.h" #include "qd/base/classInfoReg.h" -#include "qd/base/color.h" +#include "qd/base/Color.h" #include "qd/imGui/imGui.h" #include "qd/typeSystem/attributesCommon.h" #include "qd/qui/controls/window.h" diff --git a/libs/amDebugger/src/amDebugger/window/blitter_wnd.cpp b/libs/amDebugger/src/amDebugger/window/blitter_wnd.cpp index f6ecd7ef..87c334df 100644 --- a/libs/amDebugger/src/amDebugger/window/blitter_wnd.cpp +++ b/libs/amDebugger/src/amDebugger/window/blitter_wnd.cpp @@ -6,7 +6,7 @@ #include #include "qd/stl/string.h" #include "qd/stl/fixed_vector.h" -#include +#include #include diff --git a/libs/amDebugger/src/amDebugger/window/colors_wnd.cpp b/libs/amDebugger/src/amDebugger/window/colors_wnd.cpp index 25562879..c7840a43 100644 --- a/libs/amDebugger/src/amDebugger/window/colors_wnd.cpp +++ b/libs/amDebugger/src/amDebugger/window/colors_wnd.cpp @@ -2,7 +2,7 @@ #include "amDebugger/debuggerWndApp.h" #include "qd/stl/fixed_vector.h" #include -#include "qd/base/color.h" +#include "qd/base/Color.h" #include "qd/imGui/imGui.h" namespace amD { diff --git a/libs/amDebugger/src/amDebugger/window/copper_wnd.cpp b/libs/amDebugger/src/amDebugger/window/copper_wnd.cpp index 2adc0e66..2d9b9616 100644 --- a/libs/amDebugger/src/amDebugger/window/copper_wnd.cpp +++ b/libs/amDebugger/src/amDebugger/window/copper_wnd.cpp @@ -5,7 +5,7 @@ #include "amDebugger/debuggerOps.h" #include "amDebugger/vm/memory.h" #include "amDebugger/vm/vmInterface.h" -#include "qd/base/color.h" +#include "qd/base/Color.h" #include #include "amDebugger/ui/uiStyle.h" #include "amDebugger/ui/uiView.h" diff --git a/libs/amDebugger/src/amDebugger/window/disassembly_wnd.cpp b/libs/amDebugger/src/amDebugger/window/disassembly_wnd.cpp index 3b35f5a0..9494c6f6 100644 --- a/libs/amDebugger/src/amDebugger/window/disassembly_wnd.cpp +++ b/libs/amDebugger/src/amDebugger/window/disassembly_wnd.cpp @@ -99,6 +99,7 @@ void DisassemblyView::drawContentImp() ImGuiTableFlags_SizingFixedFit; // | ImGuiTableFlags_ScrollY; int nReqLine = find_disasm_addr_line_idx(m_vDisasmLines, m_mustViewAddr); + int nDrawStartLine = (nReqLine >= 0) ? nReqLine : 0; // Disasm Ctrl if (ImGui::BeginTable("##disassembly", 4, flags, ImVec2(0, disWndSizeY))) @@ -114,7 +115,7 @@ void DisassemblyView::drawContentImp() const BreakpointsSortedList& bpList = dbg->getBreakpointsSorted(); - for (size_t i = (size_t)nReqLine; i < m_vDisasmLines.size(); ++i) + for (size_t i = (size_t)nDrawStartLine; i < m_vDisasmLines.size(); ++i) { const cda::Item& entry = *m_vDisasmLines[i]; ImGui::TableNextRow(ImGuiTableRowFlags_None, row_min_height); diff --git a/libs/amDebugger/src/amDebugger/window/screen_wnd.cpp b/libs/amDebugger/src/amDebugger/window/screen_wnd.cpp index 7909395a..7b33e6a3 100644 --- a/libs/amDebugger/src/amDebugger/window/screen_wnd.cpp +++ b/libs/amDebugger/src/amDebugger/window/screen_wnd.cpp @@ -23,14 +23,18 @@ void ScreenWnd::drawContentImp() ImGui::EndMenuBar(); } - grabScreenToTexture(dbg); + // Detect resize in progress + ImVec2 curWndSize = ImGui::GetWindowSize(); + bool isResizing = (mLastWndSize.x != curWndSize.x || mLastWndSize.y != curWndSize.y) && mLastWndSize.x > 0; + mLastWndSize = curWndSize; + + if (!isResizing) + grabScreenToTexture(dbg); int scrSizeX = vm->getScreenSizeX(); int scrSizeY = vm->getScreenSizeY(); - float scrollbarSize = ImGui::GetStyle().ScrollbarSize; - ImVec2 scrollingChildSize = ImVec2(ImGui::GetWindowWidth() - scrollbarSize, ImGui::GetWindowHeight() - 30); - if (auto bc = qIm::LockChild("##scrolling", scrollingChildSize, ImGuiChildFlags_None, - ImGuiWindowFlags_HorizontalScrollbar)) + ImVec2 availSize = ImGui::GetContentRegionAvail(); + if (auto bc = qIm::LockChild("##scrolling", availSize, ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar)) { int hPos = vm->getHPos(); int vPos = vm->getVPos(); @@ -38,19 +42,42 @@ void ScreenWnd::drawContentImp() ImGui::Text("VPos:%i HPos:%i cycle:%i", vPos, hPos, cycle); - // screen texture + // Calculate scaled size to fit window while preserving aspect ratio + ImVec2 avail = ImGui::GetContentRegionAvail(); + float displayW = (float)scrSizeX; + float displayH = (float)scrSizeY; + float scale = 1.0f; + if (avail.x > 1.0f && avail.y > 1.0f && scrSizeX > 0 && scrSizeY > 0) + { + float scaleX = avail.x / (float)scrSizeX; + float scaleY = avail.y / (float)scrSizeY; + scale = (scaleX < scaleY) ? scaleX : scaleY; + displayW = (float)scrSizeX * scale; + displayH = (float)scrSizeY * scale; + } + ImVec2 p0 = ImGui::GetCursorScreenPos(); - ImVec2 p1(p0.x + scrSizeX, p0.y + scrSizeY); - ImGui::Image(mTextureId, ImVec2((float)scrSizeX, (float)scrSizeY), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), - ImVec4(1.0f, 1.0f, 1.0f, 1.0f), ImGui::GetStyleColorVec4(ImGuiCol_Border)); + ImVec2 p1(p0.x + displayW, p0.y + displayH); - if (g_dbg_cfg->showVHPopsLines) + if (isResizing) { + // During resize: show placeholder frame ImDrawList* dr = ImGui::GetWindowDrawList(); - // VPOS - dr->AddLine({p0.x, p0.y + (float)vPos}, {p1.x, p0.y + (float)vPos}, qd::Color::MAGENTA75); - // HPOS - dr->AddLine({p0.x + (float)hPos, p0.y}, {p0.x + hPos, p1.y}, qd::Color::MAGENTA75); + dr->AddRect(p0, p1, IM_COL32(128, 128, 128, 255), 0.0f, 0, 2.0f); + ImGui::Dummy(ImVec2(displayW, displayH)); + } + else + { + // Normal: show scaled image + ImGui::Image(mTextureId, ImVec2(displayW, displayH), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), + ImVec4(1.0f, 1.0f, 1.0f, 1.0f), ImGui::GetStyleColorVec4(ImGuiCol_Border)); + + if (g_dbg_cfg->showVHPopsLines) + { + ImDrawList* dr = ImGui::GetWindowDrawList(); + dr->AddLine({p0.x, p0.y + vPos * scale}, {p1.x, p0.y + vPos * scale}, qd::Color::MAGENTA75); + dr->AddLine({p0.x + hPos * scale, p0.y}, {p0.x + hPos * scale, p1.y}, qd::Color::MAGENTA75); + } } } } diff --git a/libs/amDebugger/src/amDebugger/window/screen_wnd.h b/libs/amDebugger/src/amDebugger/window/screen_wnd.h index 666e8c74..a092409f 100644 --- a/libs/amDebugger/src/amDebugger/window/screen_wnd.h +++ b/libs/amDebugger/src/amDebugger/window/screen_wnd.h @@ -10,6 +10,7 @@ class ScreenWnd : public AmDbgWindow QDB_WINDOW_REGISTER(WndId::Screen, amD::window::ScreenWnd, amD::AmDbgWindow); ImTextureID mTextureId = 0; + ImVec2 mLastWndSize = {0, 0}; public: virtual void onCreate(UiViewCreateCtx* cp) override diff --git a/libs/qd/CMakeLists.txt b/libs/qd/CMakeLists.txt index 07a51fb0..a82ba06d 100644 --- a/libs/qd/CMakeLists.txt +++ b/libs/qd/CMakeLists.txt @@ -35,7 +35,11 @@ set(QDICE_SRC) function(add_source_group SUBDIR) set(CURDIR "${QDICE_DIR}/${SUBDIR}") - file(GLOB SUB_FILES_SRC "${CURDIR}/*.cpp" "${CURDIR}/*.h") + # CONFIGURE_DEPENDS: ensures CMake re-evaluates the glob when source files + # are added/removed, so incremental builds always pick up changed sources. + # Without it, file(GLOB) is only evaluated at configure time and new/modified + # files are silently missed until a full cmake reconfigure. + file(GLOB SUB_FILES_SRC CONFIGURE_DEPENDS "${CURDIR}/*.cpp" "${CURDIR}/*.h") # Add files to local lists list(APPEND QDICE_SRC ${SUB_FILES_SRC}) # Pass updated lists to the global scope diff --git a/libs/qd/app/application.h b/libs/qd/app/application.h index 967ed608..9536e864 100644 --- a/libs/qd/app/application.h +++ b/libs/qd/app/application.h @@ -1,6 +1,6 @@ #pragma once #include "qd/app/appMessages.h" -#include "qd/base/EFlow.h" +#include "qd/base/eFlow.h" #include "qd/stl/ref_ptr.h" #include "qd/typeSystem/typeInfoBuilder.h" diff --git a/libs/qd/app/applicationPart.h b/libs/qd/app/applicationPart.h index 32c5d566..18bb679e 100644 --- a/libs/qd/app/applicationPart.h +++ b/libs/qd/app/applicationPart.h @@ -1,5 +1,5 @@ #pragma once -#include "qd/base/EFlow.h" +#include "qd/base/eFlow.h" #include "qd/stl/string.h" #include "qd/typeSystem/attributesCommon.h" #include diff --git a/libs/qd/file/archiveBase.cpp b/libs/qd/file/archiveBase.cpp index 74382a57..e7f29c31 100644 --- a/libs/qd/file/archiveBase.cpp +++ b/libs/qd/file/archiveBase.cpp @@ -1,6 +1,6 @@ #include "qd/file/archiveBase.h" #include "qd/file/archiveSerializer.h" -#include "qd/base/color.h" +#include "qd/base/Color.h" #include "qd/base/endian.h" #include "qd/debug/assert.h" #include "qd/log/log.h" diff --git a/libs/qd/file/archiveBase.h b/libs/qd/file/archiveBase.h index 8cf1fba1..8d5639b5 100644 --- a/libs/qd/file/archiveBase.h +++ b/libs/qd/file/archiveBase.h @@ -3,7 +3,7 @@ #include "fileBase.h" #include "qd/base/base.h" #include "qd/base/baseTypes.h" // SINGLETON -#include "qd/base/tribool.h" +#include "qd/base/Tribool.h" #include "qd/debug/assert.h" #include "qd/debug/exception.h" #include "qd/stl/string.h" diff --git a/libs/qd/file/archiveSerializer.cpp b/libs/qd/file/archiveSerializer.cpp index 820d27f4..2a816b80 100644 --- a/libs/qd/file/archiveSerializer.cpp +++ b/libs/qd/file/archiveSerializer.cpp @@ -1,5 +1,5 @@ #include "qd/file/archiveSerializer.h" -#include "qd/base/color.h" +#include "qd/base/Color.h" #include "qd/debug/assert.h" #include "qd/file/fileBase.h" #include "qd/file/memFile.h" diff --git a/libs/qd/imGui/imGuiContextManager.h b/libs/qd/imGui/imGuiContextManager.h index 9954bfdc..ee99c005 100644 --- a/libs/qd/imGui/imGuiContextManager.h +++ b/libs/qd/imGui/imGuiContextManager.h @@ -1,8 +1,8 @@ #pragma once #include "qd/app/moduleManager.h" #include "qd/base/base.h" -#include "qd/base/color.h" -#include "qd/base/EFlow.h" +#include "qd/base/Color.h" +#include "qd/base/eFlow.h" #include "qd/math/fixedPoint.h" #include diff --git a/libs/qd/imGui/imGuiHelperClass.h b/libs/qd/imGui/imGuiHelperClass.h index 1e8650eb..013f3bcc 100644 --- a/libs/qd/imGui/imGuiHelperClass.h +++ b/libs/qd/imGui/imGuiHelperClass.h @@ -1,7 +1,7 @@ #pragma once #include #include -#include "qd/base/tribool.h" +#include "qd/base/Tribool.h" namespace qIm { diff --git a/libs/qd/imGui/style/style.h b/libs/qd/imGui/style/style.h index 3f6207f1..73b58093 100644 --- a/libs/qd/imGui/style/style.h +++ b/libs/qd/imGui/style/style.h @@ -1,7 +1,7 @@ #pragma once #include "qd/stl/array.h" #include -#include "qd/base/color.h" +#include "qd/base/Color.h" namespace qd { diff --git a/libs/qd/imGui/style/styleDark.cpp b/libs/qd/imGui/style/styleDark.cpp index c07bcee3..e8f82407 100644 --- a/libs/qd/imGui/style/styleDark.cpp +++ b/libs/qd/imGui/style/styleDark.cpp @@ -24,7 +24,7 @@ void imGuiApplyStyleDark() colors[ImGuiCol_TabUnfocused] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f); colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); colors[ImGuiCol_DockingPreview] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f); - colors[ImGuiCol_DockingEmptyBg] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); diff --git a/libs/qd/mem/fnvHash.h b/libs/qd/mem/fnvHash.h index c65dda90..0ba4facd 100644 --- a/libs/qd/mem/fnvHash.h +++ b/libs/qd/mem/fnvHash.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "qd/base/compiler.h" diff --git a/libs/qd/node/node.h b/libs/qd/node/node.h index 2c8143d9..ef62ce96 100644 --- a/libs/qd/node/node.h +++ b/libs/qd/node/node.h @@ -4,7 +4,7 @@ #include #include #include -#include "qd/base/EFlow.h" +#include "qd/base/eFlow.h" namespace qd { diff --git a/libs/qd/qui/controls/window.cpp b/libs/qd/qui/controls/window.cpp index b20127a3..36c7004c 100644 --- a/libs/qd/qui/controls/window.cpp +++ b/libs/qd/qui/controls/window.cpp @@ -1,6 +1,6 @@ #include "window.h" #include "imgui/imgui.h" -#include "qd/base/tribool.h" +#include "qd/base/Tribool.h" namespace qd { diff --git a/libs/qd/qui/controls/window.h b/libs/qd/qui/controls/window.h index 072cae56..9a75a63c 100644 --- a/libs/qd/qui/controls/window.h +++ b/libs/qd/qui/controls/window.h @@ -1,6 +1,6 @@ #pragma once #include "imgui/imgui.h" -#include "qd/math/Point2.h" +#include "qd/math/point2.h" #include "qd/qui/uiNode.h" #include "qd/stl/vector.h" diff --git a/libs/qd/qui/uiNode.h b/libs/qd/qui/uiNode.h index 085dbd4b..f52e133c 100644 --- a/libs/qd/qui/uiNode.h +++ b/libs/qd/qui/uiNode.h @@ -3,7 +3,7 @@ #include "qd/stl/ref_ptr.h" #include "qd/typeSystem/typeDeclare.h" #include "qd/typeSystem/typeInfo.h" -#include "qd/base/EFlow.h" +#include "qd/base/eFlow.h" #include "qd/stl/fixed_vector.h" #include "qd/stl/unique_ptr.h" diff --git a/libs/qd/qui/uiOperation.h b/libs/qd/qui/uiOperation.h index ae37550d..d026a34a 100644 --- a/libs/qd/qui/uiOperation.h +++ b/libs/qd/qui/uiOperation.h @@ -2,7 +2,7 @@ #include "qd/base/base.h" #include "qd/base/baseTypes.h" #include "qd/base/classInfoReg.h" -#include "qd/base/EFlow.h" +#include "qd/base/eFlow.h" #include "qd/debug/assert.h" #include "qd/qui/operationsRegistry.h" #include "qd/stl/fixed_vector.h" diff --git a/libs/uae_lib/drawing.cpp b/libs/uae_lib/drawing.cpp index 204bf32a..0bd6f9c9 100644 --- a/libs/uae_lib/drawing.cpp +++ b/libs/uae_lib/drawing.cpp @@ -4666,6 +4666,8 @@ static void draw_frame2(struct vidbuffer *vbin, struct vidbuffer *vbout) bool firstline = true; int lastline = thisframe_y_adjust_real - (1 << linedbl); for (int i = 0; i < max_ypos_thisframe1; i++) { + if (!amiga2aspect_line_map) + break; int i1 = i + min_ypos_for_screen; int line = i + thisframe_y_adjust_real; int whereline = amiga2aspect_line_map[i1]; diff --git a/libs/uae_lib/main.cpp b/libs/uae_lib/main.cpp index 6eea095b..adeecae1 100644 --- a/libs/uae_lib/main.cpp +++ b/libs/uae_lib/main.cpp @@ -842,6 +842,7 @@ void uae_quit (void) #endif if (quit_program != -UAE_QUIT) quit_program = -UAE_QUIT; + set_special(SPCFLAG_MODE_CHANGE); target_quit (); } diff --git a/libs/uae_lib/newcpu.cpp b/libs/uae_lib/newcpu.cpp index 0e14b6d8..1e237cac 100644 --- a/libs/uae_lib/newcpu.cpp +++ b/libs/uae_lib/newcpu.cpp @@ -6540,11 +6540,14 @@ void m68k_go (int may_quit) cpu_prefs_changed_flag = 0; in_m68k_go++; for (;;) { + if (quit_program < 0) + quit_program = -quit_program; + if (quit_program == UAE_QUIT) + break; + int restored = 0; void (*run_func)(void); - printf("rununing\n"); - cputrace.state = -1; if (regs.halted == CPU_HALT_ACCELERATOR_CPU_FALLBACK) { @@ -6747,11 +6750,6 @@ void m68k_go (int may_quit) #endif run_func(); - if (quit_program < 0) { - quit_program = -quit_program; - } - if (quit_program == UAE_QUIT) - break; } protect_roms(false); mman_set_barriers(true); diff --git a/libs/vAmiga/Core/CMakeLists.txt b/libs/vAmiga/Core/CMakeLists.txt index 88ea33b3..c95be906 100644 --- a/libs/vAmiga/Core/CMakeLists.txt +++ b/libs/vAmiga/Core/CMakeLists.txt @@ -51,13 +51,13 @@ if(MSVC) # target_compile_options(VACore PUBLIC /wd4324) set_source_files_properties(CPU/Moira/Moira.cpp PROPERTIES COMPILE_FLAGS "/wd4127") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - target_compile_options(VACore PUBLIC -Wall -Werror) + target_compile_options(VACore PUBLIC -Wall) target_compile_options(VACore PUBLIC -Wno-restrict) target_compile_options(VACore PUBLIC -Wno-unused-parameter) # target_compile_options(VACore PUBLIC -Wno-array-bounds) target_compile_options(VACore PUBLIC -fconcepts) else() - target_compile_options(VACore PUBLIC -Wall -Werror) + target_compile_options(VACore PUBLIC -Wall) target_compile_options(VACore PUBLIC -Wno-unused-parameter) target_compile_options(VACore PUBLIC -Wno-gnu-anonymous-struct) target_compile_options(VACore PUBLIC -Wno-nested-anon-types) diff --git a/libs/vAmiga/Core/FileSystems/FSStorage.cpp b/libs/vAmiga/Core/FileSystems/FSStorage.cpp index 1cedb930..ebad3aec 100644 --- a/libs/vAmiga/Core/FileSystems/FSStorage.cpp +++ b/libs/vAmiga/Core/FileSystems/FSStorage.cpp @@ -68,7 +68,7 @@ FSStorage::sortedKeys() const result.reserve(blocks.size()); for (const auto& [key, _] : blocks) result.push_back(key); - std::ranges::sort(result); + std::sort(result.begin(), result.end()); return result; } diff --git a/libs/vAmiga_imp_lib/src/qvAmigaImp/va_server_thread.cpp b/libs/vAmiga_imp_lib/src/qvAmigaImp/va_server_thread.cpp index d81a302f..9ad4c8cc 100644 --- a/libs/vAmiga_imp_lib/src/qvAmigaImp/va_server_thread.cpp +++ b/libs/vAmiga_imp_lib/src/qvAmigaImp/va_server_thread.cpp @@ -293,6 +293,9 @@ void VAmServerThread::onVAmigaThreadMain() { for (;;) { if (m_bRequestToQuit) break; + // Process debugger operations and keyboard events + onVAmHandleEvents(); + vamiga::VAmiga *pVAmiga = m_pVAmiga; vamiga::VideoPortAPI &vVideoPort = pVAmiga->videoPort; bool lof, prevlof; diff --git a/libs/vAmiga_imp_lib/src/qvAmigaImp/va_vm_imp.cpp b/libs/vAmiga_imp_lib/src/qvAmigaImp/va_vm_imp.cpp index 1950d0c3..0f0d0fa4 100644 --- a/libs/vAmiga_imp_lib/src/qvAmigaImp/va_vm_imp.cpp +++ b/libs/vAmiga_imp_lib/src/qvAmigaImp/va_vm_imp.cpp @@ -11,6 +11,7 @@ #include "amDebugger/debuggerOps.h" #include "amDebugger/debuggerWndApp.h" +#include "amDebugger/debugger.h" #include "amDebugger/vm/vmInterface.h" #include "qd/base/endian.h" #include "qd/qui/operationsRegistry.h" @@ -47,8 +48,11 @@ VAmVmImp::VAmVmImp(VAmServerThread* pVAmThread, vamiga::VAmiga* pVAmiga) { TSuper::cpu = &instCpu; TSuper::mem = &instMemory; TSuper::custom = &instCustomRegs; + instCustomRegs.m_pVm = this; TSuper::copper = &instCopper; + instCopper.m_pVm = this; TSuper::blitter = &instBlitter; + instBlitter.m_pVm = this; { instEmu.vm = this; TSuper::emu = &instEmu; @@ -106,15 +110,20 @@ VAmVmImp* vm = this; } else if (args->cast_()) { r = true; - // if (::currprefs.turbo_emulation != 0) { - // ::warpmode(0); // off - // } else { - // ::warpmode(2); // on - // } + vamiga::VAmiga* pVAmiga = vm->m_vAmiga; + if (pVAmiga->isWarping()) { + pVAmiga->warpOff(1); // source=1 (non-zero, 0 is reserved for config) + } else { + pVAmiga->warpOn(1); + } + + } else if (args->cast_()) { + r = true; + vm->setVmDebugMode(EVmDebugMode::Break); } else if (args->cast_()) { r = true; - //::uae_reset(1, 1); + vm->m_vAmiga->hardReset(); } else if (auto p = args->cast_()) { r = true; @@ -126,6 +135,10 @@ VAmVmImp* vm = this; qtd::string cmd = qd::string_format("fs %i", p->waitScanLines); pVAm->execConsoleCmd(std::move(cmd)); return qd::EFlow::SUCCESS; + } else if (auto p = args->cast_()) { + r = true; + pVAm->execConsoleCmd(qtd::string(p->cmd)); + } else if (args->cast_()) { r = true; // if (pVAm->isWndAlwaysOnTop()) { @@ -140,113 +153,149 @@ VAmVmImp* vm = this; void* VAmVmImp::Blitter::getScreenPixBuf(int mon_id, int* out_size_w, int* out_size_h, int* pitch) { - return nullptr; -#if 0 - vidbuf_description* vidinfo = &adisplays[mon_id].gfxvidinfo; - vidbuffer* vb = &vidinfo->drawbuffer; - if (!vb || !vb->bufmem) - return nullptr; - *out_size_w = vb->outwidth; - *out_size_h = vb->outheight; - *pitch = vb->rowbytes; - return vb->bufmem; -#endif // + // Access the display texture buffer from VAmServerThread + VAmServerThread* pThread = m_pVm->m_pVAmThread; + if (!pThread || !pThread->m_pAmigaBuffer) return nullptr; + + *out_size_w = pThread->m_scrWidth; // HPIXELS (912) + *out_size_h = pThread->m_scrHeight; // VPIXELS (313) + *pitch = pThread->m_scrWidth * sizeof(uint32_t); + return pThread->m_pAmigaBuffer; } bool VAmVmImp::Blitter::isBlitterActive() const { - return false; // blt_info.blit_main || blt_info.blit_finald || - // blt_info.blit_queued; + return m_pVm->m_vAmiga->agnus.blitter.getInfo().bbusy; } void VAmVmImp::CustomRegs::fetch() { - // size_t dump_len; - // ::save_custom(&dump_len, (uae_u8*)regsData.data(), 1); - // for (size_t i = 0; i < regsData.size(); ++i) - // qd::swapBytes_<2>(®sData[i]); + // Read each custom register via vAmiga's spypeek16 API + // cust_reg_data maps each CustReg enum to its hardware address (0xDFFxxx) + for (size_t i = 0; i < CustReg::_COUNT_; ++i) { + uint32_t addr = CustReg::cust_reg_data[i].addr; + regsData[i + data_offset] = + m_pVm->m_vAmiga->mem.debugger.spypeek16(vamiga::Accessor::CPU, addr); + } } void VAmVmImp::CustomRegs::commit() { - // eastl::fixed_vector - // dst = {regsData.begin(), regsData.end()}; uint8_t* beg = - // (uint8_t*)dst.begin(); dst.erase((uint16_t*)(beg + 0x120), - // (uint16_t*)(beg + 0x180)); dst.erase((uint16_t*)(beg + 0x0A0), - // (uint16_t*)(beg + 0x0E0)); - // - // for (size_t i = 0; i < dst.size(); ++i) - // qd::swapBytes_<2>(&dst[i]); - // ::restore_custom((uae_u8*)dst.data()); + // Write modified register values back through vAmiga's memory subsystem. + // Note: Many custom registers are read-only (e.g., VPOSR, DMACONR). + // Writing to read-only addresses is harmless — the hardware ignores writes. + // Only write registers that differ from their fetched values. + for (size_t i = 0; i < CustReg::_COUNT_; ++i) { + uint32_t addr = CustReg::cust_reg_data[i].addr; + // Use the memory write path — writes to custom registers go through Agnus + m_pVm->mem->setU16(addr, regsData[i + data_offset]); + } } amD::AddrRef VAmVmImp::Copper::getCopperAddr(IVm::ECopperAddr_ copno) { - return 0; // ::get_copper_address(copno); + switch (copno) { + case IVm::CopperAddr_cop1lc: return m_copInfo.cop1lc; + case IVm::CopperAddr_cop2lc: return m_copInfo.cop2lc; + case IVm::CopperAddr_ip: return m_copInfo.coppc0; + case IVm::CopperAddr_vblankip: return m_copInfo.cop1lc; // vblank starts copper list 1 + default: return 0; + } } -void VAmVmImp::Copper::fetch() {} +void VAmVmImp::Copper::fetch() { + m_copInfo = m_pVm->m_vAmiga->agnus.copper.getInfo(); +} int VAmVmImp::Emu::getDebugDmaMode() { return 0; // ::debug_dma; } +void VAmVmImp::Emu::initBreakPoints(amD::BreakpointsSortedList& bpList) { + bpList.mBreakpoints.clear(); + + vamiga::VAmiga* pVAmiga = vm->m_vAmiga; + long count = (long)pVAmiga->cpu.breakpoints.elements(); + + for (long i = 0; i < count; ++i) { + auto guardInfo = pVAmiga->cpu.breakpoints.guardNr(i); + if (!guardInfo.has_value()) continue; + if (!guardInfo->enabled) continue; + + amD::Breakpoint& curBp = bpList.mBreakpoints.emplace_back(); + curBp.addr1 = guardInfo->addr; + curBp.addr2 = 0; + curBp.enabled = guardInfo->enabled; + curBp.reg = IVm::EReg::PC; // vAmiga breakpoints are address-based + + amD::BreakpointsSortedList::OneAddrBp bp; + bp.addr = curBp.addr1; + bp.bpIdx = (int)bpList.mBreakpoints.size() - 1; + bpList.mOneAddrBps.insert(bp); + } +} + void VAmVmImp::Emu::setDebugDmaMode(int p_mode) { //::debug_dma = p_mode; } void VAmVmImp::setVmDebugMode(EVmDebugMode debug_mode) { TSuper::setVmDebugMode(debug_mode); -// if (debug_mode == EVmDebugMode::Break) { -// while (!instEmu.isDebugActivatedFull()) { -// // ::debugger_active = 0; -// // ::debugging = 0; -// // ::activate_debugger_new(); -// } -// } else if (debug_mode == EVmDebugMode::Live) { -// if (m_pVAmThread) m_pVAmThread->execConsoleCmd("g"); -// // ::debugger_active = 0; -// } + vamiga::VAmiga* pVAmiga = m_vAmiga; + if (debug_mode == EVmDebugMode::Break) { + pVAmiga->pause(); + } else if (debug_mode == EVmDebugMode::Live) { + pVAmiga->run(); + } } -int VAmVmImp::getVPos() { return 0; } +int VAmVmImp::getVPos() { + return (int)m_vAmiga->amiga.getInfo().vpos; +} int VAmVmImp::getHPos() { - return 0; // ::current_hpos_safe(); // ::current_hpos(); + return (int)m_vAmiga->amiga.getInfo().hpos; } int VAmVmImp::getCurCycle() { - // int c = (int)((::get_cycles() - ::vsync_cycles) / CYCLE_UNIT); - return 0; // c; + // Return horizontal position as the cycle count within the current scanline + return (int)m_vAmiga->amiga.getInfo().hpos; } bool VAmVmImp::Emu::isDebugActivated() const { - return 0; //::debugging > 0 && (::debugger_active > 0); + // Debugger is active when the emulator is paused (breakpoint hit, manual break) + return vm->m_vAmiga->isPaused(); } bool VAmVmImp::Emu::isDebugActivatedFull() const { - return 0; //::debugging > 0 && (::debugger_active > 0 && ::regs.spcflags & - //: SPCFLAG_BRK); + // Fully activated = paused AND debugger is in Break mode + return vm->m_vAmiga->isPaused() && vm->getVmDebugMode() == EVmDebugMode::Break; } bool VAmVmImp::Floppy::getEnabled() { - //::floppyslot& cfgFloppy = ::changed_prefs.floppyslots[m_nFloppy]; - return 0; // cfgFloppy.dfxtype >= 0; + return m_pVm->m_vAmiga->df[m_nFloppy]->getConfig().connected; } void VAmVmImp::Floppy::setEnabled(bool v) { - // ::floppyslot& cfgFloppy = ::changed_prefs.floppyslots[m_nFloppy]; - // cfgFloppy.dfxtype = v ? 0 : -1; + try { + m_pVm->m_vAmiga->emu->set(vamiga::Opt::DRIVE_CONNECT, v ? 1 : 0, {m_nFloppy}); + } catch (...) { + // Ignore config errors (e.g., cannot disconnect drive 0) + } } void VAmVmImp::Floppy::setAdfPath(const qtd::string &v) { - vamiga::FloppyDriveAPI *df = m_pVm->m_vAmiga->df[m_nFloppy]; - df->insert(v.c_str(), m_writeProtect); + m_adfPath = v; + if (v.empty()) { + m_pVm->m_vAmiga->df[m_nFloppy]->ejectDisk(); + } else { + vamiga::FloppyDriveAPI *df = m_pVm->m_vAmiga->df[m_nFloppy]; + df->insert(v.c_str(), m_writeProtect); + } } qtd::string VAmVmImp::Floppy::getAdfPath() { - //vamiga::FloppyDriveAPI *df = m_pVm->m_vAmiga->df[m_nFloppy]; - return ""; + return m_adfPath; } @@ -255,6 +304,17 @@ void VAmVmImp::Floppy::init(IVm::VM *vm) m_pVm = static_cast(vm); } +bool VAmVmImp::Floppy::getWriteProtect() +{ + return m_pVm->m_vAmiga->df[m_nFloppy]->getInfo().hasProtectedDisk; +} + +void VAmVmImp::Floppy::setWriteProtect(bool v) +{ + m_writeProtect = v; + m_pVm->m_vAmiga->df[m_nFloppy]->setFlag(vamiga::DiskFlags::PROTECTED, v); +} + bool VAmVmImp::Cpu::getFlg(ECpuFlg_ f) const { switch (f) { @@ -308,6 +368,34 @@ uint8_t* VAmVmImp::Memory::getRealAddr(AddrRef ptr) { } } +uint16_t VAmVmImp::Memory::getU16(AddrRef addr) { + return m_pVm->m_vAmiga->mem.debugger.spypeek16(vamiga::Accessor::CPU, (uint32_t)addr); +} + +bool VAmVmImp::Memory::getU16(AddrRef addr, uint16_t* out) { + *out = getU16(addr); + return true; +} + +void VAmVmImp::Memory::setU16(AddrRef addr, uint16_t v) { + // Find the bank for this address and write directly + const IVm::MemBank* pBank = findBankByAddr(addr); + if (!pBank || !pBank->m_realAddr) return; + // Write in big-endian (Amiga is 68000 big-endian) + uint8_t* ptr = pBank->m_realAddr + (addr - pBank->m_startAddr); + ptr[0] = (uint8_t)(v >> 8); + ptr[1] = (uint8_t)(v & 0xFF); +} + +uint32_t VAmVmImp::Memory::getU32(AddrRef addr) { + return ((uint32_t)getU16(addr) << 16) | (uint32_t)getU16(addr + 2); +} + +void VAmVmImp::Memory::setU32(AddrRef addr, uint32_t v) { + setU16(addr, (uint16_t)(v >> 16)); + setU16(addr + 2, (uint16_t)(v & 0xFFFF)); +} + void VAmVmImp::Memory::init(IVm::VM* p_vm) { m_pVm = (VAmVmImp*)p_vm; vamiga::VAmiga* pVAmiga = m_pVm->m_vAmiga; diff --git a/libs/vAmiga_imp_lib/src/qvAmigaImp/va_vm_imp.h b/libs/vAmiga_imp_lib/src/qvAmigaImp/va_vm_imp.h index b56d998b..a5756337 100644 --- a/libs/vAmiga_imp_lib/src/qvAmigaImp/va_vm_imp.h +++ b/libs/vAmiga_imp_lib/src/qvAmigaImp/va_vm_imp.h @@ -66,7 +66,7 @@ class VAmVmImp final : public IVm::VM { virtual bool getFlg(ECpuFlg_ f) const override; virtual int getIntMask() const override { - return 0; //regs.intmask; + return (m_pCpuInfo->sr >> 8) & 7; // IPL bits from SR } virtual void fetch() override { @@ -85,28 +85,18 @@ class VAmVmImp final : public IVm::VM { public: virtual void init(IVm::VM* p_vm) override; virtual uint8_t* getRealAddr(AddrRef ptr) override; - virtual bool getU16(AddrRef addr, uint16_t* out) override { - *out = false; //(uint16_t)::memory_get_word(addr); - return true; - } - virtual uint16_t getU16(AddrRef addr) override { - return 0; //(uint16_t)::memory_get_word(addr); - } - virtual void setU16(AddrRef addr, uint16_t v) override { - //::memory_put_word(addr, v); - } - virtual uint32_t getU32(AddrRef addr) override { - return 0; //(uint32_t)::memory_get_long(addr); - } - virtual void setU32(AddrRef addr, uint32_t v) override { - //::memory_put_long(addr, v); - } + virtual bool getU16(AddrRef addr, uint16_t* out) override; + virtual uint16_t getU16(AddrRef addr) override; + virtual void setU16(AddrRef addr, uint16_t v) override; + virtual uint32_t getU32(AddrRef addr) override; + virtual void setU32(AddrRef addr, uint32_t v) override; }; // struct Memory Memory instMemory; //------------------------------------------------------------------------ struct Blitter final : public IVm::Blitter { + VAmVmImp* m_pVm = nullptr; public: virtual bool isBlitterActive() const override; virtual void* getScreenPixBuf(int mon_id, int* out_size_w, int* out_size_h, int* pitch) override; @@ -117,6 +107,8 @@ class VAmVmImp final : public IVm::VM { class CustomRegs final : public IVm::CustomRegs { static constexpr size_t data_offset = 2; eastl::array regsData; + public: + VAmVmImp* m_pVm = nullptr; public: void fetch() override; @@ -135,6 +127,8 @@ class VAmVmImp final : public IVm::VM { //------------------------------------------------------------------------ class Copper final : public IVm::Copper { public: + VAmVmImp* m_pVm = nullptr; + vamiga::CopperInfo m_copInfo = {}; virtual void fetch() override; virtual AddrRef getCopperAddr(IVm::ECopperAddr_ copno) override; }; // class Copper @@ -150,9 +144,10 @@ class VAmVmImp final : public IVm::VM { bool isDebugActivatedFull() const; bool isDebugActivated() const; virtual void getScreenSize(int* out_w, int* out_h) const override { - *out_w = 754; - *out_h = 576; + *out_w = (int)vamiga::HPIXELS; // 912 + *out_h = (int)vamiga::VPIXELS; // 313 } + virtual void initBreakPoints(amD::BreakpointsSortedList& bpList) override; }; // class Emu Emu instEmu; @@ -161,14 +156,12 @@ class VAmVmImp final : public IVm::VM { class Floppy : public IVm::Floppy { VAmVmImp *m_pVm = nullptr; bool m_writeProtect = false; + qtd::string m_adfPath; public: virtual bool getEnabled() override; virtual void setEnabled(bool v) override; - virtual bool getWriteProtect() override { - return false; - } - virtual void setWriteProtect(bool v) override { - } + virtual bool getWriteProtect() override; + virtual void setWriteProtect(bool v) override; virtual qtd::string getAdfPath() override; virtual void setAdfPath(const qtd::string& v) override; diff --git a/src/quasar_app/crashhandler/crashhandler.cpp b/src/quasar_app/crashhandler/crashhandler.cpp new file mode 100644 index 00000000..6b75a89d --- /dev/null +++ b/src/quasar_app/crashhandler/crashhandler.cpp @@ -0,0 +1,22 @@ +#include "crashhandler.h" + +#if defined(_WIN32) + #include "crashhandler_windows.h" +#elif defined(__APPLE__) + #include "crashhandler_macos.h" +#elif defined(__linux__) + #include "crashhandler_linux.h" +#endif + +CrashHandler* CrashHandler::create() +{ +#if defined(_WIN32) + return new CrashHandlerWindows(); +#elif defined(__APPLE__) + return new CrashHandlerMacOS(); +#elif defined(__linux__) + return new CrashHandlerLinux(); +#else + #error "Unsupported platform" +#endif +} diff --git a/src/quasar_app/crashhandler/crashhandler.h b/src/quasar_app/crashhandler/crashhandler.h new file mode 100644 index 00000000..c92940ad --- /dev/null +++ b/src/quasar_app/crashhandler/crashhandler.h @@ -0,0 +1,12 @@ +#pragma once + +class CrashHandler +{ +public: + virtual ~CrashHandler() = default; + virtual void install() = 0; + virtual void uninstall() = 0; + + // Returns a platform-specific implementation. Caller owns the pointer. + static CrashHandler* create(); +}; diff --git a/src/quasar_app/crashhandler/crashhandler_linux.cpp b/src/quasar_app/crashhandler/crashhandler_linux.cpp new file mode 100644 index 00000000..738edb3a --- /dev/null +++ b/src/quasar_app/crashhandler/crashhandler_linux.cpp @@ -0,0 +1,248 @@ +#if defined(__linux__) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "crashhandler_linux.h" + +static const int WATCHED_SIGNALS[] = { SIGSEGV, SIGBUS, SIGABRT, SIGILL, SIGFPE }; +static const int WATCHED_SIGNALS_COUNT = sizeof(WATCHED_SIGNALS) / sizeof(WATCHED_SIGNALS[0]); + +static const char* signalName(int sig) +{ + switch (sig) + { + case SIGSEGV: return "SIGSEGV (segmentation fault)"; + case SIGBUS: return "SIGBUS (bus error)"; + case SIGABRT: return "SIGABRT (abort)"; + case SIGILL: return "SIGILL (illegal instruction)"; + case SIGFPE: return "SIGFPE (floating-point exception)"; + default: return "unknown signal"; + } +} + +static const char* sigCodeName(int sig, int code) +{ + if (sig == SIGSEGV) + { + switch (code) + { + case SEGV_MAPERR: return "address not mapped"; + case SEGV_ACCERR: return "invalid permissions"; + default: return "unknown"; + } + } + if (sig == SIGBUS) + { + switch (code) + { + case BUS_ADRALN: return "invalid address alignment"; + case BUS_ADRERR: return "nonexistent physical address"; + case BUS_OBJERR: return "object-specific hardware error"; + default: return "unknown"; + } + } + if (sig == SIGFPE) + { + switch (code) + { + case FPE_INTDIV: return "integer divide by zero"; + case FPE_INTOVF: return "integer overflow"; + case FPE_FLTDIV: return "floating-point divide by zero"; + case FPE_FLTOVF: return "floating-point overflow"; + case FPE_FLTUND: return "floating-point underflow"; + case FPE_FLTRES: return "floating-point inexact result"; + case FPE_FLTINV: return "invalid floating-point operation"; + default: return "unknown"; + } + } + return ""; +} + +// All output goes through write() — async-signal-safe, bypasses all loggers. +static void writeErr(const char* msg) +{ + write(STDERR_FILENO, msg, strlen(msg)); +} + +static void writeRegisters(ucontext_t* uc, char* buf, size_t bufSize) +{ + if (!uc) return; + +#if defined(__x86_64__) + const mcontext_t& mc = uc->uc_mcontext; + snprintf(buf, bufSize, + "\nRegisters:\n" + " RAX=%016llx RBX=%016llx RCX=%016llx RDX=%016llx\n" + " RSI=%016llx RDI=%016llx RBP=%016llx RSP=%016llx\n" + " R8 =%016llx R9 =%016llx R10=%016llx R11=%016llx\n" + " R12=%016llx R13=%016llx R14=%016llx R15=%016llx\n" + " RIP=%016llx EFLAGS=%016llx\n", + (unsigned long long)mc.gregs[REG_RAX], (unsigned long long)mc.gregs[REG_RBX], + (unsigned long long)mc.gregs[REG_RCX], (unsigned long long)mc.gregs[REG_RDX], + (unsigned long long)mc.gregs[REG_RSI], (unsigned long long)mc.gregs[REG_RDI], + (unsigned long long)mc.gregs[REG_RBP], (unsigned long long)mc.gregs[REG_RSP], + (unsigned long long)mc.gregs[REG_R8], (unsigned long long)mc.gregs[REG_R9], + (unsigned long long)mc.gregs[REG_R10], (unsigned long long)mc.gregs[REG_R11], + (unsigned long long)mc.gregs[REG_R12], (unsigned long long)mc.gregs[REG_R13], + (unsigned long long)mc.gregs[REG_R14], (unsigned long long)mc.gregs[REG_R15], + (unsigned long long)mc.gregs[REG_RIP], (unsigned long long)mc.gregs[REG_EFL]); +#elif defined(__i386__) + const mcontext_t& mc = uc->uc_mcontext; + snprintf(buf, bufSize, + "\nRegisters:\n" + " EAX=%08x EBX=%08x ECX=%08x EDX=%08x\n" + " ESI=%08x EDI=%08x EBP=%08x ESP=%08x\n" + " EIP=%08x EFLAGS=%08x\n", + (unsigned)mc.gregs[REG_EAX], (unsigned)mc.gregs[REG_EBX], + (unsigned)mc.gregs[REG_ECX], (unsigned)mc.gregs[REG_EDX], + (unsigned)mc.gregs[REG_ESI], (unsigned)mc.gregs[REG_EDI], + (unsigned)mc.gregs[REG_EBP], (unsigned)mc.gregs[REG_ESP], + (unsigned)mc.gregs[REG_EIP], (unsigned)mc.gregs[REG_EFL]); +#elif defined(__aarch64__) + const mcontext_t& mc = uc->uc_mcontext; + snprintf(buf, bufSize, + "\nRegisters:\n" + " X0 =%016llx X1 =%016llx X2 =%016llx X3 =%016llx\n" + " X4 =%016llx X5 =%016llx X6 =%016llx X7 =%016llx\n" + " X8 =%016llx X9 =%016llx X10=%016llx X11=%016llx\n" + " X12=%016llx X13=%016llx X14=%016llx X15=%016llx\n" + " X16=%016llx X17=%016llx X18=%016llx X19=%016llx\n" + " X20=%016llx X21=%016llx X22=%016llx X23=%016llx\n" + " X24=%016llx X25=%016llx X26=%016llx X27=%016llx\n" + " X28=%016llx X29=%016llx X30=%016llx SP =%016llx\n" + " PC =%016llx\n", + (unsigned long long)mc.regs[0], (unsigned long long)mc.regs[1], + (unsigned long long)mc.regs[2], (unsigned long long)mc.regs[3], + (unsigned long long)mc.regs[4], (unsigned long long)mc.regs[5], + (unsigned long long)mc.regs[6], (unsigned long long)mc.regs[7], + (unsigned long long)mc.regs[8], (unsigned long long)mc.regs[9], + (unsigned long long)mc.regs[10], (unsigned long long)mc.regs[11], + (unsigned long long)mc.regs[12], (unsigned long long)mc.regs[13], + (unsigned long long)mc.regs[14], (unsigned long long)mc.regs[15], + (unsigned long long)mc.regs[16], (unsigned long long)mc.regs[17], + (unsigned long long)mc.regs[18], (unsigned long long)mc.regs[19], + (unsigned long long)mc.regs[20], (unsigned long long)mc.regs[21], + (unsigned long long)mc.regs[22], (unsigned long long)mc.regs[23], + (unsigned long long)mc.regs[24], (unsigned long long)mc.regs[25], + (unsigned long long)mc.regs[26], (unsigned long long)mc.regs[27], + (unsigned long long)mc.regs[28], (unsigned long long)mc.regs[29], + (unsigned long long)mc.regs[30], (unsigned long long)mc.sp, + (unsigned long long)mc.pc); +#else + snprintf(buf, bufSize, "\nRegisters: (unsupported architecture)\n"); +#endif + writeErr(buf); +} + +static pid_t getThreadId() +{ +#if defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 30)) + return gettid(); +#else + return static_cast(syscall(SYS_gettid)); +#endif +} + +static void posixSignalHandler(int sig, siginfo_t* info, void* context) +{ + void* frames[64]; + int frameCount = backtrace(frames, 64); + ucontext_t* uc = static_cast(context); + + char line[512]; + char regBuf[2048]; + writeErr("\n*** UNHANDLED SIGNAL (Quaesar) ***\n"); + + snprintf(line, sizeof(line), " Signal : %d %s\n", sig, signalName(sig)); + writeErr(line); + + if (info) + { + const char* codeName = sigCodeName(sig, info->si_code); + if (codeName[0]) + { + snprintf(line, sizeof(line), " Code : %d (%s)\n", info->si_code, codeName); + writeErr(line); + } + snprintf(line, sizeof(line), " Address : %p\n", info->si_addr); + writeErr(line); + } + + snprintf(line, sizeof(line), " PID/TID : %d / %d\n", getpid(), getThreadId()); + writeErr(line); + + writeRegisters(uc, regBuf, sizeof(regBuf)); + + writeErr("\nStack Trace:\n"); + backtrace_symbols_fd(frames, frameCount, STDERR_FILENO); + + // Write identical content to a log file in /tmp + time_t now = time(nullptr); + char logPath[64]; + snprintf(logPath, sizeof(logPath), "/tmp/quaesar_crash_%ld_%d.log", (long)now, getpid()); + + FILE* f = fopen(logPath, "w"); + if (f) + { + fprintf(f, "*** UNHANDLED SIGNAL (Quaesar) ***\n"); + fprintf(f, " Signal : %d %s\n", sig, signalName(sig)); + if (info) + { + const char* codeName = sigCodeName(sig, info->si_code); + if (codeName[0]) + fprintf(f, " Code : %d (%s)\n", info->si_code, codeName); + fprintf(f, " Address : %p\n", info->si_addr); + } + fprintf(f, " PID/TID : %d / %d\n", getpid(), getThreadId()); + fprintf(f, "%s", regBuf); + fprintf(f, "\nStack Trace:\n"); + char** syms = backtrace_symbols(frames, frameCount); + if (syms) + { + for (int i = 0; i < frameCount; i++) + fprintf(f, " %s\n", syms[i]); + free(syms); + } + fclose(f); + + snprintf(line, sizeof(line), "\n Log : %s\n", logPath); + writeErr(line); + } + + writeErr("***********************************\n\n"); + + // Re-raise so the OS generates a core dump with the real signal + signal(sig, SIG_DFL); + raise(sig); +} + +void CrashHandlerLinux::install() +{ + struct sigaction sa = {}; + sa.sa_sigaction = posixSignalHandler; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + + for (int i = 0; i < WATCHED_SIGNALS_COUNT; i++) + sigaction(WATCHED_SIGNALS[i], &sa, nullptr); +} + +void CrashHandlerLinux::uninstall() +{ + struct sigaction sa = {}; + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + + for (int i = 0; i < WATCHED_SIGNALS_COUNT; i++) + sigaction(WATCHED_SIGNALS[i], &sa, nullptr); +} + +#endif // __linux__ diff --git a/src/quasar_app/crashhandler/crashhandler_linux.h b/src/quasar_app/crashhandler/crashhandler_linux.h new file mode 100644 index 00000000..a57abd8b --- /dev/null +++ b/src/quasar_app/crashhandler/crashhandler_linux.h @@ -0,0 +1,13 @@ +#pragma once +#if defined(__linux__) + +#include "crashhandler.h" + +class CrashHandlerLinux : public CrashHandler +{ +public: + void install() override; + void uninstall() override; +}; + +#endif // __linux__ diff --git a/src/quasar_app/crashhandler/crashhandler_macos.cpp b/src/quasar_app/crashhandler/crashhandler_macos.cpp new file mode 100644 index 00000000..70e90101 --- /dev/null +++ b/src/quasar_app/crashhandler/crashhandler_macos.cpp @@ -0,0 +1,215 @@ +#ifdef __APPLE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "crashhandler_macos.h" + +static const int WATCHED_SIGNALS[] = { SIGSEGV, SIGBUS, SIGABRT, SIGILL, SIGFPE }; +static const int WATCHED_SIGNALS_COUNT = sizeof(WATCHED_SIGNALS) / sizeof(WATCHED_SIGNALS[0]); + +static const char* signalName(int sig) +{ + switch (sig) + { + case SIGSEGV: return "SIGSEGV (segmentation fault)"; + case SIGBUS: return "SIGBUS (bus error)"; + case SIGABRT: return "SIGABRT (abort)"; + case SIGILL: return "SIGILL (illegal instruction)"; + case SIGFPE: return "SIGFPE (floating-point exception)"; + default: return "unknown signal"; + } +} + +static const char* sigCodeName(int sig, int code) +{ + if (sig == SIGSEGV) + { + switch (code) + { + case SEGV_MAPERR: return "address not mapped"; + case SEGV_ACCERR: return "invalid permissions"; + default: return "unknown"; + } + } + if (sig == SIGBUS) + { + switch (code) + { + case BUS_ADRALN: return "invalid address alignment"; + case BUS_ADRERR: return "nonexistent physical address"; + case BUS_OBJERR: return "object-specific hardware error"; + default: return "unknown"; + } + } + if (sig == SIGFPE) + { + switch (code) + { + case FPE_INTDIV: return "integer divide by zero"; + case FPE_INTOVF: return "integer overflow"; + case FPE_FLTDIV: return "floating-point divide by zero"; + case FPE_FLTOVF: return "floating-point overflow"; + case FPE_FLTUND: return "floating-point underflow"; + case FPE_FLTRES: return "floating-point inexact result"; + case FPE_FLTINV: return "invalid floating-point operation"; + default: return "unknown"; + } + } + return ""; +} + +// All output goes through write() — async-signal-safe, bypasses all loggers. +static void writeErr(const char* msg) +{ + write(STDERR_FILENO, msg, strlen(msg)); +} + +static void writeRegisters(ucontext_t* uc, char* buf, size_t bufSize) +{ + if (!uc) return; + +#if defined(__x86_64__) + const auto& mc = *uc->uc_mcontext; + snprintf(buf, bufSize, + "\nRegisters:\n" + " RAX=%016llx RBX=%016llx RCX=%016llx RDX=%016llx\n" + " RSI=%016llx RDI=%016llx RBP=%016llx RSP=%016llx\n" + " R8 =%016llx R9 =%016llx R10=%016llx R11=%016llx\n" + " R12=%016llx R13=%016llx R14=%016llx R15=%016llx\n" + " RIP=%016llx RFLAGS=%016llx\n", + mc.__ss.__rax, mc.__ss.__rbx, mc.__ss.__rcx, mc.__ss.__rdx, + mc.__ss.__rsi, mc.__ss.__rdi, mc.__ss.__rbp, mc.__ss.__rsp, + mc.__ss.__r8, mc.__ss.__r9, mc.__ss.__r10, mc.__ss.__r11, + mc.__ss.__r12, mc.__ss.__r13, mc.__ss.__r14, mc.__ss.__r15, + mc.__ss.__rip, mc.__ss.__rflags); +#elif defined(__aarch64__) + const auto& mc = *uc->uc_mcontext; + snprintf(buf, bufSize, + "\nRegisters:\n" + " X0 =%016llx X1 =%016llx X2 =%016llx X3 =%016llx\n" + " X4 =%016llx X5 =%016llx X6 =%016llx X7 =%016llx\n" + " X8 =%016llx X9 =%016llx X10=%016llx X11=%016llx\n" + " X12=%016llx X13=%016llx X14=%016llx X15=%016llx\n" + " X16=%016llx X17=%016llx X18=%016llx X19=%016llx\n" + " X20=%016llx X21=%016llx X22=%016llx X23=%016llx\n" + " X24=%016llx X25=%016llx X26=%016llx X27=%016llx\n" + " X28=%016llx FP =%016llx LR =%016llx SP =%016llx\n" + " PC =%016llx CPSR=%08x\n", + mc.__ss.__x[0], mc.__ss.__x[1], mc.__ss.__x[2], mc.__ss.__x[3], + mc.__ss.__x[4], mc.__ss.__x[5], mc.__ss.__x[6], mc.__ss.__x[7], + mc.__ss.__x[8], mc.__ss.__x[9], mc.__ss.__x[10], mc.__ss.__x[11], + mc.__ss.__x[12], mc.__ss.__x[13], mc.__ss.__x[14], mc.__ss.__x[15], + mc.__ss.__x[16], mc.__ss.__x[17], mc.__ss.__x[18], mc.__ss.__x[19], + mc.__ss.__x[20], mc.__ss.__x[21], mc.__ss.__x[22], mc.__ss.__x[23], + mc.__ss.__x[24], mc.__ss.__x[25], mc.__ss.__x[26], mc.__ss.__x[27], + mc.__ss.__x[28], mc.__ss.__fp, mc.__ss.__lr, mc.__ss.__sp, + mc.__ss.__pc, mc.__ss.__cpsr); +#else + snprintf(buf, bufSize, "\nRegisters: (unsupported architecture)\n"); +#endif + writeErr(buf); +} + +static void posixSignalHandler(int sig, siginfo_t* info, void* context) +{ + void* frames[64]; + int frameCount = backtrace(frames, 64); + ucontext_t* uc = static_cast(context); + + char line[512]; + char regBuf[2048]; + writeErr("\n*** UNHANDLED SIGNAL (Quaesar) ***\n"); + + snprintf(line, sizeof(line), " Signal : %d %s\n", sig, signalName(sig)); + writeErr(line); + + if (info) + { + const char* codeName = sigCodeName(sig, info->si_code); + if (codeName[0]) + { + snprintf(line, sizeof(line), " Code : %d (%s)\n", info->si_code, codeName); + writeErr(line); + } + snprintf(line, sizeof(line), " Address : %p\n", info->si_addr); + writeErr(line); + } + + snprintf(line, sizeof(line), " PID/TID : %d / %u\n", getpid(), mach_thread_self()); + writeErr(line); + + writeRegisters(uc, regBuf, sizeof(regBuf)); + + writeErr("\nStack Trace:\n"); + backtrace_symbols_fd(frames, frameCount, STDERR_FILENO); + + // Write identical content to a log file in /tmp + time_t now = time(nullptr); + char logPath[256]; + snprintf(logPath, sizeof(logPath), "/tmp/quaesar_crash_%ld_%d.log", (long)now, getpid()); + + FILE* f = fopen(logPath, "w"); + if (f) + { + fprintf(f, "*** UNHANDLED SIGNAL (Quaesar) ***\n"); + fprintf(f, " Signal : %d %s\n", sig, signalName(sig)); + if (info) + { + const char* codeName = sigCodeName(sig, info->si_code); + if (codeName[0]) + fprintf(f, " Code : %d (%s)\n", info->si_code, codeName); + fprintf(f, " Address : %p\n", info->si_addr); + } + fprintf(f, " PID/TID : %d / %u\n", getpid(), mach_thread_self()); + fprintf(f, "%s", regBuf); + fprintf(f, "\nStack Trace:\n"); + char** syms = backtrace_symbols(frames, frameCount); + if (syms) + { + for (int i = 0; i < frameCount; i++) + fprintf(f, " %s\n", syms[i]); + free(syms); + } + fclose(f); + + snprintf(line, sizeof(line), "\n Log : %s\n", logPath); + writeErr(line); + } + + writeErr("***********************************\n\n"); + + // Re-raise so the OS generates a core dump with the real signal + signal(sig, SIG_DFL); + raise(sig); +} + +void CrashHandlerMacOS::install() +{ + struct sigaction sa = {}; + sa.sa_sigaction = posixSignalHandler; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + + for (int i = 0; i < WATCHED_SIGNALS_COUNT; i++) + sigaction(WATCHED_SIGNALS[i], &sa, nullptr); +} + +void CrashHandlerMacOS::uninstall() +{ + struct sigaction sa = {}; + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + + for (int i = 0; i < WATCHED_SIGNALS_COUNT; i++) + sigaction(WATCHED_SIGNALS[i], &sa, nullptr); +} + +#endif // __APPLE__ diff --git a/src/quasar_app/crashhandler/crashhandler_macos.h b/src/quasar_app/crashhandler/crashhandler_macos.h new file mode 100644 index 00000000..03703ef0 --- /dev/null +++ b/src/quasar_app/crashhandler/crashhandler_macos.h @@ -0,0 +1,13 @@ +#pragma once +#ifdef __APPLE__ + +#include "crashhandler.h" + +class CrashHandlerMacOS : public CrashHandler +{ +public: + void install() override; + void uninstall() override; +}; + +#endif // __APPLE__ diff --git a/src/quasar_app/crashhandler/crashhandler_windows.cpp b/src/quasar_app/crashhandler/crashhandler_windows.cpp new file mode 100644 index 00000000..0f27fc88 --- /dev/null +++ b/src/quasar_app/crashhandler/crashhandler_windows.cpp @@ -0,0 +1,268 @@ +#ifdef _WIN32 + +#ifndef NOMINMAX + #define NOMINMAX +#endif +#include +#include +#include + +#include "crashhandler_windows.h" + +// Write directly to stderr handle and the debugger output channel. +// For GUI apps with no console, AttachConsole connects to the parent +// terminal (e.g. cmd / PowerShell) so the output appears there. +static void writeConsole(const char* msg) +{ + static bool consoleAttached = false; + if (!consoleAttached) + { + // Attach to the parent process console if one exists; if the app was + // launched from a terminal the output will appear there. This is a + // no-op when there is no parent console (double-click launch). + AttachConsole(ATTACH_PARENT_PROCESS); + consoleAttached = true; + } + + HANDLE hErr = GetStdHandle(STD_ERROR_HANDLE); + if (hErr != INVALID_HANDLE_VALUE && hErr != nullptr) + { + DWORD written = 0; + WriteFile(hErr, msg, static_cast(strlen(msg)), &written, nullptr); + } + + // Always send to the debugger output window (WinDbg / Visual Studio) + OutputDebugStringA(msg); +} + +static bool initSymEngine() +{ + static bool inited = false; + if (inited) return true; + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME); + if (!SymInitialize(GetCurrentProcess(), nullptr, TRUE)) return false; + inited = true; + return true; +} + +static void writeStackTrace(EXCEPTION_POINTERS* ep) +{ + writeConsole("\nStack Trace:\n"); + if (!initSymEngine()) + { + writeConsole(" (symbol engine unavailable)\n"); + return; + } + + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + CONTEXT ctx = *ep->ContextRecord; + + STACKFRAME64 frame = {}; + DWORD machine = 0; +#if defined(_M_AMD64) || defined(_M_X64) + machine = IMAGE_FILE_MACHINE_AMD64; + frame.AddrPC.Offset = ctx.Rip; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = ctx.Rbp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = ctx.Rsp; + frame.AddrStack.Mode = AddrModeFlat; +#elif defined(_M_IX86) + machine = IMAGE_FILE_MACHINE_I386; + frame.AddrPC.Offset = ctx.Eip; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = ctx.Ebp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = ctx.Esp; + frame.AddrStack.Mode = AddrModeFlat; +#elif defined(_M_ARM64) + machine = IMAGE_FILE_MACHINE_ARM64; + frame.AddrPC.Offset = ctx.Pc; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = ctx.Fp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = ctx.Sp; + frame.AddrStack.Mode = AddrModeFlat; +#else + writeConsole(" (unsupported machine type for stack walk)\n"); + return; +#endif + + constexpr DWORD maxName = 512; + char symBuf[sizeof(SYMBOL_INFO) + maxName] = {}; + SYMBOL_INFO* sym = reinterpret_cast(symBuf); + sym->SizeOfStruct = sizeof(SYMBOL_INFO); + sym->MaxNameLen = maxName; + + IMAGEHLP_LINE64 lineInfo = {}; + lineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + char line[1024]; + int frameNo = 0; + while (StackWalk64(machine, process, thread, &frame, &ctx, nullptr, + SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) + { + if (frame.AddrPC.Offset == 0) break; + DWORD64 pc = frame.AddrPC.Offset; + + DWORD64 symDisp = 0; + if (SymFromAddr(process, pc, &symDisp, sym)) + { + DWORD lineDisp = 0; + if (SymGetLineFromAddr64(process, pc, &lineDisp, &lineInfo)) + { + StringCchPrintfA(line, sizeof(line), " [%2d] 0x%016llx %s at %s:%lu\n", + frameNo, (unsigned long long)pc, sym->Name, + lineInfo.FileName, lineInfo.LineNumber); + } + else + { + StringCchPrintfA(line, sizeof(line), " [%2d] 0x%016llx %s\n", + frameNo, (unsigned long long)pc, sym->Name); + } + } + else + { + StringCchPrintfA(line, sizeof(line), " [%2d] 0x%016llx (no symbol)\n", + frameNo, (unsigned long long)pc); + } + writeConsole(line); + + if (++frameNo > 64) + { + writeConsole(" ... (truncated at 64 frames)\n"); + break; + } + } +} + +static void writeRegisters(EXCEPTION_POINTERS* ep) +{ + const CONTEXT& c = *ep->ContextRecord; + char buf[1024]; +#if defined(_M_AMD64) || defined(_M_X64) + StringCchPrintfA(buf, sizeof(buf), + "\nRegisters:\n" + " RAX=%016llx RBX=%016llx RCX=%016llx RDX=%016llx\n" + " RSI=%016llx RDI=%016llx RBP=%016llx RSP=%016llx\n" + " R8 =%016llx R9 =%016llx R10=%016llx R11=%016llx\n" + " R12=%016llx R13=%016llx R14=%016llx R15=%016llx\n" + " RIP=%016llx EFL=%08lx\n", + (unsigned long long)c.Rax, (unsigned long long)c.Rbx, + (unsigned long long)c.Rcx, (unsigned long long)c.Rdx, + (unsigned long long)c.Rsi, (unsigned long long)c.Rdi, + (unsigned long long)c.Rbp, (unsigned long long)c.Rsp, + (unsigned long long)c.R8, (unsigned long long)c.R9, + (unsigned long long)c.R10, (unsigned long long)c.R11, + (unsigned long long)c.R12, (unsigned long long)c.R13, + (unsigned long long)c.R14, (unsigned long long)c.R15, + (unsigned long long)c.Rip, (unsigned long)c.EFlags); +#elif defined(_M_IX86) + StringCchPrintfA(buf, sizeof(buf), + "\nRegisters:\n" + " EAX=%08lx EBX=%08lx ECX=%08lx EDX=%08lx\n" + " ESI=%08lx EDI=%08lx EBP=%08lx ESP=%08lx\n" + " EIP=%08lx EFL=%08lx\n", + c.Eax, c.Ebx, c.Ecx, c.Edx, c.Esi, c.Edi, c.Ebp, c.Esp, c.Eip, c.EFlags); +#elif defined(_M_ARM64) + StringCchPrintfA(buf, sizeof(buf), + "\nRegisters:\n" + " PC=%016llx SP=%016llx FP=%016llx LR=%016llx\n", + (unsigned long long)c.Pc, (unsigned long long)c.Sp, + (unsigned long long)c.Fp, (unsigned long long)c.Lr); +#else + StringCchPrintfA(buf, sizeof(buf), "\nRegisters: (unsupported machine)\n"); +#endif + writeConsole(buf); +} + +static const char* exceptionName(DWORD code) +{ + switch (code) + { + case EXCEPTION_ACCESS_VIOLATION: return "EXCEPTION_ACCESS_VIOLATION"; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; + case EXCEPTION_BREAKPOINT: return "EXCEPTION_BREAKPOINT"; + case EXCEPTION_DATATYPE_MISALIGNMENT: return "EXCEPTION_DATATYPE_MISALIGNMENT"; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; + case EXCEPTION_FLT_OVERFLOW: return "EXCEPTION_FLT_OVERFLOW"; + case EXCEPTION_FLT_UNDERFLOW: return "EXCEPTION_FLT_UNDERFLOW"; + case EXCEPTION_ILLEGAL_INSTRUCTION: return "EXCEPTION_ILLEGAL_INSTRUCTION"; + case EXCEPTION_INT_DIVIDE_BY_ZERO: return "EXCEPTION_INT_DIVIDE_BY_ZERO"; + case EXCEPTION_INT_OVERFLOW: return "EXCEPTION_INT_OVERFLOW"; + case EXCEPTION_PRIV_INSTRUCTION: return "EXCEPTION_PRIV_INSTRUCTION"; + case EXCEPTION_STACK_OVERFLOW: return "EXCEPTION_STACK_OVERFLOW"; + default: return "UNKNOWN_EXCEPTION"; + } +} + +static LONG WINAPI sehFilter(EXCEPTION_POINTERS* ep) +{ + DWORD code = ep->ExceptionRecord->ExceptionCode; + void* addr = ep->ExceptionRecord->ExceptionAddress; + DWORD pid = GetCurrentProcessId(); + DWORD tid = GetCurrentThreadId(); + + // --- Console / debugger output --- + char line[512]; + + writeConsole("\n*** UNHANDLED EXCEPTION (Quaesar) ***\n"); + + StringCchPrintfA(line, sizeof(line), " Exception : 0x%08lX (%s)\n", code, exceptionName(code)); + writeConsole(line); + + StringCchPrintfA(line, sizeof(line), " Address : %p\n", addr); + writeConsole(line); + + StringCchPrintfA(line, sizeof(line), " PID / TID : %lu / %lu\n", pid, tid); + writeConsole(line); + + // --- Stack trace and registers --- + writeStackTrace(ep); + writeRegisters(ep); + + // --- Minidump --- + char tempDir[MAX_PATH] = {}; + char dumpPath[MAX_PATH] = {}; + GetTempPathA(MAX_PATH, tempDir); + StringCchPrintfA(dumpPath, MAX_PATH, "%squaesar_crash_%lu_%lu.dmp", tempDir, pid, GetTickCount()); + + HANDLE hFile = CreateFileA(dumpPath, GENERIC_WRITE, 0, nullptr, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile != INVALID_HANDLE_VALUE) + { + MINIDUMP_EXCEPTION_INFORMATION mei{}; + mei.ThreadId = tid; + mei.ExceptionPointers = ep; + mei.ClientPointers = FALSE; + + MiniDumpWriteDump(GetCurrentProcess(), pid, hFile, + static_cast(MiniDumpNormal | MiniDumpWithThreadInfo), + &mei, nullptr, nullptr); + CloseHandle(hFile); + + StringCchPrintfA(line, sizeof(line), " Minidump : %s\n", dumpPath); + writeConsole(line); + } + else + { + writeConsole(" Minidump : failed to write\n"); + } + + writeConsole("**************************************\n\n"); + + return EXCEPTION_EXECUTE_HANDLER; +} + +void CrashHandlerWindows::install() +{ + SetUnhandledExceptionFilter(sehFilter); +} + +void CrashHandlerWindows::uninstall() +{ + SetUnhandledExceptionFilter(nullptr); +} + +#endif // _WIN32 diff --git a/src/quasar_app/crashhandler/crashhandler_windows.h b/src/quasar_app/crashhandler/crashhandler_windows.h new file mode 100644 index 00000000..3fa18f27 --- /dev/null +++ b/src/quasar_app/crashhandler/crashhandler_windows.h @@ -0,0 +1,13 @@ +#pragma once +#ifdef _WIN32 + +#include "crashhandler.h" + +class CrashHandlerWindows : public CrashHandler +{ +public: + void install() override; + void uninstall() override; +}; + +#endif // _WIN32 diff --git a/src/quasar_app/qsr_application.cpp b/src/quasar_app/qsr_application.cpp index dcf6f40c..36bda125 100644 --- a/src/quasar_app/qsr_application.cpp +++ b/src/quasar_app/qsr_application.cpp @@ -2,12 +2,19 @@ #include #include "SDL.h" #include "amDebugger/dbgConnection.h" +#include "amDebugger/debugger.h" +#include "amDebugger/debuggerOps.h" #include "amDebugger/debuggerServer.h" #include "amDebugger/debuggerWndApp.h" #include "qd/app/appPartsMgr.h" #include "qd/imGui/imGuiContextManager.h" +#include "qd/qui/uiOperation.h" #include "qsr_main_wnd_client_app.h" #include "uae_imp/uae_server_app_part.h" +#include "uae_imp/uae_server_thread.h" + +// UAE debugger state (defined in debug.cpp) +extern int debugger_active; namespace amD::uae { @@ -37,14 +44,43 @@ void QuaesarApplication::onConstruct(qd::CreateApplicationParams& in) { qd::ModuleManager::get()->getModuleInstOrCreate_(); m_pVmPlayerWndAppPart = getAppParts()->createPart_("VM client app"); - //m_pVmPlayerWndAppPart->setVmPlayer(pVmIO); m_pDebuggerApp = getAppParts()->createPart_("Quaesar Debugger"); m_pDebuggerApp->init(); - // ref_ptr pDbgConnect = pFactory->createVmDebuggerConnection(); - // amD::Debugger* pDbg = m_pDebuggerApp->getDbg(); - // pDbg->setDbgServiceBridge(pDbgConnect); + // Forward debugger ops to the real emulator. When paused via UAE's + // internal debugger, route step/continue as console commands directly. + m_pDebuggerApp->setForwardOpToEmulatorCb([this](qd::operation::BaseOpArgs* args) { + // Mirror debug mode to the Debugger's dummy VM for menu enable/disable state + amD::Debugger* pDbg = m_pDebuggerApp->getDbg(); + if (pDbg) { + if (args->cast_() || + args->cast_()) + pDbg->setDebugMode(IVm::EVmDebugMode::Break); + else if (args->cast_()) + pDbg->setDebugMode(IVm::EVmDebugMode::Live); + } + if (qsr::IVmClientPlayer* pVmPlayer = m_pVmPlayerWndAppPart->getVmProvider()) { + UaeServerThread* pUae = dynamic_cast(pVmPlayer); + // When UAE's debug_1() is blocking (debugger_active), the ops queue + // is stuck. Route step/continue directly via execConsoleCmd(). + if (pUae && debugger_active) { + if (args->cast_()) + pUae->execConsoleCmd("t"); + else if (args->cast_()) + pUae->execConsoleCmd("z"); + else if (args->cast_()) + pUae->execConsoleCmd("ot"); + else if (args->cast_()) + pUae->execConsoleCmd("g"); + // Other ops while paused: clone and push (will be processed after resume) + else if (qd::operation::BaseOpArgs* pCloned = args->clone()) + pVmPlayer->pushOperationMsg(qtd::unique_ptr(pCloned)); + } else if (qd::operation::BaseOpArgs* pCloned = args->clone()) { + pVmPlayer->pushOperationMsg(qtd::unique_ptr(pCloned)); + } + } + }); m_pVmPlayerWndAppPart->bringWndToFront(); } diff --git a/src/quasar_app/qsr_main.cpp b/src/quasar_app/qsr_main.cpp index ebe0e2ba..0ebf6533 100644 --- a/src/quasar_app/qsr_main.cpp +++ b/src/quasar_app/qsr_main.cpp @@ -5,8 +5,10 @@ #include #include #include +#include #include "amDebugger/debuggerWndApp.h" #include "cli11/CLI11.hpp" +#include "crashhandler/crashhandler.h" #include "qsr_application.h" #include "qsr_config.h" #include "quaesar.h" @@ -23,6 +25,10 @@ // Quaesar main int SDL_main(int argc, char* argv[]) { + // Install crash handler as early as possible + auto crashHandler = std::unique_ptr(CrashHandler::create()); + crashHandler->install(); + // read options from CLI CLI::App cliApp{"Quaesar"}; @@ -35,7 +41,11 @@ int SDL_main(int argc, char* argv[]) { cliApp.add_option("-s", g_cfg_startup.uaeExtArgs, "key followed by the original WinUAE commands. Example:\n" " quaesar.exe -k c:\\Amiga\\KICK13.rom -s filesystem=rw,dh0:c:\\Amiga\\hd0"); - cliApp.parse(argc, argv); + try { + cliApp.parse(argc, argv); + } catch (const CLI::ParseError& e) { + return cliApp.exit(e); + } // initialize SDL if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) { diff --git a/src/quasar_app/qsr_main_wnd_client_app.cpp b/src/quasar_app/qsr_main_wnd_client_app.cpp index 7c303aea..73cb477b 100644 --- a/src/quasar_app/qsr_main_wnd_client_app.cpp +++ b/src/quasar_app/qsr_main_wnd_client_app.cpp @@ -98,13 +98,18 @@ void QsrMainClientWndApp::_drawGuiMenus() { void QsrMainClientWndApp::renderAppPart() { // render VM display texture screen IVmClientPlayer* pVmPlayer = getVmProvider(); - if (pVmPlayer) { - uint32_t curFrame = pVmPlayer->getScrFrameNo(); - if (curFrame != m_renderedFrameNo) { - m_renderedFrameNo = curFrame; + if (!pVmPlayer) + return; + + uint32_t curFrame = pVmPlayer->getScrFrameNo(); + if (curFrame == m_renderedFrameNo) + return; // No new frame - skip render to avoid flicker + + m_renderedFrameNo = curFrame; + { int curWndSizeX, curWndSizeY; - SDL_GetWindowSize(m_pWindow, &curWndSizeX, &curWndSizeY); + SDL_GetRendererOutputSize(m_hWndRenderer, &curWndSizeX, &curWndSizeY); int bufWidth, bufHeight; uint32_t* pSrcDisplayBuf = nullptr; @@ -145,7 +150,6 @@ void QsrMainClientWndApp::renderAppPart() { SDL_RenderCopy(m_hWndRenderer, hDisplayTex, nullptr, &rect); pVmPlayer->unlockDisplayTexBuf(); } - } } if (m_bShowGui) diff --git a/src/quasar_app/uae_imp/uae_server_thread.cpp b/src/quasar_app/uae_imp/uae_server_thread.cpp index a5131215..04ef708a 100644 --- a/src/quasar_app/uae_imp/uae_server_thread.cpp +++ b/src/quasar_app/uae_imp/uae_server_thread.cpp @@ -9,8 +9,6 @@ // clang-format on #include "uae_server_thread.h" -#include -#include #include "qd/debug/assert.h" #include "qd/log/log.h" #include "qd/thread/thread.h" @@ -18,297 +16,320 @@ #include "quasar_app/quaesar.h" #include "uae_server_app_part.h" #include "uae_vm_imp.h" +#include +#include - -extern void real_main(int argc, TCHAR** argv); +extern void real_main(int argc, TCHAR **argv); extern void qs_keyboard_set_translation(); -extern void quae__parseCmdLine(int argc, TCHAR** argv); +extern void quae__parseCmdLine(int argc, TCHAR **argv); namespace amD::uae { -extern void do_console_cmd_immediate(const char* cmd); -}; //namespace amD::uae - +extern void do_console_cmd_immediate(const char *cmd); +}; // namespace amD::uae class UaeConsoleQueue { public: - std::queue m_consoleCmdQueue; - qd::ThreadEvent* m_pThreadEvent; - qd::Mutex* m_pMutex; + std::queue m_consoleCmdQueue; + qd::ThreadEvent *m_pThreadEvent; + qd::Mutex *m_pMutex; public: - UaeConsoleQueue() { - m_pThreadEvent = new qd::ThreadEvent(true); - m_pMutex = new qd::Mutex(); - } - - void addCmdToQueue(qtd::string cmd) { - if (cmd.empty()) - return; - m_pMutex->lock(); - m_consoleCmdQueue.push(std::move(cmd)); - m_pMutex->unlock(); - m_pThreadEvent->set(); - } - - bool popConsoleCmdWait(qtd::string& out_cmd) { - m_pThreadEvent->wait(100); - qd::MutexLock ml(*m_pMutex); - if (m_consoleCmdQueue.empty()) - return false; - const qtd::string& cmd = m_consoleCmdQueue.front(); - out_cmd = std::move(cmd); + UaeConsoleQueue() { + m_pThreadEvent = new qd::ThreadEvent(true); + m_pMutex = new qd::Mutex(); + } + + void addCmdToQueue(qtd::string cmd) { + if (cmd.empty()) + return; + m_pMutex->lock(); + m_consoleCmdQueue.push(std::move(cmd)); + m_pMutex->unlock(); + m_pThreadEvent->set(); + } + + bool popConsoleCmdWait(qtd::string &out_cmd) { + for (;;) { + m_pThreadEvent->wait(); // block until set() by addCmdToQueue + qd::MutexLock ml(*m_pMutex); + if (!m_consoleCmdQueue.empty()) { + out_cmd = std::move(m_consoleCmdQueue.front()); m_consoleCmdQueue.pop(); return true; + } + // Spurious wake — reset and wait again + m_pThreadEvent->reset(); } + } - void destroy() { - m_consoleCmdQueue = {}; - if (m_pThreadEvent) { - m_pThreadEvent->set(); - SAFE_DELETE(m_pThreadEvent); - } - SAFE_DELETE(m_pMutex); + void destroy() { + m_consoleCmdQueue = {}; + if (m_pThreadEvent) { + m_pThreadEvent->set(); + SAFE_DELETE(m_pThreadEvent); } + SAFE_DELETE(m_pMutex); + } - ~UaeConsoleQueue() { - destroy(); - } + ~UaeConsoleQueue() { destroy(); } -}; // class UaeConsoleQueue +}; // class UaeConsoleQueue ////////////////////////////////////////////////////////////////////////// - -static int uae_thread_main_func(void*) { - std::vector argv; - argv.push_back("quaesar.exe"); - argv.reserve(g_cfg_startup.uaeExtArgs.size() * 2 + 1); - // pass remain Quaesar CLI args to UAE - for (const auto& s : g_cfg_startup.uaeExtArgs) { - argv.push_back("-s"); - argv.push_back(s.c_str()); - } - quae__parseCmdLine((int)argv.size(), const_cast(&argv[0])); - - ::real_main(0, nullptr); // call main function of UAE emulator - return 0; +static int uae_thread_main_func(void *) { + std::vector argv; + argv.push_back("quaesar.exe"); + argv.reserve(g_cfg_startup.uaeExtArgs.size() * 2 + 1); + // pass remain Quaesar CLI args to UAE + for (const auto &s : g_cfg_startup.uaeExtArgs) { + argv.push_back("-s"); + argv.push_back(s.c_str()); + } + quae__parseCmdLine((int)argv.size(), const_cast(&argv[0])); + + ::real_main(0, nullptr); // call main function of UAE emulator + return 0; } +UaeServerThread::UaeServerThread(qsr::UaeServerAppPart *pServerApp) + : m_pServerApp(pServerApp) { + m_pVm = new IVm::imp::UaeVmImp(); + m_pVm->setServerImp(this); -UaeServerThread::UaeServerThread(qsr::UaeServerAppPart* pServerApp) : m_pServerApp(pServerApp) { - m_pVm = new IVm::imp::UaeVmImp(); - m_pVm->setServerImp(this); - - assert(!g_pSingleton); - g_pSingleton = this; - m_pConsoleQueue = new UaeConsoleQueue(); + assert(!g_pSingleton); + g_pSingleton = this; + m_pConsoleQueue = new UaeConsoleQueue(); + m_pauseEvent = new qd::ThreadEvent(true); // auto-reset event } - void UaeServerThread::initialize() { - ::syncbase = 1000000; - qs_keyboard_set_translation(); - ::default_prefs(&::currprefs, true, 0); - ::fixup_prefs(&::currprefs, true); - - //const CfgQsrStartup& options = ; - if (!g_cfg_startup.input.empty()) { - if (qd::ends_with(g_cfg_startup.input, ".exe") || !qd::ends_with(g_cfg_startup.input, ".adf")) { - if (FILE* check_file = fopen(g_cfg_startup.input.c_str(), "rb")) { - fclose(check_file); - Adf::create_for_exefile(g_cfg_startup.input.c_str()); - strcpy(::currprefs.floppyslots[0].df, "dummy.adf"); - } else { - SDL_Log("can't open input file:'%s'", g_cfg_startup.input.c_str()); - } - } else { - strcpy(::currprefs.floppyslots[0].df, g_cfg_startup.input.c_str()); - } - } - - if (!g_cfg_startup.serialPort.empty()) { - currprefs.use_serial = 1; - strcpy(currprefs.sername, g_cfg_startup.serialPort.c_str()); + ::syncbase = 1000000; + qs_keyboard_set_translation(); + ::default_prefs(&::currprefs, true, 0); + ::fixup_prefs(&::currprefs, true); + + // const CfgQsrStartup& options = ; + if (!g_cfg_startup.input.empty()) { + if (qd::ends_with(g_cfg_startup.input, ".exe") || + !qd::ends_with(g_cfg_startup.input, ".adf")) { + if (FILE *check_file = fopen(g_cfg_startup.input.c_str(), "rb")) { + fclose(check_file); + Adf::create_for_exefile(g_cfg_startup.input.c_str()); + strcpy(::currprefs.floppyslots[0].df, "dummy.adf"); + } else { + SDL_Log("can't open input file:'%s'", g_cfg_startup.input.c_str()); + } + } else { + strcpy(::currprefs.floppyslots[0].df, g_cfg_startup.input.c_str()); } - - // Most compatible mode - currprefs.cpu_cycle_exact = true; - currprefs.cpu_memory_cycle_exact = true; - currprefs.blitter_cycle_exact = true; - currprefs.floppy_speed = 100; - // currprefs.turbo_emulation = 1; // it disables sound - currprefs.sound_stereo_separation = 0; - currprefs.uaeboard = 1; - currprefs.win32_filesystem_mangle_reserved_names = true; // required for FS - currprefs.filesys_custom_uaefsdb = false; // hack to not implement 'custom_fsdb_*' funcs now - - strcpy(currprefs.romfile, g_cfg_startup.kickRomPath.c_str()); - - m_scrWidth = 754; - m_scrHeight = 576; - assert(!m_pAmigaBuffer); - m_pAmigaBuffer = new uint32_t[m_scrWidth * m_scrHeight]; - SDL_AtomicSet(&m_scrFrameNo, 0); - - // start UAE Thread - m_onUaeInitialized = new qd::ThreadEvent(true); - m_uaeThread = SDL_CreateThread(&uae_thread_main_func, "UAE emulator", nullptr); - - // wait UAE initialization - m_onUaeInitialized->wait(); - - // initialize after UAE is ready - m_pVm->init(); + } + + if (!g_cfg_startup.serialPort.empty()) { + currprefs.use_serial = 1; + strcpy(currprefs.sername, g_cfg_startup.serialPort.c_str()); + } + + // Most compatible mode + currprefs.cpu_cycle_exact = true; + currprefs.cpu_memory_cycle_exact = true; + currprefs.blitter_cycle_exact = true; + currprefs.floppy_speed = 100; + // currprefs.turbo_emulation = 1; // it disables sound + currprefs.sound_stereo_separation = 0; + currprefs.uaeboard = 1; + currprefs.win32_filesystem_mangle_reserved_names = true; // required for FS + currprefs.filesys_custom_uaefsdb = + false; // hack to not implement 'custom_fsdb_*' funcs now + + strcpy(currprefs.romfile, g_cfg_startup.kickRomPath.c_str()); + + m_scrWidth = 754; + m_scrHeight = 576; + assert(!m_pAmigaBuffer); + m_pAmigaBuffer = new uint32_t[m_scrWidth * m_scrHeight]; + SDL_AtomicSet(&m_scrFrameNo, 0); + + // start UAE Thread + m_onUaeInitialized = new qd::ThreadEvent(true); + m_uaeThread = + SDL_CreateThread(&uae_thread_main_func, "UAE emulator", nullptr); + + // wait UAE initialization + m_onUaeInitialized->wait(); + + // initialize after UAE is ready + m_pVm->init(); } - void UaeServerThread::destroy() { - if (m_pConsoleQueue) { - qd::logInfo("Waiting UAE thread over ..."); - execConsoleCmd("q"); - // wait UAE done - SDL_WaitThread(m_uaeThread, nullptr); - - if (m_pConsoleQueue) - m_pConsoleQueue->destroy(); - SAFE_DELETE(m_pConsoleQueue); - SAFE_DELETE(m_onUaeInitialized); - } - delete[] m_pAmigaBuffer; - m_pAmigaBuffer = nullptr; + m_isDestroying = true; + uae_quit(); + if (m_pConsoleQueue) { + qd::logInfo("Waiting UAE thread over ..."); + execConsoleCmd("q"); + // wait UAE done + SDL_WaitThread(m_uaeThread, nullptr); + + if (m_pConsoleQueue) + m_pConsoleQueue->destroy(); + SAFE_DELETE(m_pConsoleQueue); + SAFE_DELETE(m_onUaeInitialized); + } + + // If paused, resume to unblock the UAE thread + if (m_isPaused) + resumeEmulation(); + + delete[] m_pAmigaBuffer; + m_pAmigaBuffer = nullptr; + SAFE_DELETE(m_pauseEvent); } - UaeServerThread::~UaeServerThread() { - destroy(); - assert(g_pSingleton == this); - g_pSingleton = nullptr; + destroy(); + assert(g_pSingleton == this); + g_pSingleton = nullptr; } - void UaeServerThread::setUaeInitialized(bool) { - ASSERT_AND_DO(m_onUaeInitialized, return); - m_onUaeInitialized->set(); + ASSERT_AND_DO(m_onUaeInitialized, return ); + m_onUaeInitialized->set(); } - -uint32_t* UaeServerThread::_lockUaeScreenTexBuf(int amiga_width, int amiga_height) { - // - m_UaeScrTextureMutex.lock(); - if (amiga_width != m_scrWidth || amiga_height != m_scrHeight) { - delete[] m_pAmigaBuffer; - m_scrWidth = amiga_width; - m_scrHeight = amiga_height; - m_pAmigaBuffer = new uint32_t[m_scrWidth * m_scrHeight]; - } - return m_pAmigaBuffer; +uint32_t *UaeServerThread::_lockUaeScreenTexBuf(int amiga_width, + int amiga_height) { + // + m_UaeScrTextureMutex.lock(); + if (amiga_width != m_scrWidth || amiga_height != m_scrHeight) { + delete[] m_pAmigaBuffer; + m_scrWidth = amiga_width; + m_scrHeight = amiga_height; + m_pAmigaBuffer = new uint32_t[m_scrWidth * m_scrHeight]; + } + return m_pAmigaBuffer; } - void UaeServerThread::_unlockUaeScreenTexBuf() { - m_UaeScrTextureMutex.unlock(); - SDL_AtomicIncRef(&m_scrFrameNo); + m_UaeScrTextureMutex.unlock(); + SDL_AtomicIncRef(&m_scrFrameNo); } - int UaeServerThread::getScrFrameNo() { - return (int)SDL_AtomicGet(&m_scrFrameNo); + return (int)SDL_AtomicGet(&m_scrFrameNo); } - -void UaeServerThread::pushSdlEvent(const SDL_Event& event) { - qd::MutexLock ml(m_eventMutex); - m_sdlEventsQueue.push_back(event); +void UaeServerThread::pushSdlEvent(const SDL_Event &event) { + qd::MutexLock ml(m_eventMutex); + m_sdlEventsQueue.push_back(event); } - -void UaeServerThread::pushOperationMsg(qtd::unique_ptr args) { - qd::MutexLock ml(m_eventMutex); - m_pClientOpsStack.push_back(qtd::move(args)); +void UaeServerThread::pushOperationMsg( + qtd::unique_ptr args) { + qd::MutexLock ml(m_eventMutex); + m_pClientOpsStack.push_back(qtd::move(args)); } - bool UaeServerThread::onUaeHandleEvents() { + { qd::MutexLock ml(m_eventMutex); while (!m_sdlEventsQueue.empty()) { - const SDL_Event& event = m_sdlEventsQueue.front(); - applySdlEventProc(event); - m_sdlEventsQueue.pop_front(); + const SDL_Event &event = m_sdlEventsQueue.front(); + applySdlEventProc(event); + m_sdlEventsQueue.pop_front(); } while (!m_pClientOpsStack.empty()) { - qd::operation::BaseOpArgs* pCurOpMsg = m_pClientOpsStack.front().get(); - m_pVm->applyOperationMsgProc(pCurOpMsg); - m_pClientOpsStack.pop_front(); + qd::operation::BaseOpArgs *pCurOpMsg = m_pClientOpsStack.front().get(); + m_pVm->applyOperationMsgProc(pCurOpMsg); + m_pClientOpsStack.pop_front(); } - return false; -} - + } + + // If paused, block here until resumed. This halts the UAE emulation loop. + // The resumeEmulation() call from the main thread will set m_isPaused = false + // and signal m_pauseEvent, unblocking us. + // We use a timeout so we periodically wake up to process queued operations + // (e.g., the resume/continue operation itself). + while (m_isPaused) { + m_pauseEvent->wait(50); // wake every 50ms to process queued ops + // Process any queued operations that arrived while paused + qd::MutexLock ml(m_eventMutex); + while (!m_pClientOpsStack.empty()) { + qd::operation::BaseOpArgs *pCurOpMsg = m_pClientOpsStack.front().get(); + m_pVm->applyOperationMsgProc(pCurOpMsg); + m_pClientOpsStack.pop_front(); + } + } -IVm::VM* UaeServerThread::getVm() const { - return m_pVm; + return false; } - -bool UaeServerThread::lockDisplayTexBuf(int* width, int* height, uint32_t** out_pixels) { - if (!m_UaeScrTextureMutex.tryLock()) - return false; - if (!m_pAmigaBuffer) { - unlockDisplayTexBuf(); - return false; - } - *width = m_scrWidth; - *height = m_scrHeight; - *out_pixels = m_pAmigaBuffer; - return true; +void UaeServerThread::pauseEmulation() { + m_isPaused = true; + m_pauseEvent->reset(); } - -void UaeServerThread::unlockDisplayTexBuf() { - m_UaeScrTextureMutex.unlock(); +void UaeServerThread::resumeEmulation() { + m_isPaused = false; + m_pauseEvent->set(); } +IVm::VM *UaeServerThread::getVm() const { return m_pVm; } -void UaeServerThread::applySdlEventProc(const SDL_Event& event) { - switch (event.type) { - case SDL_KEYDOWN: { - const SDL_Keycode scancode = event.key.keysym.scancode; - const int keyboard = 0; - const bool newstate = true; - const bool alwaysrelease = true; - inputdevice_translatekeycode(keyboard, scancode, newstate, alwaysrelease); - } break; - case SDL_KEYUP: { - const SDL_Keycode scancode = event.key.keysym.scancode; - const int keyboard = 0; - const bool newstate = false; - const bool alwaysrelease = false; - inputdevice_translatekeycode(keyboard, scancode, newstate, alwaysrelease); - } break; - default: - break; - } +bool UaeServerThread::lockDisplayTexBuf(int *width, int *height, + uint32_t **out_pixels) { + if (!m_UaeScrTextureMutex.tryLock()) + return false; + if (!m_pAmigaBuffer) { + unlockDisplayTexBuf(); + return false; + } + *width = m_scrWidth; + *height = m_scrHeight; + *out_pixels = m_pAmigaBuffer; + return true; } - -void UaeServerThread::applyImmediateConsoleCmd(qtd::string&& cmd) { - amD::uae::do_console_cmd_immediate(cmd.c_str()); +void UaeServerThread::unlockDisplayTexBuf() { m_UaeScrTextureMutex.unlock(); } + +void UaeServerThread::applySdlEventProc(const SDL_Event &event) { + switch (event.type) { + case SDL_KEYDOWN: { + const SDL_Keycode scancode = event.key.keysym.scancode; + const int keyboard = 0; + const bool newstate = true; + const bool alwaysrelease = true; + inputdevice_translatekeycode(keyboard, scancode, newstate, alwaysrelease); + } break; + case SDL_KEYUP: { + const SDL_Keycode scancode = event.key.keysym.scancode; + const int keyboard = 0; + const bool newstate = false; + const bool alwaysrelease = false; + inputdevice_translatekeycode(keyboard, scancode, newstate, alwaysrelease); + } break; + default: + break; + } } - -void UaeServerThread::execConsoleCmd(qtd::string&& cmd) { - m_pConsoleQueue->addCmdToQueue(std::move(cmd)); +void UaeServerThread::applyImmediateConsoleCmd(qtd::string &&cmd) { + amD::uae::do_console_cmd_immediate(cmd.c_str()); } +void UaeServerThread::execConsoleCmd(qtd::string &&cmd) { + m_pConsoleQueue->addCmdToQueue(std::move(cmd)); +} -int UaeServerThread::uaeWaitConsoleCmdImpl(char* out, int maxlen) { - qtd::string cmd; - if (!m_pConsoleQueue->popConsoleCmdWait(cmd)) - return -1; - - const int len = (int)cmd.size(); - if (len < maxlen) - strcpy(out, cmd.data()); - else - assert(0); - return len; +int UaeServerThread::uaeWaitConsoleCmdImpl(char *out, int maxlen) { + qtd::string cmd; + if (!m_pConsoleQueue->popConsoleCmdWait(cmd)) + return -1; + + const int len = (int)cmd.size(); + if (len < maxlen) + strcpy(out, cmd.data()); + else + assert(0); + return len; } diff --git a/src/quasar_app/uae_imp/uae_server_thread.h b/src/quasar_app/uae_imp/uae_server_thread.h index 3e9537a2..dbe451cd 100644 --- a/src/quasar_app/uae_imp/uae_server_thread.h +++ b/src/quasar_app/uae_imp/uae_server_thread.h @@ -26,6 +26,9 @@ class UaeServerThread : public qsr::IVmClientPlayer { std::deque m_sdlEventsQueue; class UaeConsoleQueue* m_pConsoleQueue = nullptr; std::deque> m_pClientOpsStack; + bool m_isDestroying = false; + bool m_isPaused = false; + qd::ThreadEvent* m_pauseEvent = nullptr; public: int m_scrWidth = 754; @@ -52,6 +55,10 @@ class UaeServerThread : public qsr::IVmClientPlayer { virtual void pushOperationMsg(qtd::unique_ptr args) override; bool onUaeHandleEvents(); + void pauseEmulation(); + void resumeEmulation(); + bool isPaused() const { return m_isPaused; } + void execConsoleCmd(qtd::string&& cmd); int uaeWaitConsoleCmdImpl(char* out, int maxlen); diff --git a/src/quasar_app/uae_imp/uae_vm_imp.cpp b/src/quasar_app/uae_imp/uae_vm_imp.cpp index 24a1baaf..574db249 100644 --- a/src/quasar_app/uae_imp/uae_vm_imp.cpp +++ b/src/quasar_app/uae_imp/uae_vm_imp.cpp @@ -89,7 +89,7 @@ qd::EFlow UaeVmImp::applyOperationMsgProcImp(qd::operation::BaseOpArgs* args) { } else if (args->cast_()) { r = true; vm->setVmDebugMode(EVmDebugMode::Break); - pUae->execConsoleCmd("t"); + if (pUae) pUae->execConsoleCmd("t"); } else if (args->cast_()) { r = true; @@ -100,13 +100,14 @@ qd::EFlow UaeVmImp::applyOperationMsgProcImp(qd::operation::BaseOpArgs* args) { } else if (args->cast_()) { r = true; - pUae->execConsoleCmd("z"); + if (pUae) pUae->execConsoleCmd("z"); } else if (args->cast_()) { r = true; - pUae->execConsoleCmd("ot"); + if (pUae) pUae->execConsoleCmd("ot"); } else if (auto p = args->cast_()) { + if (!pUae) return EFlow::NO_RESULT; qtd::string cmd = qd::string_format("f %08x", (uint32_t)p->address); if (p->nBreakpoint >= 0) cmd += qd::string_format(" %i", p->nBreakpoint); @@ -121,17 +122,23 @@ qd::EFlow UaeVmImp::applyOperationMsgProcImp(qd::operation::BaseOpArgs* args) { ::warpmode(2); // on } + } else if (args->cast_()) { + r = true; + vm->setVmDebugMode(EVmDebugMode::Break); + } else if (args->cast_()) { r = true; ::uae_reset(1, 1); } else if (auto p = args->cast_()) { + if (!pUae) return EFlow::NO_RESULT; r = true; qtd::string cmd = qd::string_format("ob %08x", (uint32_t)p->address); pUae->execConsoleCmd(std::move(cmd)); return qd::EFlow::SUCCESS; } else if (auto p = args->cast_()) { + if (!pUae) return EFlow::NO_RESULT; qtd::string cmd = qd::string_format("fs %i", p->waitScanLines); pUae->execConsoleCmd(std::move(cmd)); return qd::EFlow::SUCCESS; @@ -140,11 +147,6 @@ qd::EFlow UaeVmImp::applyOperationMsgProcImp(qd::operation::BaseOpArgs* args) { r = true; } else if (args->cast_()) { r = true; - // if (pUae->isWndAlwaysOnTop()) { - // pUae->setWndAlwaysOnTop(false); - // } else { - // pUae->setWndAlwaysOnTop(true); - // } } return r ? EFlow::STOP : EFlow::NO_RESULT; } @@ -208,15 +210,12 @@ void UaeVmImp::Emu::setDebugDmaMode(int p_mode) { void UaeVmImp::setVmDebugMode(EVmDebugMode debug_mode) { TSuper::setVmDebugMode(debug_mode); if (debug_mode == EVmDebugMode::Break) { - while (!instEmu.isDebugActivatedFull()) { - ::debugger_active = 0; - ::debugging = 0; - ::activate_debugger_new(); + if (m_pUaeThread) { + ::activate_debugger_new_pc(0, 0xFFFFFFFF); } } else if (debug_mode == EVmDebugMode::Live) { if (m_pUaeThread) m_pUaeThread->execConsoleCmd("g"); - ::debugger_active = 0; } } diff --git a/src/quasar_app/uae_imp/uae_vm_imp.h b/src/quasar_app/uae_imp/uae_vm_imp.h index 737fa63a..057518a8 100644 --- a/src/quasar_app/uae_imp/uae_vm_imp.h +++ b/src/quasar_app/uae_imp/uae_vm_imp.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include FORWARD_DECLARATION_1(UaeServerThread); diff --git a/src/uae_lib_imp/dummy.cpp b/src/uae_lib_imp/dummy.cpp index 06b106eb..6b51b5bf 100644 --- a/src/uae_lib_imp/dummy.cpp +++ b/src/uae_lib_imp/dummy.cpp @@ -592,11 +592,9 @@ void console_flush() { } int console_get(char* out, int maxlen) { - for (;;) { - int len = qsr_waitConsoleCmd(out, maxlen); - if (len > 0) - return len; - } + int len = qsr_waitConsoleCmd(out, maxlen); + if (len > 0) + return len; return -1; }