Skip to content

Commit 57f5f38

Browse files
committed
Add MCPinstaller and improve DMA device handling
MCPinstaller: - Standalone GUI tool for configuring MCP clients - Auto-detects Claude Desktop, Cursor, VS Code, and 8+ other clients - Embedded mcp_bridge.js (no separate file needed) - Extracts bridge to AppData/Orpheus on install - Admin manifest for writing to various config directories DMA improvements: - Display actual device name (Enigma X1, ScreamerM2, etc.) in status bar - Query FPGA device ID via VMMDLL_ConfigGet - Fallback to generic "fpga" if device unknown Other fixes: - Fix F11 fullscreen toggle not working after first use - Reset ImGui layout on fullscreen transitions for smoother experience - Move runtime DLLs to AppData for persistent storage - Update release workflow to include MCPinstaller.exe
1 parent 26acf48 commit 57f5f38

14 files changed

Lines changed: 1011 additions & 80 deletions

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ jobs:
3030
run: |
3131
mkdir release
3232
copy build\bin\Release\orpheus.exe release\
33+
copy build\bin\Release\MCPinstaller.exe release\
3334
copy LICENSE release\
3435
copy README.md release\
3536

CMakeLists.txt

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@ include(Dependencies)
5252
# Main Executable
5353
# ============================================================================
5454

55-
# Collect source files
55+
# Collect source files (exclude installer directory)
5656
file(GLOB_RECURSE ORPHEUS_SOURCES
5757
"src/*.cpp"
5858
"src/**/*.cpp"
5959
)
60+
# Remove installer sources from main executable
61+
list(FILTER ORPHEUS_SOURCES EXCLUDE REGEX ".*installer.*")
6062

6163
# Windows resource file (for .exe icon)
6264
if(WIN32)
@@ -132,11 +134,67 @@ elseif(PLATFORM_LINUX)
132134
)
133135
endif()
134136

137+
# ============================================================================
138+
# MCPinstaller - Standalone MCP Bridge Installer
139+
# ============================================================================
140+
141+
set(MCPINSTALLER_SOURCES
142+
"src/installer/installer_main.cpp"
143+
"src/installer/installer_app.cpp"
144+
)
145+
146+
# Windows resource file (for .exe icon)
147+
if(WIN32)
148+
set(MCPINSTALLER_RESOURCES "${CMAKE_SOURCE_DIR}/resources/orpheus.rc")
149+
endif()
150+
151+
add_executable(MCPinstaller ${MCPINSTALLER_SOURCES} ${MCPINSTALLER_RESOURCES})
152+
153+
target_include_directories(MCPinstaller PRIVATE
154+
${CMAKE_SOURCE_DIR}/src
155+
${CMAKE_SOURCE_DIR}/include
156+
${EMBEDDED_RESOURCE_INCLUDE_DIR}
157+
${JSON_INCLUDE_DIR}
158+
${STB_INCLUDE_DIR}
159+
)
160+
161+
target_link_libraries(MCPinstaller PRIVATE
162+
imgui
163+
glfw
164+
)
165+
166+
if(PLATFORM_WINDOWS)
167+
target_link_libraries(MCPinstaller PRIVATE
168+
opengl32
169+
)
170+
171+
set_target_properties(MCPinstaller PROPERTIES
172+
WIN32_EXECUTABLE TRUE
173+
)
174+
175+
if(MSVC)
176+
target_compile_options(MCPinstaller PRIVATE
177+
/W4 /MP /utf-8
178+
)
179+
target_link_options(MCPinstaller PRIVATE
180+
/ENTRY:mainCRTStartup
181+
/MANIFESTUAC:level='requireAdministrator'
182+
)
183+
target_compile_definitions(MCPinstaller PRIVATE
184+
_CRT_SECURE_NO_WARNINGS
185+
)
186+
endif()
187+
elseif(PLATFORM_LINUX)
188+
target_link_libraries(MCPinstaller PRIVATE
189+
dl pthread GL
190+
)
191+
endif()
192+
135193
# ============================================================================
136194
# Installation
137195
# ============================================================================
138196

