diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 1e6b71e9..5d3e4cc0 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -6,7 +6,9 @@ "${workspaceFolder}/dependencies/MetaStuff/include", "${workspaceFolder}/dependencies", "${workspaceFolder}/src/DarkId.Papyrus.DebugServer", - "${workspaceFolder}/vcpkg_installed/skyrim/x64-windows-static/include" + "${workspaceFolder}/src/DarkId.Papyrus.DebugServer/Protocol", + "${workspaceFolder}/vcpkg_installed/skyrim/x64-windows-static/include", + "${workspaceFolder}/vcpkg_installed/skyrim/x64-windows-static/include/Champollion" ], "defines": [ "WIN32", @@ -30,7 +32,9 @@ "${workspaceFolder}/dependencies/MetaStuff/include", "${workspaceFolder}/dependencies", "${workspaceFolder}/src/DarkId.Papyrus.DebugServer", - "${workspaceFolder}/vcpkg_installed/fallout4/x64-windows-static/include" + "${workspaceFolder}/src/DarkId.Papyrus.DebugServer/Protocol", + "${workspaceFolder}/vcpkg_installed/fallout4/x64-windows-static/include", + "${workspaceFolder}/vcpkg_installed/skyrim/x64-windows-static/include/Champollion" ], "defines": [ "WIN32", diff --git a/dependencies/ports/cppdap/portfile.cmake b/dependencies/ports/cppdap/portfile.cmake index ef41989f..8e51fcf7 100644 --- a/dependencies/ports/cppdap/portfile.cmake +++ b/dependencies/ports/cppdap/portfile.cmake @@ -1,18 +1,23 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH - REPO nikitalita/cppdap - REF d1b698f67abc0da9af072e9d054e2761481c4f86 - SHA512 fe12ec3bc3de1f02e6669fd637b86bb07c9878fc012d1d7d28efeb3471e6936956e89de6b31325c70ff86b20bb45627c654f99c92ad276d8512838a19aef17f8 - HEAD_REF cmake-install + REPO google/cppdap + REF 0a340c6d71ca00893ca1aefea38f3504e6755196 + SHA512 14f8d0438678eb715f171b95ed9733485bdb681bc174642f196b7665aad3157b500ba6e6abccc5adcb8ec17aeffa94ae562dcbd31248244d9c8f4a11b97fc2ea + HEAD_REF main ) -# Check if one or more features are a part of a package installation. -# See /docs/maintainers/vcpkg_check_features.md for more details + vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS FEATURES use-nlohmann-json CPPDAP_USE_EXTERNAL_NLOHMANN_JSON_PACKAGE - rapidjson CPPDAP_USE_EXTERNAL_RAPID_JSON_PACKAGE + use-rapidjson CPPDAP_USE_EXTERNAL_RAPIDJSON_PACKAGE ) +if (NOT CPPDAP_USE_EXTERNAL_NLOHMANN_JSON_PACKAGE AND NOT CPPDAP_USE_EXTERNAL_RAPIDJSON_PACKAGE) + message(FATAL_ERROR "Must set either \"use-nlohmann-json\" or \"use-rapidjson\" feature.") +elseif(CPPDAP_USE_EXTERNAL_NLOHMANN_JSON_PACKAGE AND CPPDAP_USE_EXTERNAL_RAPIDJSON_PACKAGE) + message(FATAL_ERROR "Cannot set both \"use-nlohmann-json\" and \"use-rapidjson\" feature.") +endif() + vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}" OPTIONS diff --git a/src/DarkId.Papyrus.DebugServer/ArrayStateNode.cpp b/src/DarkId.Papyrus.DebugServer/ArrayStateNode.cpp index 2870f266..265bda19 100644 --- a/src/DarkId.Papyrus.DebugServer/ArrayStateNode.cpp +++ b/src/DarkId.Papyrus.DebugServer/ArrayStateNode.cpp @@ -15,7 +15,7 @@ namespace DarkId::Papyrus::DebugServer m_type = &_m_inst_type; } - bool ArrayStateNode::SerializeToProtocol(Variable& variable) + bool ArrayStateNode::SerializeToProtocol(dap::Variable& variable) { variable.variablesReference = m_value ? GetId() : 0; variable.indexedVariables = m_value ? m_value->size() : 0; diff --git a/src/DarkId.Papyrus.DebugServer/ArrayStateNode.h b/src/DarkId.Papyrus.DebugServer/ArrayStateNode.h index aa5c347f..d16c9afb 100644 --- a/src/DarkId.Papyrus.DebugServer/ArrayStateNode.h +++ b/src/DarkId.Papyrus.DebugServer/ArrayStateNode.h @@ -2,7 +2,7 @@ #include "GameInterfaces.h" -#include "Protocol/protocol.h" +#include #include "StateNodeBase.h" namespace DarkId::Papyrus::DebugServer @@ -20,7 +20,7 @@ namespace DarkId::Papyrus::DebugServer virtual ~ArrayStateNode() override = default; - bool SerializeToProtocol(Variable& variable) override; + bool SerializeToProtocol(dap::Variable& variable) override; bool GetChildNames(std::vector& names) override; bool GetChildNode(std::string name, std::shared_ptr& node) override; diff --git a/src/DarkId.Papyrus.DebugServer/BreakpointManager.cpp b/src/DarkId.Papyrus.DebugServer/BreakpointManager.cpp index 85a5eff1..b7d6c5de 100644 --- a/src/DarkId.Papyrus.DebugServer/BreakpointManager.cpp +++ b/src/DarkId.Papyrus.DebugServer/BreakpointManager.cpp @@ -7,15 +7,16 @@ #endif namespace DarkId::Papyrus::DebugServer { - PDError BreakpointManager::SetBreakpoints(Source& source, const std::vector& srcBreakpoints, std::vector& breakpoints) + dap::ResponseOrError BreakpointManager::SetBreakpoints(dap::Source& source, const std::vector& srcBreakpoints) { + dap::SetBreakpointsResponse response; std::set breakpointLines; - - auto scriptName = NormalizeScriptName(source.name); + + auto scriptName = NormalizeScriptName(source.name.value("")); auto binary = m_pexCache->GetScript(scriptName.c_str()); if (!binary) { - logger::error("Could not find PEX data for script {}", scriptName); - } // Continue on to set the breakpoints as unverified + return dap::Error("Could not find PEX data for script %s", scriptName); + } const auto sourceReference = m_pexCache->GetScriptReference(scriptName.c_str()); source.sourceReference = sourceReference; @@ -28,11 +29,10 @@ namespace DarkId::Papyrus::DebugServer logger::error("Could not save PEX dump for {}"sv, scriptName); } #endif - bool hasDebugInfo = binary && binary->getDebugInfo().getFunctionInfos().size() > 0; - // only log error if PEX is loaded - if (binary && !hasDebugInfo) { - logger::error("No debug info in script {}"sv, scriptName); - } // Continue on to set the breakpoints as unverified + bool hasDebugInfo = binary->getDebugInfo().getFunctionInfos().size() > 0; + if (!hasDebugInfo) { + return dap::Error("Could not find PEX data for script %s", scriptName); + } for (const auto& srcBreakpoint : srcBreakpoints) { @@ -60,22 +60,16 @@ namespace DarkId::Papyrus::DebugServer breakpointLines.emplace(srcBreakpoint.line); - Breakpoint breakpoint; + dap::Breakpoint breakpoint; breakpoint.source = source; breakpoint.verified = foundLine; breakpoint.line = srcBreakpoint.line; - breakpoints.push_back(breakpoint); + response.breakpoints.push_back(breakpoint); } m_breakpoints[sourceReference] = breakpointLines; - if (!binary) { - return PDError::NO_PEX_DATA; - } - else if (!hasDebugInfo) { - return PDError::NO_DEBUG_INFO; - } - return PDError::OK; + return response; } bool BreakpointManager::GetExecutionIsAtValidBreakpoint(RE::BSScript::Internal::CodeTasklet* tasklet) diff --git a/src/DarkId.Papyrus.DebugServer/BreakpointManager.h b/src/DarkId.Papyrus.DebugServer/BreakpointManager.h index 3bf17e68..779c474d 100644 --- a/src/DarkId.Papyrus.DebugServer/BreakpointManager.h +++ b/src/DarkId.Papyrus.DebugServer/BreakpointManager.h @@ -1,12 +1,12 @@ #pragma once #include #include -#include "Protocol/protocol.h" +#include +#include #include "GameInterfaces.h" #include "PexCache.h" -#include "PDError.h" namespace DarkId::Papyrus::DebugServer { @@ -21,7 +21,7 @@ namespace DarkId::Papyrus::DebugServer { } - PDError SetBreakpoints(Source& source, const std::vector& srcBreakpoints, std::vector& breakpoints); + dap::ResponseOrError SetBreakpoints(dap::Source& source, const std::vector& srcBreakpoints); bool GetExecutionIsAtValidBreakpoint(RE::BSScript::Internal::CodeTasklet* tasklet); }; } diff --git a/src/DarkId.Papyrus.DebugServer/DarkId.Papyrus.DebugServer.Fallout4.vcxproj b/src/DarkId.Papyrus.DebugServer/DarkId.Papyrus.DebugServer.Fallout4.vcxproj index abb3c638..1a971235 100644 --- a/src/DarkId.Papyrus.DebugServer/DarkId.Papyrus.DebugServer.Fallout4.vcxproj +++ b/src/DarkId.Papyrus.DebugServer/DarkId.Papyrus.DebugServer.Fallout4.vcxproj @@ -1,6 +1,5 @@  - + Debug @@ -38,14 +37,10 @@ - + - + @@ -56,7 +51,7 @@ false false false - $(ProjectDir)bin\$(ProjectName)\$(Platform)\$(Configuration)\ + F:\Games\fallout_4_mods_folder\mods\Papyrus Debug Extension\F4SE\Plugins\ $(ProjectDir)obj\$(ProjectName)\$(Platform)\$(Configuration)\ @@ -64,7 +59,7 @@ false false false - $(ProjectDir)bin\$(ProjectName)\$(Platform)\$(Configuration)\ + F:\Games\fallout_4_mods_folder\mods\Papyrus Debug Extension\F4SE\Plugins\ $(ProjectDir)obj\$(ProjectName)\$(Platform)\$(Configuration)\ @@ -202,7 +197,6 @@ - @@ -214,8 +208,8 @@ - - + + @@ -236,7 +230,6 @@ - @@ -246,14 +239,12 @@ - - - - - + + + @@ -264,7 +255,6 @@ - diff --git a/src/DarkId.Papyrus.DebugServer/DarkId.Papyrus.DebugServer.Fallout4.vcxproj.filters b/src/DarkId.Papyrus.DebugServer/DarkId.Papyrus.DebugServer.Fallout4.vcxproj.filters index 7f4463d5..2e4289f3 100644 --- a/src/DarkId.Papyrus.DebugServer/DarkId.Papyrus.DebugServer.Fallout4.vcxproj.filters +++ b/src/DarkId.Papyrus.DebugServer/DarkId.Papyrus.DebugServer.Fallout4.vcxproj.filters @@ -9,19 +9,12 @@ - - Protocol - - - - Protocol - @@ -59,29 +52,21 @@ State + + Protocol + + + Protocol + - - Protocol - Protocol - - Protocol - - - Protocol - - - Protocol - - - @@ -128,6 +113,14 @@ State - + + Protocol + + + Protocol + + + Protocol + \ No newline at end of file diff --git a/src/DarkId.Papyrus.DebugServer/DebugExecutionManager.cpp b/src/DarkId.Papyrus.DebugServer/DebugExecutionManager.cpp index e9a98fe9..c3013bee 100644 --- a/src/DarkId.Papyrus.DebugServer/DebugExecutionManager.cpp +++ b/src/DarkId.Papyrus.DebugServer/DebugExecutionManager.cpp @@ -1,4 +1,5 @@ #include "DebugExecutionManager.h" +#include namespace DarkId::Papyrus::DebugServer { @@ -15,7 +16,7 @@ namespace DarkId::Papyrus::DebugServer const auto func = tasklet->topFrame->owningFunction; auto shouldSendPause = false; - StopReason stopReason = StopReason::StopPause; + dap::StoppedEvent event; if (m_state == DebuggerState::kPaused) { @@ -25,12 +26,14 @@ namespace DarkId::Papyrus::DebugServer if (m_state != DebuggerState::kPaused && m_breakpointManager->GetExecutionIsAtValidBreakpoint(tasklet)) { m_state = DebuggerState::kPaused; - m_protocol->EmitStoppedEvent(StoppedEvent(StopReason::StopBreakpoint, tasklet->stack->stackID)); + event.reason = "breakpoint"; + event.threadId = tasklet->stack->stackID; + m_session->send(event); } if (m_state == DebuggerState::kStepping && !RuntimeState::GetStack(m_currentStepStackId)) { - m_protocol->EmitContinuedEvent(ContinuedEvent()); + m_session->send(dap::ContinuedEvent()); m_currentStepStackId = 0; m_currentStepStackFrame = nullptr; @@ -46,7 +49,7 @@ namespace DarkId::Papyrus::DebugServer if (!currentFrames.empty()) { - auto stepFrameIndex = -1; + ptrdiff_t stepFrameIndex = -1; const auto stepFrameIter = std::find(currentFrames.begin(), currentFrames.end(), m_currentStepStackFrame); if (stepFrameIter != currentFrames.end()) @@ -56,23 +59,23 @@ namespace DarkId::Papyrus::DebugServer switch (m_currentStepType) { - case Debugger::StepType::STEP_IN: + case StepType::STEP_IN: shouldSendPause = true; - stopReason = StopReason::StopStep; + event.reason = "step"; break; - case Debugger::StepType::STEP_OUT: + case StepType::STEP_OUT: // If the stack exists, but the original frame is gone, we know we're in a previous frame now. if (stepFrameIndex == -1) { shouldSendPause = true; - stopReason = StopReason::StopStep; + event.reason = "step"; } break; - case Debugger::StepType::STEP_OVER: + case StepType::STEP_OVER: if (stepFrameIndex <= 0) { shouldSendPause = true; - stopReason = StopReason::StopStep; + event.reason = "step"; } break; } @@ -85,12 +88,14 @@ namespace DarkId::Papyrus::DebugServer m_state = DebuggerState::kPaused; m_currentStepStackId = 0; m_currentStepStackFrame = nullptr; - m_protocol->EmitStoppedEvent(StoppedEvent(stopReason, tasklet->stack->stackID)); + event.threadId = tasklet->stack->stackID; + m_session->send(event); } while (m_state == DebuggerState::kPaused && !m_closed) { - Sleep(100); + using namespace std::chrono_literals; + std::this_thread::sleep_for(100ms); } } @@ -104,7 +109,7 @@ namespace DarkId::Papyrus::DebugServer bool DebugExecutionManager::Continue() { m_state = DebuggerState::kRunning; - m_protocol->EmitContinuedEvent(ContinuedEvent()); + m_session->send(dap::ContinuedEvent()); return true; } @@ -120,7 +125,7 @@ namespace DarkId::Papyrus::DebugServer return true; } - bool DebugExecutionManager::Step(uint32_t stackId, const Debugger::StepType stepType) + bool DebugExecutionManager::Step(uint32_t stackId, const StepType stepType) { if (m_state != DebuggerState::kPaused) { diff --git a/src/DarkId.Papyrus.DebugServer/DebugExecutionManager.h b/src/DarkId.Papyrus.DebugServer/DebugExecutionManager.h index d3faac0c..264bb5e8 100644 --- a/src/DarkId.Papyrus.DebugServer/DebugExecutionManager.h +++ b/src/DarkId.Papyrus.DebugServer/DebugExecutionManager.h @@ -1,17 +1,22 @@ #pragma once -#include "Protocol/debugger.h" #include "GameInterfaces.h" #include "BreakpointManager.h" #include "RuntimeState.h" +#include #include namespace DarkId::Papyrus::DebugServer { - + enum StepType { + STEP_IN = 0, + STEP_OVER, + STEP_OUT + }; + using namespace RE::BSScript::Internal; class DebugExecutionManager @@ -26,18 +31,18 @@ namespace DarkId::Papyrus::DebugServer std::mutex m_instructionMutex; bool m_closed = false; - Protocol* m_protocol; + std::shared_ptr m_session; RuntimeState* m_runtimeState; BreakpointManager* m_breakpointManager; DebuggerState m_state = DebuggerState::kRunning; uint32_t m_currentStepStackId = 0; - Debugger::StepType m_currentStepType = Debugger::StepType::STEP_IN; + StepType m_currentStepType = StepType::STEP_IN; RE::BSScript::StackFrame* m_currentStepStackFrame; public: - explicit DebugExecutionManager(Protocol* protocol, RuntimeState* runtimeState, + explicit DebugExecutionManager(std::shared_ptr session, RuntimeState* runtimeState, BreakpointManager* breakpointManager) - : m_protocol(protocol), m_runtimeState(runtimeState), m_breakpointManager(breakpointManager), + : m_session(session), m_runtimeState(runtimeState), m_breakpointManager(breakpointManager), m_currentStepStackFrame(nullptr) { } @@ -46,6 +51,6 @@ namespace DarkId::Papyrus::DebugServer void HandleInstruction(CodeTasklet* tasklet, CodeTasklet::OpCode opCode); bool Continue(); bool Pause(); - bool Step(uint32_t stackId, Debugger::StepType stepType); + bool Step(uint32_t stackId, StepType stepType); }; } diff --git a/src/DarkId.Papyrus.DebugServer/DebugServer.cpp b/src/DarkId.Papyrus.DebugServer/DebugServer.cpp index b8cf42a1..15127b6a 100644 --- a/src/DarkId.Papyrus.DebugServer/DebugServer.cpp +++ b/src/DarkId.Papyrus.DebugServer/DebugServer.cpp @@ -1,101 +1,65 @@ #include "DebugServer.h" namespace DarkId::Papyrus::DebugServer { - DebugServer::DebugServer() : - m_session(NULL) - { - - } - - void DebugServer::Send(std::string message) - { - m_server.send(m_connectionHandle, message.c_str(), message.length(), websocketpp::frame::opcode::text); - } - - void DebugServer::HandleMessage(websocketpp::connection_hdl hdl, message_ptr msg) - { - if (m_session) - { - m_session->Receive(msg->get_payload()); - } - } - - void DebugServer::HandleOpen(websocketpp::connection_hdl hdl) - { - if (m_session && m_server.get_con_from_hdl(hdl) != m_server.get_con_from_hdl(m_connectionHandle)) - { - m_server.close(m_connectionHandle, websocketpp::close::status::normal, "Connection closed by new session."); - } - - m_connectionHandle = hdl; - m_session = new DebugServerSession(std::bind(&DebugServer::Send, this, ::_1)); - } - - void DebugServer::HandleClose(websocketpp::connection_hdl hdl) - { - if (m_server.get_con_from_hdl(hdl) != m_server.get_con_from_hdl(m_connectionHandle)) - { - return; - } - - m_session->Close(); - delete m_session; - m_session = NULL; - } - - uint32_t DebugServer::ListenInternal() - { - try - { - m_server.init_asio(); - - m_server.set_open_handler(bind(&DebugServer::HandleOpen, this, ::_1)); - m_server.set_close_handler(bind(&DebugServer::HandleClose, this, ::_1)); - m_server.set_message_handler(bind(&DebugServer::HandleMessage, this, ::_1, ::_2)); - m_server.set_max_message_size(1024 * 1024 * 10); - m_server.set_max_http_body_size(1024 * 1024 * 10); - - // Listen on port 43201 - // TODO: Make configurable - -#if SKYRIM - m_server.listen(43201); -#elif FALLOUT - m_server.listen(2077); -#endif - - // Start the server accept loop - m_server.start_accept(); - - // Start the ASIO io_service run loop - m_server.run(); - } - catch (websocketpp::exception const & e) - { - logger::info("{}"sv, e.what()); - return e.code().value(); - } - catch (...) - { - logger::info("other_exception"); - return -1; - } - - return 0; - } - - DWORD DebugServer::ListenThreadStart(void* param) - { - DebugServer* server = (DebugServer*)param; - return server->ListenInternal(); - } + DebugServer::DebugServer(): m_session(nullptr){ } bool DebugServer::Listen() { - DWORD threadId; - - m_thread = CreateThread(NULL, 0, ListenThreadStart, this, 0, &threadId); - + #if SKYRIM + int port = 43201; + #elif FALLOUT + int port = 2077; + #endif + auto onClientConnected = + [&](const std::shared_ptr& connection) { + m_session = dap::Session::create(); + m_session->bind(connection); + std::shared_ptr thing = std::move(m_session); + debugger = std::unique_ptr( new PapyrusDebugger(std::move(thing)) ); + // The Initialize request is the first message sent from the client and + // the response reports debugger capabilities. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize + m_session->registerHandler([](const dap::InitializeRequest& request) { + dap::InitializeResponse response; + response.supportsConfigurationDoneRequest = true; + response.supportsLoadedSourcesRequest = true; + return response; + }); + + m_session->registerSentHandler( + [&](const dap::ResponseOrError&) { + m_session->send(dap::InitializedEvent()); + }); + + // Signal used to terminate the server session when a DisconnectRequest + // is made by the client. + bool terminate = false; + std::condition_variable cv; + std::mutex mutex; // guards 'terminate' + + // The Disconnect request is made by the client before it disconnects + // from the server. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disconnect + m_session->registerHandler([&](const dap::DisconnectRequest&) { + // Client wants to disconnect. Set terminate to true, and signal the + // condition variable to unblock the server thread. + std::unique_lock lock(mutex); + terminate = true; + cv.notify_one(); + return dap::DisconnectResponse{}; + }); + + // Wait for the client to disconnect (or reach a 5 second timeout) + // before releasing the session and disconnecting the socket to the + // client. + std::unique_lock lock(mutex); + cv.wait(lock, [&] { return terminate; }); + printf("Session terminated\n"); + m_session = nullptr; + }; + auto onError = [&](const char* msg) { printf("Server error: %s\n", msg); }; + + m_server.start(port, onClientConnected, onError); return true; } diff --git a/src/DarkId.Papyrus.DebugServer/DebugServer.h b/src/DarkId.Papyrus.DebugServer/DebugServer.h index cb61a616..e2f6ba30 100644 --- a/src/DarkId.Papyrus.DebugServer/DebugServer.h +++ b/src/DarkId.Papyrus.DebugServer/DebugServer.h @@ -1,8 +1,7 @@ #pragma once -#include "Websocket.h" -#include "DebugServerSession.h" - +#include "Protocol/websocket_server.h" +#include "PapyrusDebugger.h" namespace DarkId::Papyrus::DebugServer { class DebugServer @@ -13,23 +12,8 @@ namespace DarkId::Papyrus::DebugServer bool Listen(); private: - DebugServerSession* m_session; - - HANDLE m_thread; - server m_server; - - websocketpp::connection_hdl m_connectionHandle; - - std::basic_streambuf* m_streamBuffer; - - uint32_t ListenInternal(); - - void Send(std::string message); - - void HandleMessage(websocketpp::connection_hdl hdl, message_ptr msg); - void HandleOpen(websocketpp::connection_hdl hdl); - void HandleClose(websocketpp::connection_hdl hdl); - - static DWORD WINAPI ListenThreadStart(void* param); + std::unique_ptr m_session; + std::unique_ptr debugger; + dap::net::WebsocketServer m_server; }; } \ No newline at end of file diff --git a/src/DarkId.Papyrus.DebugServer/DebugServerSession.cpp b/src/DarkId.Papyrus.DebugServer/DebugServerSession.cpp deleted file mode 100644 index 89f481bd..00000000 --- a/src/DarkId.Papyrus.DebugServer/DebugServerSession.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "DebugServerSession.h" - -namespace DarkId::Papyrus::DebugServer -{ - DebugServerSession::DebugServerSession(std::function sendCallback) - { - m_protocol = new VSCodeProtocol(sendCallback); - m_debugger = new PapyrusDebugger(m_protocol); - m_protocol->SetDebugger(m_debugger); - - DWORD threadId; - m_thread = CreateThread(NULL, 0, CommandLoopThreadStart, this, 0, &threadId); - } - - DebugServerSession::~DebugServerSession() - { - - } - - void DebugServerSession::Receive(std::string message) - { - m_protocol->Receive(message); - } - - void DebugServerSession::Close() - { - m_protocol->Exit(); - } - - DWORD WINAPI DebugServerSession::CommandLoopThreadStart(void* param) - { - DebugServerSession* session = (DebugServerSession*)param; - auto debugger = session->m_debugger; - auto protocol = session->m_protocol; - - session->m_protocol->CommandLoop(); - - delete protocol; - delete debugger; - - return 0; - } -} \ No newline at end of file diff --git a/src/DarkId.Papyrus.DebugServer/DebugServerSession.h b/src/DarkId.Papyrus.DebugServer/DebugServerSession.h deleted file mode 100644 index df09fc0e..00000000 --- a/src/DarkId.Papyrus.DebugServer/DebugServerSession.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "PapyrusDebugger.h" -#include "Protocol/vscodeprotocol.h" - -namespace DarkId::Papyrus::DebugServer -{ - class DebugServerSession - { - PapyrusDebugger* m_debugger; - VSCodeProtocol* m_protocol; - - HANDLE m_thread; - - static DWORD WINAPI CommandLoopThreadStart(void* param); - public: - DebugServerSession(std::function sendCallback); - - void Receive(std::string message); - void Close(); - - ~DebugServerSession(); - }; -} \ No newline at end of file diff --git a/src/DarkId.Papyrus.DebugServer/LocalScopeStateNode.cpp b/src/DarkId.Papyrus.DebugServer/LocalScopeStateNode.cpp index 12aed7b5..a30a6acc 100644 --- a/src/DarkId.Papyrus.DebugServer/LocalScopeStateNode.cpp +++ b/src/DarkId.Papyrus.DebugServer/LocalScopeStateNode.cpp @@ -8,7 +8,7 @@ namespace DarkId::Papyrus::DebugServer { } - bool LocalScopeStateNode::SerializeToProtocol(Scope& scope) + bool LocalScopeStateNode::SerializeToProtocol(dap::Scope& scope) { scope.name = "Local"; scope.expensive = false; diff --git a/src/DarkId.Papyrus.DebugServer/LocalScopeStateNode.h b/src/DarkId.Papyrus.DebugServer/LocalScopeStateNode.h index bb29b1a5..e8e0bbfe 100644 --- a/src/DarkId.Papyrus.DebugServer/LocalScopeStateNode.h +++ b/src/DarkId.Papyrus.DebugServer/LocalScopeStateNode.h @@ -1,7 +1,7 @@ #pragma once #include "GameInterfaces.h" -#include "Protocol/protocol.h" +#include #include "StateNodeBase.h" @@ -14,7 +14,7 @@ namespace DarkId::Papyrus::DebugServer public: LocalScopeStateNode(RE::BSScript::StackFrame* stackFrame); - bool SerializeToProtocol(Scope& scope) override; + bool SerializeToProtocol(dap::Scope& scope) override; bool GetChildNames(std::vector& names) override; bool GetChildNode(std::string name, std::shared_ptr& node) override; }; diff --git a/src/DarkId.Papyrus.DebugServer/MetaNode.h b/src/DarkId.Papyrus.DebugServer/MetaNode.h index fcadb37b..60583f33 100644 --- a/src/DarkId.Papyrus.DebugServer/MetaNode.h +++ b/src/DarkId.Papyrus.DebugServer/MetaNode.h @@ -27,7 +27,7 @@ namespace DarkId::Papyrus::DebugServer { } - bool SerializeToProtocol(Variable& variable) override + bool SerializeToProtocol(dap::Variable& variable) override { variable.name = m_name; variable.type = std::string(typeid(T).name()); @@ -54,7 +54,7 @@ namespace DarkId::Papyrus::DebugServer { } - bool SerializeToProtocol(Variable& variable) override + bool SerializeToProtocol(dap::Variable& variable) override { variable.name = m_name; variable.type = std::string(typeid(T).name()); @@ -113,7 +113,7 @@ namespace DarkId::Papyrus::DebugServer { } - bool SerializeToProtocol(Variable& variable) override + bool SerializeToProtocol(dap::Variable& variable) override { variable.variablesReference = GetId(); variable.namedVariables = meta::getMemberCount(); diff --git a/src/DarkId.Papyrus.DebugServer/ObjectStateNode.cpp b/src/DarkId.Papyrus.DebugServer/ObjectStateNode.cpp index 17caabfd..6c993c4a 100644 --- a/src/DarkId.Papyrus.DebugServer/ObjectStateNode.cpp +++ b/src/DarkId.Papyrus.DebugServer/ObjectStateNode.cpp @@ -19,7 +19,7 @@ namespace DarkId::Papyrus::DebugServer } } - bool ObjectStateNode::SerializeToProtocol(Variable& variable) + bool ObjectStateNode::SerializeToProtocol(dap::Variable& variable) { variable.variablesReference = m_value ? GetId() : 0; @@ -42,7 +42,7 @@ namespace DarkId::Papyrus::DebugServer } else { - variable.value = variable.type; + variable.value = m_class->GetName(); } } else diff --git a/src/DarkId.Papyrus.DebugServer/ObjectStateNode.h b/src/DarkId.Papyrus.DebugServer/ObjectStateNode.h index 01a6e4e9..87c035ab 100644 --- a/src/DarkId.Papyrus.DebugServer/ObjectStateNode.h +++ b/src/DarkId.Papyrus.DebugServer/ObjectStateNode.h @@ -1,7 +1,7 @@ #pragma once #include "GameInterfaces.h" -#include "Protocol/protocol.h" +#include #include "StateNodeBase.h" #include @@ -18,7 +18,7 @@ namespace DarkId::Papyrus::DebugServer public: ObjectStateNode(std::string name, RE::BSScript::Object* value, RE::BSScript::ObjectTypeInfo* asClass, bool subView = false); - bool SerializeToProtocol(Variable& variable) override; + bool SerializeToProtocol(dap::Variable& variable) override; bool GetChildNames(std::vector& names) override; bool GetChildNode(std::string name, std::shared_ptr& node) override; diff --git a/src/DarkId.Papyrus.DebugServer/PDError.h b/src/DarkId.Papyrus.DebugServer/PDError.h deleted file mode 100644 index a8380db1..00000000 --- a/src/DarkId.Papyrus.DebugServer/PDError.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once -namespace DarkId::Papyrus::DebugServer -{ - enum PDError { - NO_PEX_DATA = -3, - NO_DEBUG_INFO = -2, - ERROR_BUG = -1, - OK = 0 - }; -} \ No newline at end of file diff --git a/src/DarkId.Papyrus.DebugServer/PapyrusDebugger.cpp b/src/DarkId.Papyrus.DebugServer/PapyrusDebugger.cpp index 26e88031..6fbf63d8 100644 --- a/src/DarkId.Papyrus.DebugServer/PapyrusDebugger.cpp +++ b/src/DarkId.Papyrus.DebugServer/PapyrusDebugger.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include "Utilities.h" #include "GameInterfaces.h" @@ -26,10 +28,9 @@ namespace RE{ namespace DarkId::Papyrus::DebugServer { - PapyrusDebugger::PapyrusDebugger(Protocol* protocol) + PapyrusDebugger::PapyrusDebugger(const std::shared_ptr& session): + m_session(session) { - m_protocol = protocol; - m_pexCache = std::make_shared(); m_breakpointManager = std::make_shared(m_pexCache.get()); @@ -37,7 +38,7 @@ namespace DarkId::Papyrus::DebugServer m_idProvider = std::make_shared(); m_runtimeState = std::make_shared(m_idProvider); - m_executionManager = std::make_shared(m_protocol, m_runtimeState.get(), m_breakpointManager.get()); + m_executionManager = std::make_shared(m_session, m_runtimeState.get(), m_breakpointManager.get()); m_createStackEventHandle = RuntimeEvents::SubscribeToCreateStack(std::bind(&PapyrusDebugger::StackCreated, this, std::placeholders::_1)); @@ -53,12 +54,53 @@ namespace DarkId::Papyrus::DebugServer m_logEventHandle = RuntimeEvents::SubscribeToLog(std::bind(&PapyrusDebugger::EventLogged, this, std::placeholders::_1)); + } - //void PapyrusDebugger::InitScriptEvent(RE::TESInitScriptEvent* initEvent) - //{ - //} - + void PapyrusDebugger::RegisterSessionHandlers(){ + m_session->registerHandler([&](const dap::PauseRequest& request) { + return Pause(request); + }); + m_session->registerHandler([&](const dap::ContinueRequest& request) { + return Continue(request); + }); + m_session->registerHandler([&](const dap::PauseRequest& request) { + return Pause(request); + }); + m_session->registerHandler([&](const dap::ThreadsRequest& request) { + return GetThreads(request); + }); + m_session->registerHandler([&](const dap::SetBreakpointsRequest& request) { + return SetBreakpoints(request); + }); + m_session->registerHandler([&](const dap::SetFunctionBreakpointsRequest& request) { + return SetFunctionBreakpoints(request); + }); + m_session->registerHandler([&](const dap::StackTraceRequest& request) { + return GetStackTrace(request); + }); + m_session->registerHandler([&](const dap::StepInRequest& request) { + return StepIn(request); + }); + m_session->registerHandler([&](const dap::StepOutRequest& request) { + return StepOut(request); + }); + m_session->registerHandler([&](const dap::NextRequest& request) { + return Next(request); + }); + m_session->registerHandler([&](const dap::ScopesRequest& request) { + return GetScopes(request); + }); + m_session->registerHandler([&](const dap::VariablesRequest& request) { + return GetVariables(request); + }); + m_session->registerHandler([&](const dap::SourceRequest& request) { + return GetSource(request); + }); + m_session->registerHandler([&](const dap::LoadedSourcesRequest& request) { + return GetLoadedSources(request); + }); + } std::string LogSeverityEnumStr(RE::BSScript::ErrorLogger::Severity severity) { if (severity == RE::BSScript::ErrorLogger::Severity::kInfo) { return std::string("INFO"); @@ -75,19 +117,19 @@ namespace DarkId::Papyrus::DebugServer void PapyrusDebugger::EventLogged(const RE::BSScript::LogEvent* logEvent) const { const std::string severity = LogSeverityEnumStr(logEvent->severity); + dap::OutputEvent output; + output.category = "console"; #if SKYRIM const auto message = std::string(logEvent->errorMsg); - const OutputEvent output(OutputCategory::OutputConsole, severity + " - " + message + "\r\n"); + output.output = message + "\r\n"; #elif FALLOUT RE::BSFixedString message; logEvent->errorMsg.GetErrorMsg(message); const auto msg = std::string(message.c_str()); const auto ownerModule = std::string(logEvent->ownerModule.c_str()); - const OutputEvent output(OutputCategory::OutputConsole, ownerModule + " - " + severity + " - " + msg + "\r\n"); + output.output = ownerModule + " - " + msg + "\r\n"; #endif - - - m_protocol->EmitOutputEvent(output); + m_session->send(output); } @@ -107,8 +149,10 @@ namespace DarkId::Papyrus::DebugServer { return; } - - m_protocol->EmitThreadEvent(ThreadEvent(ThreadReason::ThreadStarted, stackId)); + dap::ThreadEvent threadEvent; + threadEvent.reason = "started"; + threadEvent.threadId = stackId; + m_session->send(threadEvent); if (stack->top && stack->top->owningFunction) { @@ -128,8 +172,11 @@ namespace DarkId::Papyrus::DebugServer { return; } + dap::ThreadEvent threadEvent; + threadEvent.reason = "exited"; + threadEvent.threadId = stackId; - m_protocol->EmitThreadEvent(ThreadEvent(ThreadReason::ThreadExited, stackId)); + m_session->send(threadEvent); }); } @@ -142,63 +189,46 @@ namespace DarkId::Papyrus::DebugServer { if (!m_pexCache->HasScript(scriptName)) { - Source source; + dap::Source source; if (!m_pexCache->GetSourceData(scriptName, source)) { return; } - - m_protocol->EmitLoadedSourceEvent( - LoadedSourceEvent(LoadedSourceReason::SourceNew, source)); + dap::LoadedSourceEvent event; + event.reason = "new"; + event.source = source; + m_session->send(event); } } - HRESULT PapyrusDebugger::SetBreakpoints(Source& source, const std::vector& srcBreakpoints, std::vector& breakpoints) - { - return m_breakpointManager->SetBreakpoints(source, srcBreakpoints, breakpoints); - } - HRESULT PapyrusDebugger::Initialize() + PapyrusDebugger::~PapyrusDebugger() { - m_protocol->EmitInitializedEvent(); - return 0; - } + m_closed = true; - HRESULT PapyrusDebugger::Pause() - { - return m_executionManager->Pause() ? 0 : 1; - } + RuntimeEvents::UnsubscribeFromLog(m_logEventHandle); + // RuntimeEvents::UnsubscribeFromInitScript(m_initScriptEventHandle); + RuntimeEvents::UnsubscribeFromInstructionExecution(m_instructionExecutionEventHandle); + RuntimeEvents::UnsubscribeFromCreateStack(m_createStackEventHandle); + RuntimeEvents::UnsubscribeFromCleanupStack(m_cleanupStackEventHandle); - HRESULT PapyrusDebugger::Continue() - { - return m_executionManager->Continue() ? 0 : 1; + m_executionManager->Close(); } - - HRESULT PapyrusDebugger::GetSource(Source& source, std::string& output) + dap::ResponseOrError PapyrusDebugger::Continue(const dap::ContinueRequest& request) { - return m_pexCache->GetDecompiledSource(source.name.c_str(), output) ? 0 : 1; + if (m_executionManager->Continue()) + dap::ContinueResponse(); + return dap::Error("Could not Continue"); } - - HRESULT PapyrusDebugger::GetLoadedSources(std::vector& sources) + dap::ResponseOrError PapyrusDebugger::Pause(const dap::PauseRequest& request) { - const auto vm = RE::BSScript::Internal::VirtualMachine::GetSingleton(); - RE::BSSpinLockGuard lock(vm->typeInfoLock); - - for (const auto& script : vm->objectTypeMap) - { - Source source; - std::string scriptName = script.first.c_str(); - if (m_pexCache->GetSourceData(scriptName.c_str(), source)) - { - sources.push_back(source); - } - } - - return 0; + if (m_executionManager->Pause()) + dap::PauseResponse(); + return dap::Error("Could not Pause"); } - - HRESULT PapyrusDebugger::GetThreads(std::vector& threads) + dap::ResponseOrError PapyrusDebugger::GetThreads(const dap::ThreadsRequest& request) { + dap::ThreadsResponse response; const auto vm = RE::BSScript::Internal::VirtualMachine::GetSingleton(); RE::BSSpinLockGuard lock(vm->runningStacksLock); @@ -225,23 +255,90 @@ namespace DarkId::Papyrus::DebugServer const auto node = dynamic_cast(stateNode.get()); - Thread thread; + dap::Thread thread; if (node->SerializeToProtocol(thread)) { - threads.push_back(thread); + response.threads.push_back(thread); } } - return 0; + return response; } + dap::ResponseOrError PapyrusDebugger::SetBreakpoints(const dap::SetBreakpointsRequest& request) + { + dap::SetBreakpointsResponse response; + dap::Source source = request.source; + return m_breakpointManager->SetBreakpoints(source, request.breakpoints.value(std::vector())); + } + + dap::ResponseOrError PapyrusDebugger::SetFunctionBreakpoints(const dap::SetFunctionBreakpointsRequest& request) + { + return dap::Error("unimplemented"); + } + dap::ResponseOrError PapyrusDebugger::GetStackTrace(const dap::StackTraceRequest& request) + { + dap::StackTraceResponse response; + const auto vm = RE::BSScript::Internal::VirtualMachine::GetSingleton(); + RE::BSSpinLockGuard lock(vm->runningStacksLock); + + if (request.threadId == -1) + { + response.totalFrames = 0; + return dap::Error("No threadId specified"); + } + + std::vector> frameNodes; + if (!m_runtimeState->ResolveChildrenByParentPath(std::to_string(request.threadId), frameNodes)) + { + return dap::Error("Could not find ThreadId"); + } + auto startFrame = request.startFrame.value(0); + auto levels = request.levels.value(frameNodes.size()); + for (auto frameIndex = startFrame; frameIndex < frameNodes.size() && frameIndex < startFrame + levels; frameIndex++) + { + const auto node = dynamic_cast(frameNodes.at(frameIndex).get()); - HRESULT PapyrusDebugger::GetScopes(const uint64_t frameId, std::vector& scopes) + dap::StackFrame frame; + if (!node->SerializeToProtocol(frame, m_pexCache.get())) { + return dap::Error("Serialization error"); + } + + response.stackFrames.push_back(frame); + } + return response; + } + dap::ResponseOrError PapyrusDebugger::StepIn(const dap::StepInRequest& request) + { + // TODO: Support `granularity` and `target` + if (m_executionManager->Step(request.threadId, STEP_IN)) { + dap::StepInResponse(); + } + return dap::Error("Could not StepIn"); + } + dap::ResponseOrError PapyrusDebugger::StepOut(const dap::StepOutRequest& request) { + if (m_executionManager->Step(request.threadId, STEP_OUT)) { + dap::StepOutResponse(); + } + return dap::Error("Could not StepOut"); + } + dap::ResponseOrError PapyrusDebugger::Next(const dap::NextRequest& request) + { + if (m_executionManager->Step(request.threadId, STEP_OVER)) { + dap::NextResponse(); + } + return dap::Error("Could not Next"); + } + dap::ResponseOrError PapyrusDebugger::GetScopes(const dap::ScopesRequest& request) + { + dap::ScopesResponse response; const auto vm = RE::BSScript::Internal::VirtualMachine::GetSingleton(); RE::BSSpinLockGuard lock(vm->runningStacksLock); std::vector> frameScopes; - m_runtimeState->ResolveChildrenByParentId(frameId, frameScopes); + if (!m_runtimeState->ResolveChildrenByParentId(request.frameId, frameScopes)) { + return dap::Error("No scopes for frameId %d", request.frameId); + } for (const auto& frameScope : frameScopes) { @@ -250,98 +347,88 @@ namespace DarkId::Papyrus::DebugServer { continue; } - - Scope scope; + + dap::Scope scope; if (!asScopeSerializable->SerializeToProtocol(scope)) { continue; } - - scopes.push_back(scope); + + response.scopes.push_back(scope); } - return 0; + return response; } - - HRESULT PapyrusDebugger::GetVariables(uint64_t variablesReference, VariablesFilter filter, int start, int count, std::vector & variables) + dap::ResponseOrError PapyrusDebugger::GetVariables(const dap::VariablesRequest& request) { + dap::VariablesResponse response; + const auto vm = RE::BSScript::Internal::VirtualMachine::GetSingleton(); RE::BSSpinLockGuard lock(vm->runningStacksLock); std::vector> variableNodes; - m_runtimeState->ResolveChildrenByParentId(variablesReference, variableNodes); + if (!m_runtimeState->ResolveChildrenByParentId(request.variablesReference, variableNodes)) { + return dap::Error("No such variable reference %d", request.variablesReference); + } + // TODO: support `start`, `filter`, parameter + int count = 0; + int maxCount = request.count.value(variableNodes.size()); for (const auto& variableNode : variableNodes) { + if (count > maxCount) { + break; + } auto asVariableSerializable = dynamic_cast(variableNode.get()); if (!asVariableSerializable) { continue; } - Variable variable; + dap::Variable variable; if (!asVariableSerializable->SerializeToProtocol(variable)) { continue; } - - variables.push_back(variable); + + response.variables.push_back(variable); + count++; } - return 0; + return response; } - - int PapyrusDebugger::GetNamedVariables(uint64_t variablesReference) + dap::ResponseOrError PapyrusDebugger::GetSource(const dap::SourceRequest& request) { - // logger::info("Named variables count request: %d", variablesReference); - return 0; + if (!request.source.has_value() || !request.source.value().name.has_value()) { + if (!request.sourceReference) { + return dap::Error("No source name or sourceReference"); + } else { + // TODO: Support this? + return dap::Error("No source name"); + } + } + std::string name = request.source.value().name.value(); + dap::SourceResponse response; + if (m_pexCache->GetDecompiledSource(name.c_str(), response.content)) { + return response; + } + return dap::Error("Could not find source " + name); } - - HRESULT PapyrusDebugger::GetStackTrace(int threadId, int startFrame, int levels, std::vector & stackFrames, int& totalFrames) + dap::ResponseOrError PapyrusDebugger::GetLoadedSources(const dap::LoadedSourcesRequest& request) { + dap::LoadedSourcesResponse response; const auto vm = RE::BSScript::Internal::VirtualMachine::GetSingleton(); - RE::BSSpinLockGuard lock(vm->runningStacksLock); - - if (threadId == -1) - { - totalFrames = 0; - return 0; - } - - std::vector> frameNodes; - if (!m_runtimeState->ResolveChildrenByParentPath(std::to_string(threadId), frameNodes)) - { - return 0; - } + RE::BSSpinLockGuard lock(vm->typeInfoLock); - for (auto frameIndex = startFrame; frameIndex < frameNodes.size() && frameIndex < startFrame + levels; frameIndex++) + for (const auto& script : vm->objectTypeMap) { - const auto node = dynamic_cast(frameNodes.at(frameIndex).get()); - - StackFrame frame; - node->SerializeToProtocol(frame, m_pexCache.get()); - - stackFrames.push_back(frame); + dap::Source source; + std::string scriptName = script.first.c_str(); + if (m_pexCache->GetSourceData(scriptName.c_str(), source)) + { + response.sources.push_back(source); + } } - - return 1; - } - - HRESULT PapyrusDebugger::StepCommand(int threadId, StepType stepType) - { - return m_executionManager->Step(threadId, stepType) ? 0 : 1; - } - - PapyrusDebugger::~PapyrusDebugger() - { - m_closed = true; - - RuntimeEvents::UnsubscribeFromLog(m_logEventHandle); - // RuntimeEvents::UnsubscribeFromInitScript(m_initScriptEventHandle); - RuntimeEvents::UnsubscribeFromInstructionExecution(m_instructionExecutionEventHandle); - RuntimeEvents::UnsubscribeFromCreateStack(m_createStackEventHandle); - RuntimeEvents::UnsubscribeFromCleanupStack(m_cleanupStackEventHandle); - - m_executionManager->Close(); + return response; } } diff --git a/src/DarkId.Papyrus.DebugServer/PapyrusDebugger.h b/src/DarkId.Papyrus.DebugServer/PapyrusDebugger.h index 4964d270..32563708 100644 --- a/src/DarkId.Papyrus.DebugServer/PapyrusDebugger.h +++ b/src/DarkId.Papyrus.DebugServer/PapyrusDebugger.h @@ -1,7 +1,7 @@ #pragma once -#include "Protocol/debugger.h" - +#include +#include #include "RuntimeEvents.h" #include "PexCache.h" #include "BreakpointManager.h" @@ -11,47 +11,64 @@ namespace DarkId::Papyrus::DebugServer { - class PapyrusDebugger : - public Debugger + enum DisconnectAction + { + DisconnectDefault, // Attach -> Detach, Launch -> Terminate + DisconnectTerminate, + DisconnectDetach + }; + + + enum VariablesFilter + { + VariablesNamed, + VariablesIndexed, + VariablesBoth + }; + class PapyrusDebugger { public: - PapyrusDebugger(Protocol* protocol); + PapyrusDebugger(const std::shared_ptr &session); ~PapyrusDebugger(); bool IsJustMyCode() const { return false; }; void SetJustMyCode(bool enable) { }; - HRESULT Initialize() override; - HRESULT Attach() override { return 0; }; - HRESULT Launch(std::string fileExec, std::vector execArgs, bool stopAtEntry = false) override { return 0; } - HRESULT ConfigurationDone() override { return 0; } - - HRESULT Disconnect(DisconnectAction action = DisconnectDefault) override { return 0; } - - int GetLastStoppedThreadId() override { return 0; } - - HRESULT Continue() override; - HRESULT Pause() override; - HRESULT GetThreads(std::vector& threads) override; - HRESULT SetBreakpoints(Source& source, const std::vector& srcBreakpoints, std::vector& breakpoints) override; - HRESULT SetFunctionBreakpoints(const std::vector& funcBreakpoints, std::vector& breakpoints) override { return 0; } - void InsertExceptionBreakpoint(const std::string& name, Breakpoint& breakpoint) override { } - HRESULT GetStackTrace(int threadId, int startFrame, int levels, std::vector& stackFrames, int& totalFrames) override; - HRESULT StepCommand(int threadId, StepType stepType) override; - HRESULT GetScopes(uint64_t frameId, std::vector& scopes) override; - HRESULT GetVariables(uint64_t variablesReference, VariablesFilter filter, int start, int count, std::vector& variables) override; - int GetNamedVariables(uint64_t variablesReference) override; - HRESULT Evaluate(uint64_t frameId, const std::string& expression, Variable& variable, std::string& output) override { return 0; } - HRESULT SetVariable(const std::string& name, const std::string& value, uint32_t ref, std::string& output) override { return 0; } - HRESULT SetVariableByExpression(uint64_t frameId, const std::string& name, const std::string& value, std::string& output) override { return 0; } - HRESULT GetSource(Source& source, std::string& output) override; - HRESULT GetLoadedSources(std::vector& sources) override; + //HRESULT Initialize() ; + // HRESULT Attach() { return 0; }; + // HRESULT Launch(std::string fileExec, std::vector execArgs, bool stopAtEntry = false) { return 0; } + // HRESULT ConfigurationDone() { return 0; } + + // dap::Response Disconnect(DisconnectAction action = DisconnectDefault) { return dap::DisconnectResponse(); } + + int GetLastStoppedThreadId() { return 0; } + + dap::ResponseOrError Continue(const dap::ContinueRequest& request) ; + dap::ResponseOrError Pause(const dap::PauseRequest& request) ; + dap::ResponseOrError GetThreads(const dap::ThreadsRequest& request) ; + dap::ResponseOrError SetBreakpoints(const dap::SetBreakpointsRequest& request) ; + dap::ResponseOrError SetFunctionBreakpoints(const dap::SetFunctionBreakpointsRequest& request); + dap::ResponseOrError GetStackTrace(const dap::StackTraceRequest& request) ; + dap::ResponseOrError StepIn(const dap::StepInRequest& request); + dap::ResponseOrError StepOut(const dap::StepOutRequest& request); + dap::ResponseOrError Next(const dap::NextRequest& request); + dap::ResponseOrError GetScopes(const dap::ScopesRequest& request) ; + dap::ResponseOrError GetVariables(const dap::VariablesRequest& request); + dap::ResponseOrError GetSource(const dap::SourceRequest& request); + dap::ResponseOrError GetLoadedSources(const dap::LoadedSourcesRequest& request); + + // dap::Response Evaluate(const dap::SetBreakpointsRequest& request) { return 0; } + // dap::Response SetVariable(const dap::SetBreakpointsRequest& request) { return 0; } + // dap::Response SetVariableByExpression(const dap::SetBreakpointsRequest& request) { return 0; } + // void InsertExceptionBreakpoint(const std::string& name, dap::Breakpoint& breakpoint) { } + // int GetNamedVariables(uint64_t variablesReference) ; + private: bool m_closed = false; - + std::atomic msg_counter = 0; std::shared_ptr m_idProvider; - Protocol* m_protocol; + std::shared_ptr m_session; std::shared_ptr m_pexCache; std::shared_ptr m_breakpointManager; std::shared_ptr m_runtimeState; @@ -65,6 +82,8 @@ namespace DarkId::Papyrus::DebugServer // RuntimeEvents::InitScriptEventHandle m_initScriptEventHandle; RuntimeEvents::LogEventHandle m_logEventHandle; + void RegisterSessionHandlers(); + // void InitScriptEvent(RE::TESInitScriptEvent* initEvent); void EventLogged(const RE::BSScript::LogEvent* logEvent) const; void StackCreated(RE::BSTSmartPointer& stack); diff --git a/src/DarkId.Papyrus.DebugServer/PexCache.cpp b/src/DarkId.Papyrus.DebugServer/PexCache.cpp index 80225632..0a62a0bc 100644 --- a/src/DarkId.Papyrus.DebugServer/PexCache.cpp +++ b/src/DarkId.Papyrus.DebugServer/PexCache.cpp @@ -5,8 +5,8 @@ #include #include #include -#include -#include +#include +#include namespace DarkId::Papyrus::DebugServer { @@ -24,7 +24,7 @@ namespace DarkId::Papyrus::DebugServer int PexCache::GetScriptReference(const char* scriptName) const { - const std::hash hasher; + const std::hash hasher{}; std::string name = NormalizeScriptName(scriptName); std::transform(name.begin(), name.end(), name.begin(), ::tolower); @@ -69,7 +69,7 @@ namespace DarkId::Papyrus::DebugServer return true; } - bool PexCache::GetSourceData(const char* scriptName, Source& data) + bool PexCache::GetSourceData(const char* scriptName, dap::Source& data) { const auto sourceReference = GetScriptReference(scriptName); @@ -84,8 +84,9 @@ namespace DarkId::Papyrus::DebugServer if (headerSrcName.empty()) { headerSrcName = ScriptNameToPSCPath(normname); } - data = Source(normname, headerSrcName, sourceReference); - + data.name = normname; + data.path = headerSrcName; + data.sourceReference = sourceReference; return true; } } diff --git a/src/DarkId.Papyrus.DebugServer/PexCache.h b/src/DarkId.Papyrus.DebugServer/PexCache.h index 9d705547..c23e76ee 100644 --- a/src/DarkId.Papyrus.DebugServer/PexCache.h +++ b/src/DarkId.Papyrus.DebugServer/PexCache.h @@ -3,7 +3,7 @@ #include #include -#include "Protocol/protocol.h" +#include #include namespace DarkId::Papyrus::DebugServer @@ -20,7 +20,7 @@ namespace DarkId::Papyrus::DebugServer std::shared_ptr GetScript(const char* scriptName); bool GetDecompiledSource(const char* scriptName, std::string& decompiledSource); - bool GetSourceData(const char* scriptName, Source& data); + bool GetSourceData(const char* scriptName, dap::Source& data); private: std::mutex m_scriptsMutex; std::map> m_scripts; diff --git a/src/DarkId.Papyrus.DebugServer/Protocol/LICENSE b/src/DarkId.Papyrus.DebugServer/Protocol/LICENSE deleted file mode 100644 index a08e01a2..00000000 --- a/src/DarkId.Papyrus.DebugServer/Protocol/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 Samsung Electronics Co., LTD - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/src/DarkId.Papyrus.DebugServer/Protocol/cputil.cpp b/src/DarkId.Papyrus.DebugServer/Protocol/cputil.cpp deleted file mode 100644 index 12d0475c..00000000 --- a/src/DarkId.Papyrus.DebugServer/Protocol/cputil.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2018 Samsung Electronics Co., LTD -// Distributed under the MIT License. -// See the LICENSE file in the project root for more information. - -#include "cputil.h" - -#include -#include - -#ifdef _MSC_VER - -static std::wstring_convert,wchar_t> convert; - -std::string to_utf8(const wchar_t *wstr) -{ - return convert.to_bytes(wstr); -} - -#else - -static std::wstring_convert,char16_t> convert; - -std::string to_utf8(const char16_t *wstr) -{ - return convert.to_bytes(wstr); -} - -#endif - -std::string to_utf8(char16_t wch) -{ - return convert.to_bytes(wch); -} - -#ifdef _MSC_VER -std::wstring -#else -std::u16string -#endif -to_utf16(const std::string &utf8) -{ - return convert.from_bytes(utf8); -} - -std::vector split_on_tokens(const std::string &str, const char delim) -{ - std::vector res; - size_t pos = 0, prev = 0; - - while (true) - { - pos = str.find(delim, prev); - if (pos == std::string::npos) - { - res.push_back(std::string(str, prev)); - break; - } - - res.push_back(std::string(str, prev, pos - prev)); - prev = pos + 1; - } - - return res; -} diff --git a/src/DarkId.Papyrus.DebugServer/Protocol/cputil.h b/src/DarkId.Papyrus.DebugServer/Protocol/cputil.h deleted file mode 100644 index cb465154..00000000 --- a/src/DarkId.Papyrus.DebugServer/Protocol/cputil.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2017 Samsung Electronics Co., LTD -// Distributed under the MIT License. -// See the LICENSE file in the project root for more information. -#pragma once - -#include -#include - -#ifdef _MSC_VER -std::string to_utf8(const wchar_t *wstr); -std::wstring to_utf16(const std::string &utf8); -#else -std::string to_utf8(const char16_t *wstr); -std::u16string to_utf16(const std::string &utf8); -#endif - -std::string to_utf8(char16_t wch); -std::vector split_on_tokens(const std::string &str, const char delim); diff --git a/src/DarkId.Papyrus.DebugServer/Protocol/debugger.h b/src/DarkId.Papyrus.DebugServer/Protocol/debugger.h index a3185bba..766cc116 100644 --- a/src/DarkId.Papyrus.DebugServer/Protocol/debugger.h +++ b/src/DarkId.Papyrus.DebugServer/Protocol/debugger.h @@ -1,90 +1,77 @@ -// Copyright (c) 2017 Samsung Electronics Co., LTD -// Distributed under the MIT License. -// See the LICENSE file in the project root for more information. - #pragma once -#include -#include - -// For `HRESULT` definition -#ifdef FEATURE_PAL -#include -#else -#include -#endif - -#include "protocol.h" +#include -class Debugger -{ -public: - enum StepType { - STEP_IN = 0, - STEP_OVER, - STEP_OUT - }; +#include +#include - enum DisconnectAction +namespace dap{ + class Debugger { - DisconnectDefault, // Attach -> Detach, Launch -> Terminate - DisconnectTerminate, - DisconnectDetach - }; - - virtual ~Debugger() {} - virtual bool IsJustMyCode() const = 0; - virtual void SetJustMyCode(bool enable) = 0; + public: + enum StepType { + STEP_IN = 0, + STEP_OVER, + STEP_OUT + }; + enum VariablesFilter + { + VariablesNamed, + VariablesIndexed, + VariablesBoth + }; + enum DisconnectAction + { + DisconnectDefault, // Attach -> Detach, Launch -> Terminate + DisconnectTerminate, + DisconnectDetach + }; + + virtual ~Debugger() {} - virtual HRESULT Initialize() = 0; - virtual HRESULT Attach() = 0; - virtual HRESULT Launch(std::string fileExec, std::vector execArgs, bool stopAtEntry = false) = 0; - virtual HRESULT ConfigurationDone() = 0; + virtual ResponseOrError Attach(const AttachRequest& request) = 0; + virtual ResponseOrError BreakpointLocations(const BreakpointLocationsRequest& request) = 0; + virtual ResponseOrError Completions(const CompletionsRequest& request) = 0; + virtual ResponseOrError ConfigurationDone(const ConfigurationDoneRequest& request) = 0; + virtual ResponseOrError Continue(const ContinueRequest& request) = 0; + virtual ResponseOrError DataBreakpointInfo(const DataBreakpointInfoRequest& request) = 0; + virtual ResponseOrError Disassemble(const DisassembleRequest& request) = 0; + virtual ResponseOrError Disconnect(const DisconnectRequest& request) = 0; + virtual ResponseOrError Evaluate(const EvaluateRequest& request) = 0; + virtual ResponseOrError ExceptionInfo(const ExceptionInfoRequest& request) = 0; + virtual ResponseOrError Goto(const GotoRequest& request) = 0; + virtual ResponseOrError GotoTargets(const GotoTargetsRequest& request) = 0; + virtual ResponseOrError Initialize(const InitializeRequest& request) = 0; + virtual ResponseOrError Launch(const LaunchRequest& request) = 0; + virtual ResponseOrError LoadedSources(const LoadedSourcesRequest& request) = 0; + virtual ResponseOrError Modules(const ModulesRequest& request) = 0; + virtual ResponseOrError Next(const NextRequest& request) = 0; + virtual ResponseOrError Pause(const PauseRequest& request) = 0; + virtual ResponseOrError ReadMemory(const ReadMemoryRequest& request) = 0; + virtual ResponseOrError Restart(const RestartRequest& request) = 0; + virtual ResponseOrError RestartFrame(const RestartFrameRequest& request) = 0; + virtual ResponseOrError ReverseContinue(const ReverseContinueRequest& request) = 0; + virtual ResponseOrError Scopes(const ScopesRequest& request) = 0; + virtual ResponseOrError SetBreakpoints(const SetBreakpointsRequest& request) = 0; + virtual ResponseOrError SetDataBreakpoints(const SetDataBreakpointsRequest& request) = 0; + virtual ResponseOrError SetExceptionBreakpoints(const SetExceptionBreakpointsRequest& request) = 0; + virtual ResponseOrError SetExpression(const SetExpressionRequest& request) = 0; + virtual ResponseOrError SetFunctionBreakpoints(const SetFunctionBreakpointsRequest& request) = 0; + virtual ResponseOrError SetInstructionBreakpoints(const SetInstructionBreakpointsRequest& request) = 0; + virtual ResponseOrError SetVariable(const SetVariableRequest& request) = 0; + virtual ResponseOrError Source(const SourceRequest& request) = 0; + virtual ResponseOrError StackTrace(const StackTraceRequest& request) = 0; + virtual ResponseOrError StepBack(const StepBackRequest& request) = 0; + virtual ResponseOrError StepIn(const StepInRequest& request) = 0; + virtual ResponseOrError StepInTargets(const StepInTargetsRequest& request) = 0; + virtual ResponseOrError StepOut(const StepOutRequest& request) = 0; + virtual ResponseOrError Terminate(const TerminateRequest& request) = 0; + virtual ResponseOrError TerminateThreads(const TerminateThreadsRequest& request) = 0; + virtual ResponseOrError Threads(const ThreadsRequest& request) = 0; + virtual ResponseOrError Variables(const VariablesRequest& request) = 0; + protected: + std::shared_ptr m_Session; - virtual HRESULT Disconnect(DisconnectAction action = DisconnectDefault) = 0; - - virtual int GetLastStoppedThreadId() = 0; - - virtual HRESULT Continue() = 0; - virtual HRESULT Pause() = 0; - virtual HRESULT GetThreads(std::vector &threads) = 0; - virtual HRESULT SetBreakpoints(Source& source, const std::vector &srcBreakpoints, std::vector &breakpoints) = 0; - virtual HRESULT SetFunctionBreakpoints(const std::vector &funcBreakpoints, std::vector &breakpoints) = 0; - virtual void InsertExceptionBreakpoint(const std::string &name, Breakpoint &breakpoint) = 0; - virtual HRESULT GetStackTrace(int threadId, int startFrame, int levels, std::vector &stackFrames, int &totalFrames) = 0; - virtual HRESULT StepCommand(int threadId, StepType stepType) = 0; - virtual HRESULT GetScopes(uint64_t frameId, std::vector &scopes) = 0; - virtual HRESULT GetVariables(uint64_t variablesReference, VariablesFilter filter, int start, int count, std::vector &variables) = 0; - virtual int GetNamedVariables(uint64_t variablesReference) = 0; - virtual HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable, std::string &output) = 0; - virtual HRESULT SetVariable(const std::string &name, const std::string &value, uint32_t ref, std::string &output) = 0; - virtual HRESULT SetVariableByExpression(uint64_t frameId, const std::string &name, const std::string &value, std::string &output) = 0; - virtual HRESULT GetSource(Source &source, std::string& output) = 0; - virtual HRESULT GetLoadedSources(std::vector& sources) = 0; -}; - -class Protocol -{ -protected: - bool m_exit; - Debugger *m_debugger; - -public: - Protocol() : m_exit(false), m_debugger(nullptr) {} - void SetDebugger(Debugger *debugger) { m_debugger = debugger; } - - virtual void EmitInitializedEvent() = 0; - virtual void EmitStoppedEvent(StoppedEvent event) = 0; - virtual void EmitExitedEvent(ExitedEvent event) = 0; - virtual void EmitTerminatedEvent() = 0; - virtual void EmitContinuedEvent(ContinuedEvent event) = 0; - virtual void EmitThreadEvent(ThreadEvent event) = 0; - virtual void EmitModuleEvent(ModuleEvent event) = 0; - virtual void EmitLoadedSourceEvent(LoadedSourceEvent event) = 0; - virtual void EmitOutputEvent(OutputEvent event) = 0; - virtual void EmitBreakpointEvent(BreakpointEvent event) = 0; - virtual void Cleanup() = 0; - virtual void CommandLoop() = 0; - virtual ~Protocol() {} -}; + }; +} \ No newline at end of file diff --git a/src/DarkId.Papyrus.DebugServer/Protocol/protocol.h b/src/DarkId.Papyrus.DebugServer/Protocol/protocol.h deleted file mode 100644 index 9c724518..00000000 --- a/src/DarkId.Papyrus.DebugServer/Protocol/protocol.h +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) 2017 Samsung Electronics Co., LTD -// Distributed under the MIT License. -// See the LICENSE file in the project root for more information. - -#pragma once - -#include -#include - - - -// From https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts - -struct Thread -{ - int id; - std::string name; - bool running; - - Thread() {} - Thread(int id, std::string name, bool running) : id(id), name(name), running(running) {} -}; - -struct Source -{ - std::string name; - std::string path; - int sourceReference; - - Source(std::string name = std::string(), std::string path = std::string(), int sourceReference = 0) : name(name), path(path), sourceReference(sourceReference) {} - bool IsNull() const { return name.empty() && path.empty() && sourceReference == 0; } -}; - - -struct StackFrame -{ - uint32_t id; // (threadId << 32) | level - std::string name; - Source source; - int line; - int column; - int endLine; - int endColumn; - std::string moduleId; - - StackFrame() : - id(0), line(0), column(0), endLine(0), endColumn(0) {} - - StackFrame(uint64_t id) : id(id), line(0), column(0), endLine(0), endColumn(0) {} -}; - -struct Breakpoint -{ - bool verified; - std::string message; - Source source; - int line; - - std::string condition; - std::string module; - std::string params; - - Breakpoint() : verified(false), line(0) {} -}; - -enum SymbolStatus -{ - SymbolsSkipped, // "Skipped loading symbols." - SymbolsLoaded, // "Symbols loaded." - SymbolsNotFound -}; - -struct Module -{ - std::string id; - std::string name; - std::string path; - // bool isOptimized; // TODO: support both fields for VSCode protocol - // bool isUserCode; - SymbolStatus symbolStatus; - uint64_t baseAddress; // exposed for MI protocol - uint32_t size; // exposed for MI protocol - - Module() : symbolStatus(SymbolsSkipped), baseAddress(0), size(0) {} -}; - -enum BreakpointReason -{ - BreakpointChanged, - BreakpointNew, - BreakpointRemoved -}; - -enum StopReason -{ - StopStep, - StopBreakpoint, - StopException, - StopPause, - StopEntry -}; - -struct StoppedEvent -{ - StopReason reason; - std::string description; - int threadId; - std::string text; - bool allThreadsStopped; - - StackFrame frame; // exposed for MI protocol - Breakpoint breakpoint; // exposed for MI protocol - - StoppedEvent(StopReason reason, int threadId = 0) : reason(reason), threadId(threadId), allThreadsStopped(true) {} -}; - -struct ContinuedEvent -{ - int threadId; - bool allThreadsContinued; - - ContinuedEvent(int threadId = 0) : threadId(threadId), allThreadsContinued(true) {} -}; - -struct BreakpointEvent -{ - BreakpointReason reason; - Breakpoint breakpoint; - - BreakpointEvent(BreakpointReason reason, Breakpoint breakpoint) : reason(reason), breakpoint(breakpoint) {} -}; - -struct ExitedEvent -{ - int exitCode; - - ExitedEvent(int exitCode) : exitCode(exitCode) {} -}; - -enum ThreadReason -{ - ThreadStarted, - ThreadExited -}; - -struct ThreadEvent -{ - ThreadReason reason; - int threadId; - - ThreadEvent(ThreadReason reason, int threadId) : reason(reason), threadId(threadId) {} -}; - -enum OutputCategory -{ - OutputConsole, - OutputStdOut, - OutputStdErr -}; - -struct OutputEvent -{ - OutputCategory category; - std::string output; - - std::string source; // exposed for MI protocol - - OutputEvent(OutputCategory category, std::string output) : category(category), output(output) {} -}; - -enum ModuleReason -{ - ModuleNew, - ModuleChanged, - ModuleRemoved -}; - -struct ModuleEvent -{ - ModuleReason reason; - Module module; - ModuleEvent(ModuleReason reason, const Module &module) : reason(reason), module(module) {} -}; - -enum LoadedSourceReason -{ - SourceNew, - SourceChanged, - SourceRemoved -}; - -struct LoadedSourceEvent -{ - LoadedSourceReason reason; - Source source; - LoadedSourceEvent(LoadedSourceReason reason, const Source& source) : reason(reason), source(source) {} -}; - -struct Scope -{ - std::string name; - uint64_t variablesReference; - int namedVariables; - int indexedVariables; - bool expensive; - - Scope() : variablesReference(0), namedVariables(0), expensive(false) {} - - Scope(uint64_t variablesReference, const std::string &name, int namedVariables) : - name(name), - variablesReference(variablesReference), - namedVariables(namedVariables), - indexedVariables(0), - expensive(false) - {} -}; - - -// TODO: Replace strings with enums -struct VariablePresentationHint -{ - std::string kind; - std::vector attributes; - std::string visibility; -}; - -struct Variable -{ - std::string name; - std::string value; - std::string type; - VariablePresentationHint presentationHint; - std::string evaluateName; - uint32_t variablesReference; - int namedVariables; - int indexedVariables; - - Variable() : variablesReference(0), namedVariables(0), indexedVariables(0) {} -}; - -enum VariablesFilter -{ - VariablesNamed, - VariablesIndexed, - VariablesBoth -}; - -struct SourceBreakpoint -{ - int line; - std::string condition; - - SourceBreakpoint(int linenum, const std::string &cond = std::string()) : line(linenum), condition(cond) {} -}; - -struct FunctionBreakpoint -{ - std::string module; - std::string func; - std::string params; - std::string condition; - - FunctionBreakpoint(const std::string &module, - const std::string &func, - const std::string ¶ms, - const std::string &cond = std::string()) : - module(module), - func(func), - params(params), - condition(cond) - {} -}; diff --git a/src/DarkId.Papyrus.DebugServer/Protocol/torelease.h b/src/DarkId.Papyrus.DebugServer/Protocol/torelease.h deleted file mode 100644 index 18b36841..00000000 --- a/src/DarkId.Papyrus.DebugServer/Protocol/torelease.h +++ /dev/null @@ -1,130 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// Copyright (c) 2017 Samsung Electronics Co., LTD - -#pragma once - -#include - -// This class acts a smart pointer which calls the Release method on any object -// you place in it when the ToRelease class falls out of scope. You may use it -// just like you would a standard pointer to a COM object (including if (foo), -// if (!foo), if (foo == 0), etc) except for two caveats: -// 1. This class never calls AddRef and it always calls Release when it -// goes out of scope. -// 2. You should never use & to try to get a pointer to a pointer unless -// you call Release first, or you will leak whatever this object contains -// prior to updating its internal pointer. -template -class ToRelease -{ -public: - ToRelease() - : m_ptr(nullptr) - {} - - ToRelease(T* ptr) - : m_ptr(ptr) - {} - - ~ToRelease() - { - Release(); - } - - void operator=(T *ptr) - { - Release(); - - m_ptr = ptr; - } - - T* operator->() - { - return m_ptr; - } - - operator T*() - { - return m_ptr; - } - - T** operator&() - { - return &m_ptr; - } - - T* GetPtr() const - { - return m_ptr; - } - - T* Detach() - { - T* pT = m_ptr; - m_ptr = nullptr; - return pT; - } - - void Release() - { - if (m_ptr != nullptr) { - m_ptr->Release(); - m_ptr = nullptr; - } - } - - ToRelease(ToRelease&& that) noexcept : m_ptr(that.m_ptr) { that.m_ptr = nullptr; } -private: - ToRelease(const ToRelease& that) = delete; - T* m_ptr; -}; - -#ifndef IfFailRet -#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0) -#endif - -#ifndef _countof -#define _countof(x) (sizeof(x)/sizeof(x[0])) -#endif - -#ifdef PAL_STDCPP_COMPAT -#define _iswprint PAL_iswprint -#define _wcslen PAL_wcslen -#define _wcsncmp PAL_wcsncmp -#define _wcsrchr PAL_wcsrchr -#define _wcscmp PAL_wcscmp -#define _wcschr PAL_wcschr -#define _wcscspn PAL_wcscspn -#define _wcscat PAL_wcscat -#define _wcsstr PAL_wcsstr -#else // PAL_STDCPP_COMPAT -#define _iswprint iswprint -#define _wcslen wcslen -#define _wcsncmp wcsncmp -#define _wcsrchr wcsrchr -#define _wcscmp wcscmp -#define _wcschr wcschr -#define _wcscspn wcscspn -#define _wcscat wcscat -#define _wcsstr wcsstr -#endif // !PAL_STDCPP_COMPAT - -typedef uintptr_t TADDR; -typedef ULONG64 CLRDATA_ADDRESS; - -// Convert between CLRDATA_ADDRESS and TADDR. -#define TO_TADDR(cdaddr) ((TADDR)(cdaddr)) -#define TO_CDADDR(taddr) ((CLRDATA_ADDRESS)(LONG_PTR)(taddr)) - -const int mdNameLen = 2048; - -#ifdef _MSC_VER -#define PACK_BEGIN __pragma( pack(push, 1) ) -#define PACK_END __pragma( pack(pop) ) -#else -#define PACK_BEGIN -#define PACK_END __attribute__((packed)) -#endif diff --git a/src/DarkId.Papyrus.DebugServer/Protocol/vscodeprotocol.cpp b/src/DarkId.Papyrus.DebugServer/Protocol/vscodeprotocol.cpp deleted file mode 100644 index 137992ab..00000000 --- a/src/DarkId.Papyrus.DebugServer/Protocol/vscodeprotocol.cpp +++ /dev/null @@ -1,728 +0,0 @@ -// Copyright (c) 2018 Samsung Electronics Co., LTD -// Distributed under the MIT License. -// See the LICENSE file in the project root for more information. - -#include "vscodeprotocol.h" - -#include -#include - -#include "torelease.h" -#include "cputil.h" -#include -#include - -#include "PDError.h" -// for convenience -using json = nlohmann::json; -#if SKYRIM -#include -#include -namespace XSE = SKSE; -namespace logger = SKSE::log; -#elif FALLOUT -namespace XSE = F4SE; -namespace logger = F4SE::log; -#endif - -void from_json(const nlohmann::json& value, Source& source) -{ - source.name = value.value("name", std::string()); - source.path = value.value("path", std::string()); - - if (value.find("sourceReference") != value.end()) - { - source.sourceReference = value["sourceReference"]; - } -} - -void to_json(json &j, const Source &s) { - j = json{{"name", s.name}, - {"path", s.path}, - {"sourceReference", s.sourceReference}}; -} - -void to_json(json &j, const Breakpoint &b) { - j = json{ - {"line", b.line}, - {"verified", b.verified}, - {"message", b.message}}; - - if (!b.source.IsNull()) - j["source"] = b.source; -} - -void to_json(json &j, const StackFrame &f) { - j = json{ - {"id", f.id}, - {"name", f.name}, - {"line", f.line}, - {"column", f.column}, - {"endLine", f.endLine}, - {"endColumn", f.endColumn}, - {"moduleId", f.moduleId}}; - if (!f.source.IsNull()) - j["source"] = f.source; -} - -void to_json(json &j, const Thread &t) { - j = json{{"id", t.id}, - {"name", t.name}}; - // {"running", t.running} -} - -void to_json(json &j, const Scope &s) { - j = json{ - {"name", s.name}, - {"variablesReference", s.variablesReference}}; - - if (s.variablesReference > 0) - { - j["namedVariables"] = s.namedVariables; - // j["indexedVariables"] = s.indexedVariables; - } -} - -void to_json(json &j, const Variable &v) { - j = json{ - {"name", v.name}, - {"value", v.value}, - {"type", v.type}, - {"evaluateName", v.evaluateName}, - {"variablesReference", v.variablesReference}}; - - if (v.variablesReference > 0) - { - j["namedVariables"] = v.namedVariables; - // j["indexedVariables"] = v.indexedVariables; - } -} -void VSCodeProtocol::EmitStoppedEvent(StoppedEvent event) -{ - json body; - - switch(event.reason) - { - case StopStep: - body["reason"] = "step"; - break; - case StopBreakpoint: - body["reason"] = "breakpoint"; - break; - case StopException: - body["reason"] = "exception"; - break; - case StopPause: - body["reason"] = "pause"; - break; - case StopEntry: - body["reason"] = "entry"; - break; - } - - body["description"] = event.description; - body["text"] = event.text; - body["threadId"] = event.threadId; - body["allThreadsStopped"] = event.allThreadsStopped; - - // vsdbg shows additional info, but it is not a part of the protocol - // body["line"] = event.frame.line; - // body["column"] = event.frame.column; - - // body["source"] = event.frame.source; - - EmitEvent("stopped", body); -} - -void VSCodeProtocol::EmitContinuedEvent(ContinuedEvent event) -{ - json body; - - body["threadId"] = event.threadId; - body["allThreadsContinued"] = event.allThreadsContinued; - - EmitEvent("continued", body); -} - -void VSCodeProtocol::EmitExitedEvent(ExitedEvent event) -{ - json body; - body["exitCode"] = event.exitCode; - EmitEvent("exited", body); -} - -void VSCodeProtocol::EmitTerminatedEvent() -{ - EmitEvent("terminated", json::object()); -} - -void VSCodeProtocol::EmitThreadEvent(ThreadEvent event) -{ - json body; - - switch(event.reason) - { - case ThreadStarted: - body["reason"] = "started"; - break; - case ThreadExited: - body["reason"] = "exited"; - break; - } - - body["threadId"] = event.threadId; - - EmitEvent("thread", body); -} - -void VSCodeProtocol::EmitModuleEvent(ModuleEvent event) -{ - json body; - - switch(event.reason) - { - case ModuleNew: - body["reason"] = "new"; - break; - case ModuleChanged: - body["reason"] = "changed"; - break; - case ModuleRemoved: - body["reason"] = "removed"; - break; - } - - json &module = body["module"]; - module["id"] = event.module.id; - module["name"] = event.module.name; - module["path"] = event.module.path; - - switch(event.module.symbolStatus) - { - case SymbolsSkipped: - module["symbolStatus"] = "Skipped loading symbols."; - break; - case SymbolsLoaded: - module["symbolStatus"] = "Symbols loaded."; - break; - case SymbolsNotFound: - module["symbolStatus"] = "Symbols not found."; - break; - } - - EmitEvent("module", body); -} - -void VSCodeProtocol::EmitLoadedSourceEvent(LoadedSourceEvent event) -{ - json body; - - switch (event.reason) - { - case ModuleNew: - body["reason"] = "new"; - break; - case ModuleChanged: - body["reason"] = "changed"; - break; - case ModuleRemoved: - body["reason"] = "removed"; - break; - } - - body["source"] = event.source; - - EmitEvent("loadedSource", body); -} - -void VSCodeProtocol::EmitOutputEvent(OutputEvent event) -{ - json body; - - switch(event.category) - { - case OutputConsole: - body["category"] = "console"; - break; - case OutputStdOut: - body["category"] = "stdout"; - break; - case OutputStdErr: - body["category"] = "stderr"; - break; - } - - body["output"] = event.output; - - EmitEvent("output", body); -} - -void VSCodeProtocol::EmitBreakpointEvent(BreakpointEvent event) -{ - json body; - - switch(event.reason) - { - case BreakpointNew: - body["reason"] = "new"; - break; - case BreakpointChanged: - body["reason"] = "changed"; - break; - case BreakpointRemoved: - body["reason"] = "removed"; - break; - } - - body["breakpoint"] = event.breakpoint; - - EmitEvent("breakpoint", body); -} - -void VSCodeProtocol::EmitInitializedEvent() -{ - EmitEvent("initialized", json::object()); -} - -void VSCodeProtocol::EmitCapabilitiesEvent() -{ - json body = json::object(); - json capabilities = json::object(); - - AddCapabilitiesTo(capabilities); - - body["capabilities"] = capabilities; - - EmitEvent("capabilities", body); -} - -void VSCodeProtocol::Cleanup() -{ - -} - -void VSCodeProtocol::EmitEvent(const std::string &name, const nlohmann::json &body) -{ - if (m_exit) - { - return; - } - - std::lock_guard lock(m_outMutex); - json response; - response["seq"] = m_seqCounter++; - response["type"] = "event"; - response["event"] = name; - response["body"] = body; - - std::string output = response.dump(); - - m_sendCallback(output); - - Log(LOG_EVENT, output); -} - -typedef std::function CommandCallback; - -void VSCodeProtocol::AddCapabilitiesTo(json &capabilities) -{ - capabilities["supportsConfigurationDoneRequest"] = true; - capabilities["supportsLoadedSourcesRequest"] = true; - // capabilities["supportsFunctionBreakpoints"] = true; - // capabilities["supportsConditionalBreakpoints"] = true; - // capabilities["supportTerminateDebuggee"] = true; -} - -HRESULT VSCodeProtocol::HandleCommand(const std::string &command, const json &arguments, json &body) -{ - std::unordered_map commands { - { "initialize", [this](const json &arguments, json &body){ - - EmitCapabilitiesEvent(); - - m_debugger->Initialize(); - AddCapabilitiesTo(body); - - return S_OK; - } }, - { "configurationDone", [this](const json &arguments, json &body){ - return m_debugger->ConfigurationDone(); - } }, - { "setBreakpoints", [this](const json &arguments, json &body){ - HRESULT Status; - - std::vector srcBreakpoints; - for (auto &b : arguments.at("breakpoints")) - srcBreakpoints.emplace_back(b.at("line"), b.value("condition", std::string())); - - std::vector breakpoints; - - Source source = arguments.at("source"); - Status = m_debugger->SetBreakpoints(source, srcBreakpoints, breakpoints); - if (FAILED(Status)) { - if (Status == DarkId::Papyrus::DebugServer::PDError::NO_DEBUG_INFO) { - body["breakpoints"] = breakpoints; - body["messasge"] = std::string("Debug info for " + source.name + " not present in PEX data, ensure script is compiled with Debug and that the game is configured to load papyrus debug info"); - return Status; - } - else if (Status == DarkId::Papyrus::DebugServer::PDError::NO_PEX_DATA) { - body["breakpoints"] = breakpoints; - body["messasge"] = std::string("Could not locate PEX data for " + source.name + ", ensure that it is loaded."); - return Status; - } - else { - body["messasge"] = std::string("setBreakpoints failed for " + source.name); - return Status; - } - } - - body["breakpoints"] = breakpoints; - - return S_OK; - } }, - { "launch", [this](const json &arguments, json &body){ - if (!m_fileExec.empty()) - { - return m_debugger->Launch(m_fileExec, m_execArgs, arguments.value("stopAtEntry", false)); - } - std::vector args = arguments.value("args", std::vector()); - args.insert(args.begin(), arguments.at("program").get()); - return m_debugger->Launch("dotnet", args, arguments.value("stopAtEntry", false)); - } }, - { "threads", [this](const json &arguments, json &body){ - HRESULT Status; - std::vector threads; - IfFailRet(m_debugger->GetThreads(threads)); - - body["threads"] = threads; - - return S_OK; - } }, - { "disconnect", [this](const json &arguments, json &body){ - auto terminateArgIter = arguments.find("terminateDebuggee"); - Debugger::DisconnectAction action; - if (terminateArgIter == arguments.end()) - action = Debugger::DisconnectDefault; - else - action = terminateArgIter.value().get() ? Debugger::DisconnectTerminate : Debugger::DisconnectDetach; - - m_debugger->Disconnect(action); - - m_exit = true; - return S_OK; - } }, - { "stackTrace", [this](const json &arguments, json &body){ - HRESULT Status; - - int totalFrames = 0; - int threadId = arguments.at("threadId"); - - std::vector stackFrames; - IfFailRet(m_debugger->GetStackTrace( - threadId, - arguments.value("startFrame", 0), - arguments.value("levels", 0), - stackFrames, - totalFrames - )); - - body["stackFrames"] = stackFrames; - body["totalFrames"] = totalFrames; - - return S_OK; - } }, - { "continue", [this](const json &arguments, json &body){ - return m_debugger->Continue(); - } }, - { "pause", [this](const json &arguments, json &body){ - return m_debugger->Pause(); - } }, - { "next", [this](const json &arguments, json &body){ - return m_debugger->StepCommand(arguments.at("threadId"), Debugger::STEP_OVER); - } }, - { "stepIn", [this](const json &arguments, json &body){ - return m_debugger->StepCommand(arguments.at("threadId"), Debugger::STEP_IN); - } }, - { "stepOut", [this](const json &arguments, json &body){ - return m_debugger->StepCommand(arguments.at("threadId"), Debugger::STEP_OUT); - } }, - { "scopes", [this](const json &arguments, json &body){ - HRESULT Status; - std::vector scopes; - IfFailRet(m_debugger->GetScopes(arguments.at("frameId"), scopes)); - - body["scopes"] = scopes; - - return S_OK; - } }, - { "variables", [this](const json &arguments, json &body){ - HRESULT Status; - - std::string filterName = arguments.value("filter", ""); - VariablesFilter filter = VariablesBoth; - if (filterName == "named") - filter = VariablesNamed; - else if (filterName == "indexed") - filter = VariablesIndexed; - - std::vector variables; - IfFailRet(m_debugger->GetVariables( - arguments.at("variablesReference"), - filter, - arguments.value("start", 0), - arguments.value("count", 0), - variables)); - - body["variables"] = variables; - - return S_OK; - } }, - { "evaluate", [this](const json &arguments, json &body){ - HRESULT Status; - std::string expression = arguments.at("expression"); - uint64_t frameId; - auto frameIdIter = arguments.find("frameId"); - if (frameIdIter == arguments.end()) - { - // int threadId = m_debugger->GetLastStoppedThreadId(); - frameId = -1; - } - else - frameId = frameIdIter.value(); - - Variable variable; - std::string output; - Status = m_debugger->Evaluate(frameId, expression, variable, output); - if (FAILED(Status)) - { - body["message"] = output; - return Status; - } - - body["result"] = variable.value; - body["type"] = variable.type; - body["variablesReference"] = variable.variablesReference; - if (variable.variablesReference > 0) - { - body["namedVariables"] = variable.namedVariables; - // indexedVariables - } - return S_OK; - } }, - { "attach", [this](const json &arguments, json &body){ - return m_debugger->Attach(); - } }, - { "setVariable", [this](const json &arguments, json &body) { - HRESULT Status; - - std::string name = arguments.at("name"); - std::string value = arguments.at("value"); - int ref = arguments.at("variablesReference"); - - std::string output; - Status = m_debugger->SetVariable(name, value, ref, output); - if (FAILED(Status)) - { - body["message"] = output; - return Status; - } - - body["value"] = output; - - return S_OK; - } }, - { "source", [this](const json & arguments, json & body) { - HRESULT Status; - - Source source( - arguments["source"]["name"], - arguments["source"]["path"], - arguments["source"]["sourceReference"]); - - std::string content; - Status = m_debugger->GetSource(source, content); - - if (FAILED(Status)) - { - body["message"] = content; - return Status; - } - - body["content"] = content; - - return S_OK; - } }, - { "loadedSources", [this](const json & arguments, json & body) { - HRESULT Status; - - std::vector sources; - Status = m_debugger->GetLoadedSources(sources); - - if (FAILED(Status)) - { - body["message"] = "Failed to get loaded sources."; - return Status; - } - - body["sources"] = sources; - - return S_OK; -} }, - { "setFunctionBreakpoints", [this](const json &arguments, json &body) { - HRESULT Status = S_OK; - - std::vector funcBreakpoints; - for (auto &b : arguments.at("breakpoints")) - { - std::string module(""); - std::string params(""); - std::string name = b.at("name"); - - std::size_t i = name.find('!'); - - if (i != std::string::npos) - { - module = std::string(name, 0, i); - name.erase(0, i + 1); - } - - i = name.find('('); - if (i != std::string::npos) - { - std::size_t closeBrace = name.find(')'); - - params = std::string(name, i, closeBrace - i + 1); - name.erase(i, closeBrace); - } - - funcBreakpoints.emplace_back(module, name, params, b.value("condition", std::string())); - } - - std::vector breakpoints; - IfFailRet(m_debugger->SetFunctionBreakpoints(funcBreakpoints, breakpoints)); - - body["breakpoints"] = breakpoints; - - return Status; - } } - }; - - auto command_it = commands.find(command); - if (command_it == commands.end()) - { - return E_NOTIMPL; - } - - return command_it->second(arguments, body); -} - -void VSCodeProtocol::Exit() -{ - m_exit = true; -} - -void VSCodeProtocol::Receive(std::string message) -{ - std::lock_guard lock(m_inMutex); - m_inputQueue->push(message); -} - -void VSCodeProtocol::CommandLoop() -{ - while (!m_exit) - { - std::string requestText; - - while (!m_exit) - { - { - std::lock_guard lock(m_inMutex); - - if (!m_inputQueue->empty()) - { - requestText = m_inputQueue->front(); - m_inputQueue->pop(); - break; - } - } - - Sleep(100); - } - - if (requestText.empty()) - break; - - { - std::lock_guard lock(m_outMutex); - Log(LOG_COMMAND, requestText); - } - - json request = json::parse(requestText, NULL, false); - - if (request.find("command") == request.end()) - { - continue; - } - - std::string command = request.at("command"); - // assert(request["type"] == "request"); - - auto argIter = request.find("arguments"); - json arguments = (argIter == request.end() ? json::object() : argIter.value()); - - json body = json::object(); - HRESULT Status = HandleCommand(command, arguments, body); - - { - std::lock_guard lock(m_outMutex); - - json response; - response["seq"] = m_seqCounter++; - response["type"] = "response"; - response["command"] = command; - response["request_seq"] = request.at("seq"); - - if (SUCCEEDED(Status)) - { - response["success"] = true; - response["body"] = body; - } - else - { - if (body.find("message") == body.end()) - { - std::ostringstream ss; - ss << "Failed command '" << command << "' : " - << "0x" << std::setw(8) << std::setfill('0') << std::hex << Status; - response["message"] = ss.str(); - } - else - response["message"] = body["message"]; - - response["success"] = false; - } - std::string output = response.dump(); - - if (!m_exit) - { - m_sendCallback(output); - } - } - } - - if (!m_exit) - m_debugger->Disconnect(); - -} - -const std::string VSCodeProtocol::LOG_COMMAND("-> (C) "); -const std::string VSCodeProtocol::LOG_RESPONSE("<- (R) "); -const std::string VSCodeProtocol::LOG_EVENT("<- (E) "); - -void VSCodeProtocol::Log(const std::string &prefix, const std::string &text) -{ - using namespace std::literals; - logger::info("{}: {}"sv, prefix.c_str(), text.c_str()); -} diff --git a/src/DarkId.Papyrus.DebugServer/Protocol/vscodeprotocol.h b/src/DarkId.Papyrus.DebugServer/Protocol/vscodeprotocol.h deleted file mode 100644 index ec0c26da..00000000 --- a/src/DarkId.Papyrus.DebugServer/Protocol/vscodeprotocol.h +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2018 Samsung Electronics Co., LTD -// Distributed under the MIT License. -// See the LICENSE file in the project root for more information. -#pragma once - -#include -#include -#include - -#include "nlohmann/json.hpp" -#include "debugger.h" -#include - - -class VSCodeProtocol : public Protocol -{ - static const std::string LOG_COMMAND; - static const std::string LOG_RESPONSE; - static const std::string LOG_EVENT; - - std::mutex m_outMutex; - std::mutex m_inMutex; - - std::function m_sendCallback; - - std::queue* m_inputQueue; - - uint64_t m_seqCounter; - - std::string m_fileExec; - std::vector m_execArgs; - - void AddCapabilitiesTo(nlohmann::json &capabilities); - void EmitEvent(const std::string &name, const nlohmann::json &body); - HRESULT HandleCommand(const std::string &command, const nlohmann::json &arguments, nlohmann::json &body); - - void Log(const std::string &prefix, const std::string &text); - -public: - - VSCodeProtocol(std::function sendCallback) : Protocol(), m_seqCounter(1), m_sendCallback(sendCallback) { - m_inputQueue = new std::queue(); - } - - void OverrideLaunchCommand(const std::string &fileExec, const std::vector &args) - { - m_fileExec = fileExec; - m_execArgs = args; - } - - void Exit(); - - void Receive(std::string message); - - void EmitInitializedEvent() override; - void EmitStoppedEvent(StoppedEvent event) override; - void EmitExitedEvent(ExitedEvent event) override; - void EmitTerminatedEvent() override; - void EmitContinuedEvent(ContinuedEvent event) override; - void EmitThreadEvent(ThreadEvent event) override; - void EmitModuleEvent(ModuleEvent event) override; - void EmitLoadedSourceEvent(LoadedSourceEvent event) override; - void EmitOutputEvent(OutputEvent event) override; - void EmitBreakpointEvent(BreakpointEvent event) override; - void Cleanup() override; - void CommandLoop() override; - - void EmitCapabilitiesEvent(); -}; diff --git a/src/DarkId.Papyrus.DebugServer/Websocket.h b/src/DarkId.Papyrus.DebugServer/Protocol/websocket_impl.h similarity index 80% rename from src/DarkId.Papyrus.DebugServer/Websocket.h rename to src/DarkId.Papyrus.DebugServer/Protocol/websocket_impl.h index cdc5b01c..42f33c0a 100644 --- a/src/DarkId.Papyrus.DebugServer/Websocket.h +++ b/src/DarkId.Papyrus.DebugServer/Protocol/websocket_impl.h @@ -2,10 +2,11 @@ #include #include - +#include #include typedef websocketpp::server server; +typedef websocketpp::connection connection; using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_2; diff --git a/src/DarkId.Papyrus.DebugServer/Protocol/websocket_reader_writer.cpp b/src/DarkId.Papyrus.DebugServer/Protocol/websocket_reader_writer.cpp new file mode 100644 index 00000000..8ac478fb --- /dev/null +++ b/src/DarkId.Papyrus.DebugServer/Protocol/websocket_reader_writer.cpp @@ -0,0 +1,70 @@ +#include "pdsPCH.h" +#include "websocket_reader_writer.h" + +void dap::WebsocketReaderWriter::HandleMessage(websocketpp::connection_hdl hdl, message_ptr msg) { + std::unique_lock lock(readMutex); + std::string str = "Content-Length: " + std::to_string(msg->get_payload().length()) + "\r\n\r\n" + msg->get_payload(); + for (auto c : str) { + buf.push_back(c); + } + ready = true; + cv.notify_one(); +} + +dap::WebsocketReaderWriter::WebsocketReaderWriter(const std::shared_ptr& p_con): ReaderWriter() { + con = p_con; + con->set_message_handler(bind(&WebsocketReaderWriter::HandleMessage, this, ::_1, ::_2)); +} + +size_t dap::WebsocketReaderWriter::read(void* buffer, size_t n) { + std::unique_lock lock(readMutex); + if (n == 0 || !isOpen()) { + return 0; + } + size_t bytes_read = 0; + auto out = reinterpret_cast(buffer); + + while (isOpen() && buf.size() == 0) { + cv.wait(lock, [&] {return ready; }); + } + // might have closed while waiting + if (!isOpen()) { + return bytes_read; + } + for (; bytes_read < n && buf.size() > 0; bytes_read++) { + out[bytes_read] = buf.front(); + buf.pop_front(); + } + if (buf.size() == 0) + { + ready = false; + } + return bytes_read; +} + +bool dap::WebsocketReaderWriter::write(const void* buffer, size_t n) { + std::unique_lock lock(writeMutex); + if (!isOpen()) { + return false; + } + std::string str((char*)buffer, n); + // Header, disregard + if (str.find("Content-Length") == 0) { + return true; + } + return con->send(str) == websocketpp::lib::error_code(); +} + +bool dap::WebsocketReaderWriter::isOpen() { + return con->get_state() == websocketpp::session::state::value::open; +} + +void dap::WebsocketReaderWriter::close() { + std::unique_lock lock(readMutex); + std::unique_lock lock2(writeMutex); + if (isOpen()) { + con->close(websocketpp::close::status::normal, "Closed by debugger"); + } + ready = true; + cv.notify_all(); +} diff --git a/src/DarkId.Papyrus.DebugServer/Protocol/websocket_reader_writer.h b/src/DarkId.Papyrus.DebugServer/Protocol/websocket_reader_writer.h new file mode 100644 index 00000000..c008f28a --- /dev/null +++ b/src/DarkId.Papyrus.DebugServer/Protocol/websocket_reader_writer.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include "websocket_impl.h" + +namespace dap { + +class WebsocketReaderWriter : public ReaderWriter { + + public: + WebsocketReaderWriter(const std::shared_ptr& p_con); + + virtual size_t read(void* buffer, size_t n) override; + + virtual bool write(const void* buffer, size_t n) override; + + virtual bool isOpen() override; + + virtual void close() override; + private: + void HandleMessage(websocketpp::connection_hdl hdl, message_ptr msg); + + std::shared_ptr con; + std::deque buf; + bool ready = false; + std::mutex readMutex; + std::condition_variable cv; + std::mutex writeMutex; + std::atomic closed = {false}; + +}; +} diff --git a/src/DarkId.Papyrus.DebugServer/Protocol/websocket_server.cpp b/src/DarkId.Papyrus.DebugServer/Protocol/websocket_server.cpp new file mode 100644 index 00000000..2ba6f9e3 --- /dev/null +++ b/src/DarkId.Papyrus.DebugServer/Protocol/websocket_server.cpp @@ -0,0 +1,97 @@ +#include "websocket_server.h" +#include + + +namespace dap +{ + namespace net + { + bool WebsocketServer::start(int p_port, + const OnConnect& onConnect, + const OnError& onError) + { + port = port; + std::unique_lock lock(mutex); + stopWithLock(); + connectCallback = onConnect; + errorHandler = onError; + + stopped = false; + thread = std::thread(bind(&WebsocketServer::ListenInternal, this)); + return true; + } + + void WebsocketServer::HandleOpen(websocketpp::connection_hdl hdl) + { + std::shared_ptr con = m_server.get_con_from_hdl(hdl); + if (con != m_server.get_con_from_hdl(m_connectionHandle)) + { + m_server.close(m_connectionHandle, websocketpp::close::status::normal, "Connection closed by new session."); + if (rw) { + rw->close(); + } + } + m_connectionHandle = hdl; + rw = std::unique_ptr( new dap::WebsocketReaderWriter(con) ); + connectCallback(std::move(rw)); + } + + void WebsocketServer::HandleClose(websocketpp::connection_hdl hdl) + { + if (m_server.get_con_from_hdl(hdl) != m_server.get_con_from_hdl(m_connectionHandle)) + { + return; + } + if (rw) { + rw->close(); + rw = nullptr; + } + } + + uint32_t WebsocketServer::ListenInternal() + { + try + { + m_server.init_asio(); + + m_server.set_open_handler(bind(&WebsocketServer::HandleOpen, this, ::_1)); + m_server.set_close_handler(bind(&WebsocketServer::HandleClose, this, ::_1)); + m_server.set_max_message_size(1024 * 1024 * 10); + m_server.set_max_http_body_size(1024 * 1024 * 10); + + m_server.listen(port); + // Start the server accept loop + m_server.start_accept(); + + // Start the ASIO io_service run loop + m_server.run(); + } + catch (websocketpp::exception const & e) + { + errorHandler(e.what()); + return e.code().value(); + } + catch (...) + { + errorHandler("other_exception"); + return -1; + } + + return 0; + } + + void WebsocketServer::stop() + { + std::unique_lock lock(mutex); + stopWithLock(); + } + void WebsocketServer::stopWithLock() { + if (!stopped.exchange(true)) { + // this will trigger HandleClose for us + m_server.close(m_connectionHandle, websocketpp::close::status::normal, "Connection closed by debugger."); + m_server.stop(); + thread.join(); + } + } + } +} \ No newline at end of file diff --git a/src/DarkId.Papyrus.DebugServer/Protocol/websocket_server.h b/src/DarkId.Papyrus.DebugServer/Protocol/websocket_server.h new file mode 100644 index 00000000..8fe6834f --- /dev/null +++ b/src/DarkId.Papyrus.DebugServer/Protocol/websocket_server.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include "websocket_impl.h" +#include "websocket_reader_writer.h" + +namespace dap { + +namespace net { + class WebsocketServer: + public Server + { + // ignoreErrors() matches the OnError signature, and does nothing. + static inline void ignoreErrors(const char*) {} + uint32_t ListenInternal(); + void stopWithLock(); + void HandleOpen(websocketpp::connection_hdl hdl); + void HandleClose(websocketpp::connection_hdl hdl); + + int port = 0; + std::mutex mutex; + std::thread thread; + std::atomic stopped; + std::unique_ptr rw; + OnConnect connectCallback; + OnError errorHandler; + server m_server; + websocketpp::connection_hdl m_connectionHandle; +public: + using OnError = std::function; + using OnConnect = std::function&)>; + + WebsocketServer(){}; + virtual ~WebsocketServer() { stop(); }; + + // start() begins listening for connections on the given port. + // callback will be called for each connection. + // onError will be called for any connection errors. + virtual bool start(int p_port, + const OnConnect& callback, + const OnError& onError = ignoreErrors) override; + + // stop() stops listening for connections. + // stop() is implicitly called on destruction. + virtual void stop() override; +}; + +} +} \ No newline at end of file diff --git a/src/DarkId.Papyrus.DebugServer/README.md b/src/DarkId.Papyrus.DebugServer/README.md index 6177e7e0..668d2804 100644 --- a/src/DarkId.Papyrus.DebugServer/README.md +++ b/src/DarkId.Papyrus.DebugServer/README.md @@ -8,8 +8,6 @@ File any issues here: https://github.com/joelday/papyrus-lang/issues ### Building from Source -NOTE: Fallout 4 doesn't build on this branch currently, only attempt building Skyrim for right now. - Build Requirements: - [Visual Studio 2022](https://visualstudio.microsoft.com/vs/community/) diff --git a/src/DarkId.Papyrus.DebugServer/StackFrameStateNode.cpp b/src/DarkId.Papyrus.DebugServer/StackFrameStateNode.cpp index f5435b9f..d1718ece 100644 --- a/src/DarkId.Papyrus.DebugServer/StackFrameStateNode.cpp +++ b/src/DarkId.Papyrus.DebugServer/StackFrameStateNode.cpp @@ -12,11 +12,11 @@ namespace DarkId::Papyrus::DebugServer } - bool StackFrameStateNode::SerializeToProtocol(StackFrame& stackFrame, PexCache* pexCache) const + bool StackFrameStateNode::SerializeToProtocol(dap::StackFrame& stackFrame, PexCache* pexCache) const { - stackFrame = StackFrame(GetId()); + stackFrame = dap::StackFrame(GetId()); - Source source; + dap::Source source; std::string ScriptName = NormalizeScriptName(m_stackFrame->owningObjectType->GetName()); // TODO: ignoring this for now, just for debugging reference std::string srcFileName = m_stackFrame->owningFunction->GetSourceFilename().c_str(); diff --git a/src/DarkId.Papyrus.DebugServer/StackFrameStateNode.h b/src/DarkId.Papyrus.DebugServer/StackFrameStateNode.h index f29cb9cb..5470a2b1 100644 --- a/src/DarkId.Papyrus.DebugServer/StackFrameStateNode.h +++ b/src/DarkId.Papyrus.DebugServer/StackFrameStateNode.h @@ -2,7 +2,7 @@ #include "GameInterfaces.h" -#include "Protocol/protocol.h" +#include #include "PexCache.h" #include "StateNodeBase.h" @@ -15,7 +15,7 @@ namespace DarkId::Papyrus::DebugServer public: explicit StackFrameStateNode(RE::BSScript::StackFrame* stackFrame); - bool SerializeToProtocol(StackFrame& stackFrame, PexCache* pexCache) const; + bool SerializeToProtocol(dap::StackFrame& stackFrame, PexCache* pexCache) const; bool GetChildNames(std::vector& names) override; bool GetChildNode(std::string name, std::shared_ptr& node) override; diff --git a/src/DarkId.Papyrus.DebugServer/StackStateNode.cpp b/src/DarkId.Papyrus.DebugServer/StackStateNode.cpp index 36cc831a..7736c151 100644 --- a/src/DarkId.Papyrus.DebugServer/StackStateNode.cpp +++ b/src/DarkId.Papyrus.DebugServer/StackStateNode.cpp @@ -12,7 +12,7 @@ namespace DarkId::Papyrus::DebugServer { } - bool StackStateNode::SerializeToProtocol(Thread& thread) const + bool StackStateNode::SerializeToProtocol(dap::Thread& thread) const { thread.id = m_stackId; @@ -30,9 +30,6 @@ namespace DarkId::Papyrus::DebugServer thread.name = StringFormat("%s (%d)", name, thread.id); } - // TODO: This isn't even in the DAP spec. - thread.running = true; // m_state != DebuggerState::kState_Paused; - return true; } diff --git a/src/DarkId.Papyrus.DebugServer/StackStateNode.h b/src/DarkId.Papyrus.DebugServer/StackStateNode.h index a621fb22..0fcd6837 100644 --- a/src/DarkId.Papyrus.DebugServer/StackStateNode.h +++ b/src/DarkId.Papyrus.DebugServer/StackStateNode.h @@ -2,7 +2,7 @@ #include "GameInterfaces.h" -#include "Protocol/protocol.h" +#include #include "StateNodeBase.h" namespace DarkId::Papyrus::DebugServer @@ -14,7 +14,7 @@ namespace DarkId::Papyrus::DebugServer public: StackStateNode(uint32_t stackId); - bool SerializeToProtocol(Thread& thread) const; + bool SerializeToProtocol(dap::Thread& thread) const; bool GetChildNames(std::vector& names) override; bool GetChildNode(std::string name, std::shared_ptr& node) override; diff --git a/src/DarkId.Papyrus.DebugServer/StateNodeBase.h b/src/DarkId.Papyrus.DebugServer/StateNodeBase.h index 36a4a00b..e1b5356d 100644 --- a/src/DarkId.Papyrus.DebugServer/StateNodeBase.h +++ b/src/DarkId.Papyrus.DebugServer/StateNodeBase.h @@ -2,7 +2,7 @@ #include "GameInterfaces.h" -#include "Protocol/protocol.h" +#include namespace DarkId::Papyrus::DebugServer { @@ -22,13 +22,13 @@ namespace DarkId::Papyrus::DebugServer class IProtocolVariableSerializable { public: - virtual bool SerializeToProtocol(Variable& variable) = 0; + virtual bool SerializeToProtocol(dap::Variable& variable) = 0; }; class IProtocolScopeSerializable { public: - virtual bool SerializeToProtocol(Scope& scope) = 0; + virtual bool SerializeToProtocol(dap::Scope& scope) = 0; }; class IStructuredState diff --git a/src/DarkId.Papyrus.DebugServer/StructStateNode.cpp b/src/DarkId.Papyrus.DebugServer/StructStateNode.cpp index 2ea93f47..25fe3471 100644 --- a/src/DarkId.Papyrus.DebugServer/StructStateNode.cpp +++ b/src/DarkId.Papyrus.DebugServer/StructStateNode.cpp @@ -10,14 +10,14 @@ namespace DarkId::Papyrus::DebugServer m_type = RE::BSTSmartPointer(value ? value->type.get() : knownType); } - bool StructStateNode::SerializeToProtocol(Variable& variable) + bool StructStateNode::SerializeToProtocol(dap::Variable& variable) { variable.variablesReference = m_value ? GetId() : 0; variable.namedVariables = m_value ? m_type->variables.size() : 0; variable.name = m_name; variable.type = m_type->GetName(); - variable.value = m_value ? variable.type : "None"; + variable.value = m_value ? m_type->GetName() : "NONE"; return true; } diff --git a/src/DarkId.Papyrus.DebugServer/StructStateNode.h b/src/DarkId.Papyrus.DebugServer/StructStateNode.h index a0fb07db..72b1e3d1 100644 --- a/src/DarkId.Papyrus.DebugServer/StructStateNode.h +++ b/src/DarkId.Papyrus.DebugServer/StructStateNode.h @@ -2,7 +2,7 @@ #if FALLOUT #include "GameInterfaces.h" -#include "Protocol/protocol.h" +#include #include "StateNodeBase.h" namespace DarkId::Papyrus::DebugServer @@ -16,7 +16,7 @@ namespace DarkId::Papyrus::DebugServer public: StructStateNode(std::string name, RE::BSScript::Struct* value, RE::BSScript::StructTypeInfo* knownType); - bool SerializeToProtocol(Variable& variable) override; + bool SerializeToProtocol(dap::Variable& variable) override; bool GetChildNames(std::vector& names) override; bool GetChildNode(std::string name, std::shared_ptr& node) override; diff --git a/src/DarkId.Papyrus.DebugServer/ValueStateNode.cpp b/src/DarkId.Papyrus.DebugServer/ValueStateNode.cpp index 56f777a1..6835934d 100644 --- a/src/DarkId.Papyrus.DebugServer/ValueStateNode.cpp +++ b/src/DarkId.Papyrus.DebugServer/ValueStateNode.cpp @@ -8,7 +8,7 @@ namespace DarkId::Papyrus::DebugServer { } - bool ValueStateNode::SerializeToProtocol(Variable& variable) + bool ValueStateNode::SerializeToProtocol(dap::Variable& variable) { variable.name = m_name; #if SKYRIM diff --git a/src/DarkId.Papyrus.DebugServer/ValueStateNode.h b/src/DarkId.Papyrus.DebugServer/ValueStateNode.h index 2a69177c..46eac10b 100644 --- a/src/DarkId.Papyrus.DebugServer/ValueStateNode.h +++ b/src/DarkId.Papyrus.DebugServer/ValueStateNode.h @@ -2,7 +2,7 @@ #include "GameInterfaces.h" -#include "Protocol/protocol.h" +#include #include "StateNodeBase.h" namespace DarkId::Papyrus::DebugServer @@ -14,6 +14,6 @@ namespace DarkId::Papyrus::DebugServer public: ValueStateNode(std::string name, const RE::BSScript::Variable* variable); - bool SerializeToProtocol(Variable& variable) override; + bool SerializeToProtocol(dap::Variable& variable) override; }; } diff --git a/vcpkg.json b/vcpkg.json index bca0d479..0f2620f3 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -12,7 +12,8 @@ "champollion", "eventpp", "websocketpp", - "xbyak" + "xbyak", + "cppdap" ], "features": { "skyrim": { @@ -31,7 +32,7 @@ "overrides": [ { "name": "nlohmann-json", - "version": "3.7.0" + "version": "3.11.2" }, { "name": "eventpp",