139-
install(TARGETS orpheus
197+
install(TARGETS orpheus MCPinstaller
140198
RUNTIME DESTINATION bin
141199
)
142200

README.md

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<p align="center">
2-
<img src="resources/images/banner.png" alt="Orpheus" width="600">
2+
<img src="resources/images/banner.png" alt="Orpheus" width="400">
33
</p>
44

55
<p align="center">
@@ -31,7 +31,9 @@ cmake ..
3131
cmake --build . --config Release
3232
```
3333

34-
Output: `build/bin/Release/orpheus.exe`
34+
Output:
35+
- `build/bin/Release/orpheus.exe` - Main application
36+
- `build/bin/Release/MCPinstaller.exe` - MCP client configuration tool (includes embedded bridge)
3537

3638
## Usage
3739

@@ -45,24 +47,42 @@ orpheus.exe --connect
4547

4648
### MCP Integration
4749

50+
#### Quick Setup (Recommended)
51+
52+
Use **MCPinstaller** to automatically configure all your MCP clients:
53+
4854
1. Start Orpheus and enable MCP server in settings
49-
2. Configure Claude Code to use the MCP bridge:
55+
2. Copy the API key from Orpheus (Settings > MCP > Copy API Key)
56+
3. Run `MCPinstaller.exe` (included in releases)
57+
4. Enter your Orpheus server URL and API key
58+
5. Select which clients to configure (Claude Desktop, Cursor, VS Code, etc.)
59+
6. Click Install - restart your MCP clients to apply
60+
61+
MCPinstaller auto-detects and configures: Claude Desktop, Cursor, Claude Code, Windsurf, VS Code, Cline, Roo Code, LM Studio, Zed, Amazon Q, Warp, and more.
62+
63+
#### Manual Configuration
64+
65+
Alternatively, manually add to your MCP client config (e.g., `~/.claude/claude_desktop_config.json`):
5066

5167
```json
5268
{
5369
"mcpServers": {
5470
"orpheus": {
5571
"command": "node",
56-
"args": ["path/to/mcp_bridge.js"],
72+
"args": ["C:/path/to/mcp_bridge.js"],
5773
"env": {
58-
"ORPHEUS_MCP_URL": "http://localhost:8765",
59-
"ORPHEUS_API_KEY": "your-api-key"
74+
"ORPHEUS_MCP_URL": "http://192.168.1.100:8765",
75+
"ORPHEUS_API_KEY": "oph_your_key_here"
6076
}
6177
}
6278
}
6379
}
6480
```
6581

82+
**Notes:**
83+
- `ORPHEUS_MCP_URL`: Use `localhost` if on the same machine, otherwise use the Orpheus server IP
84+
- `ORPHEUS_API_KEY`: Required - copy from Orpheus GUI
85+
6686
## Project Structure
6787

6888
```
@@ -73,6 +93,7 @@ orpheus/
7393
│ ├── emulation/ # Unicorn CPU emulator
7494
│ ├── dumper/ # Game-specific SDK dumpers
7595
│ ├── mcp/ # MCP server implementation
96+
│ ├── installer/ # MCPinstaller standalone tool
7697
│ ├── ui/ # ImGui application
7798
│ └── utils/ # Logging, bookmarks
7899
├── cmake/ # CMake modules (dependencies, resource embedding)

cmake/EmbedResources.cmake

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,19 @@ function(embed_all_dlls)
107107
message(STATUS "Icon not found (optional): ${ICON_FILE}")
108108
endif()
109109

110+
# Process MCP bridge script
111+
set(MCP_BRIDGE_FILE "${CMAKE_SOURCE_DIR}/mcp_bridge.js")
112+
set(HAS_MCP_BRIDGE FALSE)
113+
if(EXISTS "${MCP_BRIDGE_FILE}")
114+
set(output_file "${OUTPUT_DIR}/embedded_mcp_bridge.h")
115+
embed_resource("${MCP_BRIDGE_FILE}" "${output_file}")
116+
list(APPEND EMBEDDED_HEADERS "${output_file}")
117+
set(HAS_MCP_BRIDGE TRUE)
118+
message(STATUS "Embedded MCP bridge: mcp_bridge.js")
119+
else()
120+
message(WARNING "MCP bridge not found: ${MCP_BRIDGE_FILE}")
121+
endif()
122+
110123
# Generate a master header that includes all embedded resources
111124
set(MASTER_HEADER "${OUTPUT_DIR}/embedded_resources.h")
112125
file(WRITE "${MASTER_HEADER}"
@@ -155,13 +168,29 @@ function(embed_all_dlls)
155168
if(HAS_ICON)
156169
file(APPEND "${MASTER_HEADER}"
157170
"inline constexpr bool has_icon = true;\n"
158-
"// orpheus_png and orpheus_png_size defined in embedded_orpheus_icon.h\n"
171+
"// orpheus_png and orpheus_png_size defined in embedded_orpheus_icon.h\n\n"
159172
)
160173
else()
161174
file(APPEND "${MASTER_HEADER}"
162175
"inline constexpr bool has_icon = false;\n"
163176
"inline constexpr unsigned char orpheus_png[] = {0}; // Placeholder byte\n"
164-
"inline constexpr size_t orpheus_png_size = 0;\n"
177+
"inline constexpr size_t orpheus_png_size = 0;\n\n"
178+
)
179+
endif()
180+
181+
# MCP Bridge resource
182+
if(HAS_MCP_BRIDGE)
183+
file(APPEND "${MASTER_HEADER}"
184+
"// MCP Bridge script\n"
185+
"inline constexpr bool has_mcp_bridge = true;\n"
186+
"// mcp_bridge_js and mcp_bridge_js_size defined in embedded_mcp_bridge.h\n"
187+
)
188+
else()
189+
file(APPEND "${MASTER_HEADER}"
190+
"// MCP Bridge script (not found)\n"
191+
"inline constexpr bool has_mcp_bridge = false;\n"
192+
"inline constexpr unsigned char mcp_bridge_js[] = {0};\n"
193+
"inline constexpr size_t mcp_bridge_js_size = 0;\n"
165194
)
166195
endif()
167196

mcp_bridge.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@ const { URL } = require('url');
1313
// Configuration
1414
// ============================================================================
1515

16-
const MCP_URL = process.env.ORPHEUS_MCP_URL || 'http://10.0.0.120:8765';
17-
const API_KEY = process.env.ORPHEUS_API_KEY || 'oph_cb3fe9a44d96ce8f4f87bc745dbbd50451c0c47b43c9c33ebb53d092d0e7c5c4';
16+
const MCP_URL = process.env.ORPHEUS_MCP_URL || 'http://localhost:8765';
17+
const API_KEY = process.env.ORPHEUS_API_KEY;
1818
const DEBUG = process.env.DEBUG === 'true';
1919

20+
if (!API_KEY) {
21+
console.error('Error: ORPHEUS_API_KEY environment variable is required');
22+
console.error('Set it in your MCP server configuration or export it before running');
23+
process.exit(1);
24+
}
25+
2026
// ============================================================================
2127
// Request Queue (Serialization)
2228
// ============================================================================

src/core/dma_interface.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,33 @@ using FN_ScatterExecute = BOOL(*)(void*);
151151
using FN_ScatterRead = BOOL(*)(void*, ULONG64, DWORD, PBYTE, PDWORD);
152152
using FN_ScatterCloseHandle = void(*)(void*);
153153
using FN_ConfigSet = BOOL(*)(void*, ULONG64, ULONG64);
154+
using FN_ConfigGet = BOOL(*)(void*, ULONG64, PULONG64);
155+
using FN_VfsReadU = DWORD(*)(void*, LPCSTR, PBYTE, DWORD, PDWORD, ULONG64);
156+
157+
// LeechCore FPGA option to get device ID
158+
static constexpr ULONG64 LC_OPT_FPGA_FPGA_ID = 0x0300008100000000ULL;
159+
160+
// FPGA Device ID to friendly name mapping (from LeechCore device_fpga.c)
161+
static const char* GetFPGADeviceName(uint64_t device_id) {
162+
switch (device_id) {
163+
case 0x00: return "SP605 / FT601";
164+
case 0x01: return "PCIeScreamer R1";
165+
case 0x02: return "AC701 / FT601";
166+
case 0x03: return "PCIeScreamer R2";
167+
case 0x04: return "ScreamerM2";
168+
case 0x05: return "NeTV2 RawUDP";
169+
case 0x08: return "FT2232H";
170+
case 0x09: return "Enigma X1";
171+
case 0x0A: return "Enigma X2";
172+
case 0x0B: return "ScreamerM2x4";
173+
case 0x0C: return "PCIeSquirrel";
174+
case 0x0D: return "Device #13N";
175+
case 0x0E: return "Device #14T";
176+
case 0x0F: return "Device #15N";
177+
case 0x10: return "Device #16T";
178+
default: return nullptr; // Unknown device
179+
}
180+
}
154181

155182
// VMM config options for refresh
156183
static constexpr ULONG64 OPT_REFRESH_ALL = 0x2001ffff;
@@ -174,6 +201,8 @@ static FN_ScatterExecute fn_ScatterExecute = nullptr;
174201
static FN_ScatterRead fn_ScatterRead = nullptr;
175202
static FN_ScatterCloseHandle fn_ScatterClose = nullptr;
176203
static FN_ConfigSet fn_ConfigSet = nullptr;
204+
static FN_ConfigGet fn_ConfigGet = nullptr;
205+
static FN_VfsReadU fn_VfsReadU = nullptr;
177206

178207
static void* vmm_module = nullptr;
179208

@@ -207,6 +236,8 @@ static bool LoadVMMFunctions() {
207236
fn_ScatterRead = LoadFunction<FN_ScatterRead>("VMMDLL_Scatter_Read");
208237
fn_ScatterClose = LoadFunction<FN_ScatterCloseHandle>("VMMDLL_Scatter_CloseHandle");
209238
fn_ConfigSet = LoadFunction<FN_ConfigSet>("VMMDLL_ConfigSet");
239+
fn_ConfigGet = LoadFunction<FN_ConfigGet>("VMMDLL_ConfigGet");
240+
fn_VfsReadU = LoadFunction<FN_VfsReadU>("VMMDLL_VfsReadU");
210241

211242
return fn_Initialize && fn_Close && fn_MemFree && fn_PidList && fn_ProcessGetInfo && fn_MemRead;
212243
}
@@ -269,13 +300,27 @@ bool DMAInterface::Initialize(const std::string& device) {
269300
ReportError("VMMDLL_Initialize failed for device: " + device);
270301
return false;
271302
}
303+
304+
// Try to get the actual FPGA device name via ConfigGet
305+
device_type_ = device; // Fallback to device string (e.g., "fpga")
306+
if (fn_ConfigGet && device == "fpga") {
307+
ULONG64 fpga_id = 0;
308+
if (fn_ConfigGet(vmm_handle_, LC_OPT_FPGA_FPGA_ID, &fpga_id)) {
309+
const char* name = GetFPGADeviceName(fpga_id);
310+
if (name) {
311+
device_type_ = name;
312+
}
313+
}
314+
}
315+
272316
return true;
273317
}
274318

275319
void DMAInterface::Close() {
276320
if (vmm_handle_ != nullptr && fn_Close != nullptr) {
277321
fn_Close(vmm_handle_);
278322
vmm_handle_ = nullptr;
323+
device_type_.clear();
279324
}
280325
}
281326

src/core/dma_interface.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ class DMAInterface {
9898
*/
9999
[[nodiscard]] bool IsConnected() const { return vmm_handle_ != nullptr; }
100100

101+
/**
102+
* Get the device type string (e.g., "fpga", "file://...")
103+
*/
104+
[[nodiscard]] const std::string& GetDeviceType() const { return device_type_; }
105+
101106
// =========================================================================
102107
// Process Operations
103108
// =========================================================================
@@ -241,6 +246,7 @@ class DMAInterface {
241246
void ReportError(const std::string& message);
242247

243248
VMMHandle vmm_handle_ = nullptr;
249+
std::string device_type_;
244250
std::string last_error_;
245251
std::function<void(const std::string&)> error_callback_;
246252
};

0 commit comments

Comments
 (0)