universal-game-modder 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ACCEPTABLE_USE.md +49 -0
- package/CHANGELOG.md +119 -0
- package/LICENSE +21 -0
- package/LICENSE-EULA.md +62 -0
- package/README.md +162 -0
- package/VERIFICATION.md +132 -0
- package/dist/backends/child-pool.js +91 -0
- package/dist/backends/mcp-child.js +102 -0
- package/dist/backends/mcp-child.js.map +1 -0
- package/dist/core/config.d.ts +10 -0
- package/dist/core/config.js +48 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/engine-detector.d.ts +13 -0
- package/dist/core/engine-detector.js +247 -0
- package/dist/core/engine-detector.js.map +1 -0
- package/dist/core/event-bus.js +18 -0
- package/dist/core/file-classifier.js +394 -0
- package/dist/core/model-db.js +517 -0
- package/dist/core/model-db.js.map +1 -0
- package/dist/core/router.d.ts +6 -0
- package/dist/core/router.js +143 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/session.js +114 -0
- package/dist/decoders/audio-export.js +57 -0
- package/dist/decoders/mesh-export.d.ts +20 -0
- package/dist/decoders/mesh-export.js +174 -0
- package/dist/decoders/mesh-export.js.map +1 -0
- package/dist/decoders/native/disassembler.js +85 -0
- package/dist/decoders/native/disassembler.js.map +1 -0
- package/dist/decoders/png-encode.d.ts +17 -0
- package/dist/decoders/png-encode.js +33 -0
- package/dist/decoders/texture-decode.js +278 -0
- package/dist/decoders/texture-decode.js.map +1 -0
- package/dist/decoders/unity/audioclip.d.ts +55 -0
- package/dist/decoders/unity/audioclip.js +87 -0
- package/dist/decoders/unity/audioclip.js.map +1 -0
- package/dist/decoders/unity/binary-reader.d.ts +45 -0
- package/dist/decoders/unity/binary-reader.js +141 -0
- package/dist/decoders/unity/binary-reader.js.map +1 -0
- package/dist/decoders/unity/mesh.js +198 -0
- package/dist/decoders/unity/mesh.js.map +1 -0
- package/dist/decoders/unity/ress-reader.d.ts +33 -0
- package/dist/decoders/unity/ress-reader.js +82 -0
- package/dist/decoders/unity/ress-reader.js.map +1 -0
- package/dist/decoders/unity/serialized-file.d.ts +65 -0
- package/dist/decoders/unity/serialized-file.js +144 -0
- package/dist/decoders/unity/texture2d.js +185 -0
- package/dist/decoders/unity/vertex-data.d.ts +54 -0
- package/dist/decoders/unity/vertex-data.js +174 -0
- package/dist/decoders/unity/vertex-data.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +23 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +66 -0
- package/dist/tools/decode-tools.d.ts +29 -0
- package/dist/tools/decode-tools.js +441 -0
- package/dist/tools/delegates/jar-editor.d.ts +2 -0
- package/dist/tools/delegates/jar-editor.js +276 -0
- package/dist/tools/delegates/unity-decompiler.d.ts +2 -0
- package/dist/tools/delegates/unity-decompiler.js +291 -0
- package/dist/tools/delegates/unreal-assets.d.ts +2 -0
- package/dist/tools/delegates/unreal-assets.js +122 -0
- package/dist/tools/detection-tools.d.ts +2 -0
- package/dist/tools/detection-tools.js +164 -0
- package/dist/tools/detection-tools.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +30 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/native/index.d.ts +2 -0
- package/dist/tools/native/index.js +1201 -0
- package/dist/tools/native-comprehension-tools.js +298 -0
- package/dist/tools/native-comprehension-tools.js.map +1 -0
- package/dist/tools/unpack-tools.d.ts +2 -0
- package/dist/tools/unpack-tools.js +382 -0
- package/dist/tools/workflow-tools.d.ts +2 -0
- package/dist/tools/workflow-tools.js +434 -0
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.js +49 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/path-utils.d.ts +15 -0
- package/dist/utils/path-utils.js +24 -0
- package/dist/utils/path-utils.js.map +1 -0
- package/dist/utils/steam-finder.d.ts +8 -0
- package/dist/utils/steam-finder.js +32 -0
- package/dist/web/web-server.d.ts +1 -0
- package/dist/web/web-server.js +369 -0
- package/licenses/THIRD-PARTY-NOTICES.md +35 -0
- package/llms-install.md +34 -0
- package/package.json +69 -0
- package/server.json +20 -0
- package/setup.ps1 +34 -0
- package/ugm.config.json +9 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { createServer } from 'http';
|
|
3
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
4
|
+
import { join, dirname } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { existsSync } from 'fs';
|
|
7
|
+
import { session } from '../core/session.js';
|
|
8
|
+
import { eventBus } from '../core/event-bus.js';
|
|
9
|
+
import { childPool } from '../backends/child-pool.js';
|
|
10
|
+
import { logger } from '../utils/logger.js';
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
let wss;
|
|
14
|
+
function broadcast(event, data) {
|
|
15
|
+
if (!wss)
|
|
16
|
+
return;
|
|
17
|
+
const message = JSON.stringify({ event, data, timestamp: Date.now() });
|
|
18
|
+
wss.clients.forEach(client => {
|
|
19
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
20
|
+
client.send(message);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function startWebServer(port) {
|
|
25
|
+
const app = express();
|
|
26
|
+
app.use(express.json());
|
|
27
|
+
// Serve static files (React build)
|
|
28
|
+
const publicDir = join(__dirname, '..', 'web', 'public');
|
|
29
|
+
if (existsSync(publicDir)) {
|
|
30
|
+
app.use(express.static(publicDir));
|
|
31
|
+
}
|
|
32
|
+
// API endpoints
|
|
33
|
+
app.get('/api/session', (_req, res) => {
|
|
34
|
+
res.json(session.getSerializableState());
|
|
35
|
+
});
|
|
36
|
+
app.get('/api/children', (_req, res) => {
|
|
37
|
+
res.json(childPool.getStatus());
|
|
38
|
+
});
|
|
39
|
+
app.get('/api/health', (_req, res) => {
|
|
40
|
+
res.json({
|
|
41
|
+
status: 'ok',
|
|
42
|
+
uptime: process.uptime(),
|
|
43
|
+
memory: process.memoryUsage(),
|
|
44
|
+
session: {
|
|
45
|
+
gameName: session.getState().gameName,
|
|
46
|
+
engine: session.getState().engine,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
// SPA fallback
|
|
51
|
+
app.get('*', (_req, res) => {
|
|
52
|
+
const indexPath = join(publicDir, 'index.html');
|
|
53
|
+
if (existsSync(indexPath)) {
|
|
54
|
+
res.sendFile(indexPath);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Serve inline dashboard if no React build exists
|
|
58
|
+
res.send(getInlineDashboard());
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const server = createServer(app);
|
|
62
|
+
// WebSocket for real-time updates
|
|
63
|
+
wss = new WebSocketServer({ server });
|
|
64
|
+
wss.on('connection', (ws) => {
|
|
65
|
+
// Send current state on connect
|
|
66
|
+
ws.send(JSON.stringify({
|
|
67
|
+
event: 'session:full',
|
|
68
|
+
data: session.getSerializableState(),
|
|
69
|
+
timestamp: Date.now(),
|
|
70
|
+
}));
|
|
71
|
+
ws.send(JSON.stringify({
|
|
72
|
+
event: 'children:status',
|
|
73
|
+
data: childPool.getStatus(),
|
|
74
|
+
timestamp: Date.now(),
|
|
75
|
+
}));
|
|
76
|
+
});
|
|
77
|
+
// Wire up event bus to WebSocket broadcast
|
|
78
|
+
const events = [
|
|
79
|
+
'tool:start', 'tool:complete', 'tool:error',
|
|
80
|
+
'session:updated', 'child:status', 'build:output', 'engine:detected',
|
|
81
|
+
];
|
|
82
|
+
for (const event of events) {
|
|
83
|
+
eventBus.on(event, (data) => broadcast(event, data));
|
|
84
|
+
}
|
|
85
|
+
// A dashboard bind failure (e.g. EADDRINUSE) must never kill the MCP server.
|
|
86
|
+
// listen() errors are emitted async on the 'error' event, so a try/catch
|
|
87
|
+
// around startWebServer() cannot catch them.
|
|
88
|
+
server.on('error', (err) => {
|
|
89
|
+
logger.warn(`UGM Web Dashboard unavailable (port ${port}): ${err.message}`);
|
|
90
|
+
});
|
|
91
|
+
wss.on('error', (err) => {
|
|
92
|
+
logger.warn(`UGM Web Dashboard WebSocket error: ${err.message}`);
|
|
93
|
+
});
|
|
94
|
+
server.listen(port, () => {
|
|
95
|
+
logger.info(`UGM Web Dashboard running at http://localhost:${port}`);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function getInlineDashboard() {
|
|
99
|
+
return `<!DOCTYPE html>
|
|
100
|
+
<html lang="en">
|
|
101
|
+
<head>
|
|
102
|
+
<meta charset="UTF-8">
|
|
103
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
104
|
+
<title>Universal Game Modder</title>
|
|
105
|
+
<style>
|
|
106
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
107
|
+
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #0a0a0f; color: #e0e0e0; }
|
|
108
|
+
.header {
|
|
109
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
|
110
|
+
padding: 20px 30px; border-bottom: 2px solid #e94560;
|
|
111
|
+
display: flex; align-items: center; gap: 15px;
|
|
112
|
+
}
|
|
113
|
+
.header h1 { font-size: 24px; color: #fff; }
|
|
114
|
+
.header .subtitle { color: #888; font-size: 14px; }
|
|
115
|
+
.engine-badge {
|
|
116
|
+
padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 600;
|
|
117
|
+
text-transform: uppercase; letter-spacing: 1px;
|
|
118
|
+
}
|
|
119
|
+
.engine-unity { background: #1a73e8; color: white; }
|
|
120
|
+
.engine-unreal { background: #9b59b6; color: white; }
|
|
121
|
+
.engine-java { background: #e67e22; color: white; }
|
|
122
|
+
.engine-godot { background: #27ae60; color: white; }
|
|
123
|
+
.engine-native { background: #666; color: white; }
|
|
124
|
+
.engine-none { background: #333; color: #888; }
|
|
125
|
+
.container { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; padding: 20px; max-width: 1600px; margin: 0 auto; }
|
|
126
|
+
.panel { background: #12121a; border: 1px solid #2a2a3a; border-radius: 8px; overflow: hidden; }
|
|
127
|
+
.panel-header { padding: 12px 16px; background: #1a1a2e; border-bottom: 1px solid #2a2a3a; font-weight: 600; font-size: 14px; display: flex; justify-content: space-between; align-items: center; }
|
|
128
|
+
.panel-body { padding: 16px; max-height: 400px; overflow-y: auto; }
|
|
129
|
+
.panel-body::-webkit-scrollbar { width: 6px; }
|
|
130
|
+
.panel-body::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }
|
|
131
|
+
.info-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #1a1a2a; }
|
|
132
|
+
.info-label { color: #888; }
|
|
133
|
+
.info-value { color: #e0e0e0; font-family: 'Cascadia Code', monospace; }
|
|
134
|
+
.log-entry { padding: 6px 8px; border-bottom: 1px solid #1a1a2a; font-size: 13px; font-family: monospace; }
|
|
135
|
+
.log-entry.success { border-left: 3px solid #27ae60; }
|
|
136
|
+
.log-entry.error { border-left: 3px solid #e94560; }
|
|
137
|
+
.log-entry.pending { border-left: 3px solid #f39c12; }
|
|
138
|
+
.log-time { color: #555; font-size: 11px; }
|
|
139
|
+
.log-tool { color: #1a73e8; font-weight: 600; }
|
|
140
|
+
.log-backend { color: #888; font-size: 11px; }
|
|
141
|
+
.log-duration { color: #27ae60; float: right; }
|
|
142
|
+
.child-status { display: flex; align-items: center; gap: 8px; padding: 8px 0; }
|
|
143
|
+
.status-dot { width: 8px; height: 8px; border-radius: 50%; }
|
|
144
|
+
.status-dot.connected { background: #27ae60; box-shadow: 0 0 6px #27ae60; }
|
|
145
|
+
.status-dot.idle { background: #555; }
|
|
146
|
+
.status-dot.disconnected { background: #e94560; }
|
|
147
|
+
.status-dot.error { background: #e94560; box-shadow: 0 0 6px #e94560; }
|
|
148
|
+
.full-width { grid-column: 1 / -1; }
|
|
149
|
+
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
|
|
150
|
+
.stat-card { background: #1a1a2e; border-radius: 6px; padding: 16px; text-align: center; }
|
|
151
|
+
.stat-value { font-size: 28px; font-weight: 700; color: #e94560; }
|
|
152
|
+
.stat-label { font-size: 12px; color: #888; margin-top: 4px; }
|
|
153
|
+
.empty-state { color: #555; text-align: center; padding: 40px; font-size: 14px; }
|
|
154
|
+
</style>
|
|
155
|
+
</head>
|
|
156
|
+
<body>
|
|
157
|
+
<div class="header">
|
|
158
|
+
<div>
|
|
159
|
+
<h1>Universal Game Modder</h1>
|
|
160
|
+
<div class="subtitle">MCP Tool Orchestrator</div>
|
|
161
|
+
</div>
|
|
162
|
+
<span id="engineBadge" class="engine-badge engine-none">No Game Loaded</span>
|
|
163
|
+
<div style="margin-left: auto; color: #555; font-size: 12px;">
|
|
164
|
+
<span id="connectionStatus">Connecting...</span>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div class="container">
|
|
169
|
+
<!-- Stats -->
|
|
170
|
+
<div class="panel full-width">
|
|
171
|
+
<div class="panel-body">
|
|
172
|
+
<div class="stat-grid">
|
|
173
|
+
<div class="stat-card">
|
|
174
|
+
<div class="stat-value" id="statToolCalls">0</div>
|
|
175
|
+
<div class="stat-label">Tool Calls</div>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="stat-card">
|
|
178
|
+
<div class="stat-value" id="statSuccessRate">-</div>
|
|
179
|
+
<div class="stat-label">Success Rate</div>
|
|
180
|
+
</div>
|
|
181
|
+
<div class="stat-card">
|
|
182
|
+
<div class="stat-value" id="statTypesFound">0</div>
|
|
183
|
+
<div class="stat-label">Types Cached</div>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="stat-card">
|
|
186
|
+
<div class="stat-value" id="statUptime">0s</div>
|
|
187
|
+
<div class="stat-label">Uptime</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<!-- Game Info -->
|
|
194
|
+
<div class="panel">
|
|
195
|
+
<div class="panel-header">Game Info</div>
|
|
196
|
+
<div class="panel-body" id="gameInfo">
|
|
197
|
+
<div class="empty-state">No game loaded. Use load_game or mod_this_game to start.</div>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
<!-- Child Processes -->
|
|
202
|
+
<div class="panel">
|
|
203
|
+
<div class="panel-header">Backend Processes</div>
|
|
204
|
+
<div class="panel-body" id="childProcesses">
|
|
205
|
+
<div class="child-status" data-name="unity-decompiler"><span class="status-dot disconnected"></span> unity-decompiler <span style="color:#555;margin-left:auto">idle</span></div>
|
|
206
|
+
<div class="child-status" data-name="unreal-assets"><span class="status-dot disconnected"></span> unreal-assets <span style="color:#555;margin-left:auto">idle</span></div>
|
|
207
|
+
<div class="child-status" data-name="jar-editor"><span class="status-dot disconnected"></span> jar-editor <span style="color:#555;margin-left:auto">idle</span></div>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<!-- Tool Log -->
|
|
212
|
+
<div class="panel full-width">
|
|
213
|
+
<div class="panel-header">
|
|
214
|
+
Tool Call Log
|
|
215
|
+
<span id="logCount" style="color:#555;font-size:12px">0 calls</span>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="panel-body" id="toolLog" style="max-height: 500px;">
|
|
218
|
+
<div class="empty-state">Waiting for tool calls...</div>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<script>
|
|
224
|
+
const state = { toolCalls: 0, successes: 0, startTime: Date.now() };
|
|
225
|
+
|
|
226
|
+
function connect() {
|
|
227
|
+
const ws = new WebSocket('ws://' + location.host);
|
|
228
|
+
|
|
229
|
+
ws.onopen = () => {
|
|
230
|
+
document.getElementById('connectionStatus').textContent = 'Connected';
|
|
231
|
+
document.getElementById('connectionStatus').style.color = '#27ae60';
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
ws.onclose = () => {
|
|
235
|
+
document.getElementById('connectionStatus').textContent = 'Disconnected';
|
|
236
|
+
document.getElementById('connectionStatus').style.color = '#e94560';
|
|
237
|
+
setTimeout(connect, 2000);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
ws.onmessage = (event) => {
|
|
241
|
+
const msg = JSON.parse(event.data);
|
|
242
|
+
handleEvent(msg.event, msg.data);
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function handleEvent(event, data) {
|
|
247
|
+
switch (event) {
|
|
248
|
+
case 'session:full':
|
|
249
|
+
case 'session:updated':
|
|
250
|
+
updateGameInfo(data);
|
|
251
|
+
break;
|
|
252
|
+
case 'tool:start':
|
|
253
|
+
addLogEntry(data, 'pending');
|
|
254
|
+
break;
|
|
255
|
+
case 'tool:complete':
|
|
256
|
+
updateLogEntry(data);
|
|
257
|
+
state.toolCalls++;
|
|
258
|
+
if (data.success) state.successes++;
|
|
259
|
+
updateStats();
|
|
260
|
+
break;
|
|
261
|
+
case 'tool:error':
|
|
262
|
+
addLogEntry(data, 'error');
|
|
263
|
+
state.toolCalls++;
|
|
264
|
+
updateStats();
|
|
265
|
+
break;
|
|
266
|
+
case 'child:status':
|
|
267
|
+
updateChildStatus(data);
|
|
268
|
+
break;
|
|
269
|
+
case 'engine:detected':
|
|
270
|
+
updateEngineBadge(data.engine, data.runtime);
|
|
271
|
+
break;
|
|
272
|
+
case 'children:status':
|
|
273
|
+
updateAllChildren(data);
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function updateGameInfo(data) {
|
|
279
|
+
const el = document.getElementById('gameInfo');
|
|
280
|
+
if (!data.gameName && !data.gamePath) return;
|
|
281
|
+
el.innerHTML = [
|
|
282
|
+
row('Game', data.gameName || 'Unknown'),
|
|
283
|
+
row('Path', data.gamePath || '-'),
|
|
284
|
+
row('Engine', data.engine || 'Not detected'),
|
|
285
|
+
row('Runtime', data.runtime || '-'),
|
|
286
|
+
row('Assemblies', (data.loadedAssemblies || []).length),
|
|
287
|
+
row('Framework', data.modFramework || '-'),
|
|
288
|
+
].join('');
|
|
289
|
+
if (data.engine) updateEngineBadge(data.engine, data.runtime);
|
|
290
|
+
if (data.discoveredTypesCount) document.getElementById('statTypesFound').textContent = data.discoveredTypesCount;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function row(label, value) {
|
|
294
|
+
return '<div class="info-row"><span class="info-label">' + label + '</span><span class="info-value">' + value + '</span></div>';
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function addLogEntry(data, status) {
|
|
298
|
+
const el = document.getElementById('toolLog');
|
|
299
|
+
if (el.querySelector('.empty-state')) el.innerHTML = '';
|
|
300
|
+
const entry = document.createElement('div');
|
|
301
|
+
entry.className = 'log-entry ' + status;
|
|
302
|
+
entry.id = 'log-' + data.tool + '-' + (data.timestamp || Date.now());
|
|
303
|
+
entry.innerHTML = '<span class="log-time">' + new Date().toLocaleTimeString() + '</span> '
|
|
304
|
+
+ '<span class="log-tool">' + data.tool + '</span> '
|
|
305
|
+
+ '<span class="log-backend">[' + data.backend + ']</span>'
|
|
306
|
+
+ (status === 'pending' ? '<span class="log-duration" style="color:#f39c12">running...</span>' : '');
|
|
307
|
+
el.insertBefore(entry, el.firstChild);
|
|
308
|
+
document.getElementById('logCount').textContent = el.children.length + ' calls';
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function updateLogEntry(data) {
|
|
312
|
+
const entries = document.querySelectorAll('.log-entry.pending');
|
|
313
|
+
for (const entry of entries) {
|
|
314
|
+
if (entry.querySelector('.log-tool')?.textContent === data.tool) {
|
|
315
|
+
entry.className = 'log-entry ' + (data.success ? 'success' : 'error');
|
|
316
|
+
const dur = entry.querySelector('.log-duration');
|
|
317
|
+
if (dur) { dur.textContent = data.duration + 'ms'; dur.style.color = data.success ? '#27ae60' : '#e94560'; }
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
addLogEntry(data, data.success ? 'success' : 'error');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function updateChildStatus(data) {
|
|
325
|
+
const container = document.getElementById('childProcesses');
|
|
326
|
+
const dotClass = { spawned: 'connected', running: 'connected', idle: 'idle', exited: 'disconnected', error: 'error' };
|
|
327
|
+
const labelText = { spawned: 'running', running: 'running', idle: 'idle', exited: 'stopped', error: 'error' };
|
|
328
|
+
const labelColor = { spawned: '#27ae60', running: '#27ae60', idle: '#888', exited: '#e94560', error: '#e94560' };
|
|
329
|
+
const s = data.status || 'idle';
|
|
330
|
+
const existing = container.querySelector('[data-name="' + data.name + '"]');
|
|
331
|
+
const html = '<span class="status-dot ' + (dotClass[s] || 'idle') + '"></span> '
|
|
332
|
+
+ data.name + ' <span style="color:' + (labelColor[s] || '#888') + ';margin-left:auto">' + (labelText[s] || s) + '</span>';
|
|
333
|
+
if (existing) { existing.innerHTML = html; }
|
|
334
|
+
else { const div = document.createElement('div'); div.className = 'child-status'; div.dataset.name = data.name; div.innerHTML = html; container.appendChild(div); }
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function updateAllChildren(data) {
|
|
338
|
+
for (const [name, info] of Object.entries(data)) {
|
|
339
|
+
updateChildStatus({ name, status: info.connected ? 'running' : 'idle' });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function updateEngineBadge(engine, runtime) {
|
|
344
|
+
const el = document.getElementById('engineBadge');
|
|
345
|
+
const engineClasses = { 'unity-mono': 'unity', 'unity-il2cpp': 'unity', 'unreal': 'unreal', 'java': 'java', 'godot': 'godot' };
|
|
346
|
+
const cls = engineClasses[engine] || 'native';
|
|
347
|
+
el.className = 'engine-badge engine-' + cls;
|
|
348
|
+
el.textContent = (engine || 'Unknown') + (runtime ? ' (' + runtime + ')' : '');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function updateStats() {
|
|
352
|
+
document.getElementById('statToolCalls').textContent = state.toolCalls;
|
|
353
|
+
document.getElementById('statSuccessRate').textContent = state.toolCalls > 0
|
|
354
|
+
? Math.round(state.successes / state.toolCalls * 100) + '%' : '-';
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
setInterval(() => {
|
|
358
|
+
const secs = Math.floor((Date.now() - state.startTime) / 1000);
|
|
359
|
+
const mins = Math.floor(secs / 60);
|
|
360
|
+
const hrs = Math.floor(mins / 60);
|
|
361
|
+
document.getElementById('statUptime').textContent = hrs > 0 ? hrs + 'h' + (mins%60) + 'm' : mins > 0 ? mins + 'm' : secs + 's';
|
|
362
|
+
}, 1000);
|
|
363
|
+
|
|
364
|
+
connect();
|
|
365
|
+
</script>
|
|
366
|
+
</body>
|
|
367
|
+
</html>`;
|
|
368
|
+
}
|
|
369
|
+
//# sourceMappingURL=web-server.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
Universal Game Modder uses the following third-party components. This file records their licenses and attribution.
|
|
4
|
+
|
|
5
|
+
## Runtime dependencies (bundled in the free open-core edition)
|
|
6
|
+
|
|
7
|
+
The free edition's native tools run in-process via these npm packages (installed by `npm install`, not vendored in this repo):
|
|
8
|
+
|
|
9
|
+
| Component | Purpose | License |
|
|
10
|
+
|-----------|---------|---------|
|
|
11
|
+
| @modelcontextprotocol/sdk | MCP protocol implementation | MIT |
|
|
12
|
+
| better-sqlite3 | Session state storage | MIT |
|
|
13
|
+
| express | Local web server (Pro dashboard) | MIT |
|
|
14
|
+
| ws | WebSocket transport (Pro dashboard) | MIT |
|
|
15
|
+
| pngjs | PNG handling | MIT |
|
|
16
|
+
| texture2ddecoder-wasm | Texture decoding | MIT |
|
|
17
|
+
|
|
18
|
+
## Pro-tier delegate backends (NOT bundled in this free edition)
|
|
19
|
+
|
|
20
|
+
The Pro tier delegates to external decompilation/asset engines. Their licenses are recorded here for completeness. **None of these binaries ship in the free open-core edition** — they are separate, user-installed or Pro-bundled components. When bundled in the Pro tier, each engine's own LICENSE and NOTICE file must accompany it.
|
|
21
|
+
|
|
22
|
+
| Engine | Used by | License | Obligation on redistribution |
|
|
23
|
+
|--------|---------|---------|------------------------------|
|
|
24
|
+
| ICSharpCode.Decompiler (ILSpy) | Unity Mono/IL2CPP decompilation | MIT | Include MIT license text |
|
|
25
|
+
| CUE4Parse / CUE4Parse-Conversion | Unreal asset reading | Apache-2.0 | Include license text + NOTICE, document modifications |
|
|
26
|
+
| CFR | Java JAR decompilation | MIT | Include MIT license text |
|
|
27
|
+
| Lib.Harmony | Runtime patching (Unreal test path) | MIT / LGPL-2.1 (dual) | Use the MIT path; include license text |
|
|
28
|
+
|
|
29
|
+
## Known pending item
|
|
30
|
+
|
|
31
|
+
- **capstone-wasm** (native disassembly binding, from the `disasm-web` monorepo): **license undeclared** at the package level. The underlying Capstone engine is BSD-3, but the wasm wrapper carries no explicit license. Native disassembly is therefore **excluded from any shipped bundle** until this is resolved (confirm the monorepo's LICENSE, swap the binding, or make disassembly an optional user-installed capability). It is not part of the free open-core edition.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
*Every license listed above is permissive (MIT / Apache-2.0). None imposes a copyleft source-disclosure obligation on Universal Game Modder's own code.*
|
package/llms-install.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Installing Universal Game Modder (for AI agents such as Cline)
|
|
2
|
+
|
|
3
|
+
Universal Game Modder (UGM) is a local stdio MCP server. It needs Node.js 18+ and nothing else.
|
|
4
|
+
|
|
5
|
+
## Steps
|
|
6
|
+
|
|
7
|
+
1. Clone the repository:
|
|
8
|
+
`git clone https://github.com/doritoman90000/universal-game-modder.git`
|
|
9
|
+
2. Install dependencies from the repository root (`dist/` is prebuilt; there is nothing to compile):
|
|
10
|
+
`npm install`
|
|
11
|
+
3. Register the server with the MCP client. Use the ABSOLUTE path to `dist/index.js` inside the cloned folder. Example MCP settings entry:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"universal-game-modder": {
|
|
17
|
+
"command": "node",
|
|
18
|
+
"args": ["/absolute/path/to/universal-game-modder/dist/index.js"]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
4. Restart the MCP client. The server announces itself as `universal-game-modder` and lists 100 tools. 29 of them (engine detection, session, and the native binary toolset) run entirely in-process with no configuration. The remaining tools route to optional external backends and return a clear "not configured" error until a path is set in `ugm.config.json`.
|
|
25
|
+
|
|
26
|
+
## Verify
|
|
27
|
+
|
|
28
|
+
Ask the agent to call `detect_engine` with `{"game_path": "<a game folder>"}` or `analyze_file_format` with `{"file_path": "<any .exe or .dll>"}`. Both return real data on a fresh install. The full verified call list is in `VERIFICATION.md`; the release gate that produced it is `verify-release.mjs`.
|
|
29
|
+
|
|
30
|
+
## Notes
|
|
31
|
+
|
|
32
|
+
- No API keys, no accounts, no network calls. Everything runs on the local machine.
|
|
33
|
+
- Optional: a machine-specific `ugm.config.local.json` (git-ignored) holds backend paths; the tracked `ugm.config.json` is placeholders only.
|
|
34
|
+
- Read `ACCEPTABLE_USE.md` and `LICENSE-EULA.md` before use. UGM is for modding, interoperability, research and education on software you legally own.
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "universal-game-modder",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"mcpName": "io.github.doritoman90000/universal-game-modder",
|
|
5
|
+
"description": "Universal Game Modding Machine - One MCP server to mod any game",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"bin": {
|
|
10
|
+
"universal-game-modder": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"licenses",
|
|
15
|
+
"ugm.config.json",
|
|
16
|
+
"setup.ps1",
|
|
17
|
+
"README.md",
|
|
18
|
+
"CHANGELOG.md",
|
|
19
|
+
"VERIFICATION.md",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"LICENSE-EULA.md",
|
|
22
|
+
"ACCEPTABLE_USE.md",
|
|
23
|
+
"llms-install.md",
|
|
24
|
+
"server.json"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/doritoman90000/universal-game-modder.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/doritoman90000/universal-game-modder#readme",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/doritoman90000/universal-game-modder/issues"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"mcp",
|
|
36
|
+
"model-context-protocol",
|
|
37
|
+
"game-modding",
|
|
38
|
+
"reverse-engineering",
|
|
39
|
+
"binary-analysis",
|
|
40
|
+
"unity",
|
|
41
|
+
"unreal",
|
|
42
|
+
"godot"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc",
|
|
46
|
+
"build:ui": "cd src/web/public && npx vite build --outDir ../../../dist/web/public",
|
|
47
|
+
"build:all": "npm run build && npm run build:ui",
|
|
48
|
+
"dev": "tsx watch src/index.ts",
|
|
49
|
+
"start": "node dist/index.js"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
53
|
+
"better-sqlite3": "^11.8.0",
|
|
54
|
+
"capstone-wasm": "^1.0.3",
|
|
55
|
+
"express": "^4.21.0",
|
|
56
|
+
"pngjs": "^7.0.0",
|
|
57
|
+
"texture2ddecoder-wasm": "^1.2.2",
|
|
58
|
+
"ws": "^8.18.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/better-sqlite3": "^7.6.11",
|
|
62
|
+
"@types/express": "^4.17.21",
|
|
63
|
+
"@types/node": "^22.10.0",
|
|
64
|
+
"@types/pngjs": "^6.0.5",
|
|
65
|
+
"@types/ws": "^8.5.13",
|
|
66
|
+
"tsx": "^4.19.0",
|
|
67
|
+
"typescript": "^5.7.0"
|
|
68
|
+
}
|
|
69
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.doritoman90000/universal-game-modder",
|
|
4
|
+
"description": "Local MCP server: detects a game's engine and gives agents native binary analysis/patching tools.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/doritoman90000/universal-game-modder",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "0.1.4",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"identifier": "universal-game-modder",
|
|
14
|
+
"version": "0.1.4",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "stdio"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
package/setup.ps1
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Universal Game Modder - setup (Windows PowerShell)
|
|
2
|
+
# One-command install for the free open-core edition (v0.1.0).
|
|
3
|
+
|
|
4
|
+
$ErrorActionPreference = "Stop"
|
|
5
|
+
Set-Location -Path $PSScriptRoot
|
|
6
|
+
|
|
7
|
+
Write-Host "Universal Game Modder - setup" -ForegroundColor Cyan
|
|
8
|
+
Write-Host "-----------------------------------"
|
|
9
|
+
|
|
10
|
+
# 1. Node check
|
|
11
|
+
try {
|
|
12
|
+
$nodeVersion = node --version
|
|
13
|
+
Write-Host "Node.js found: $nodeVersion"
|
|
14
|
+
} catch {
|
|
15
|
+
Write-Host "ERROR: Node.js not found. Install Node.js 18+ from https://nodejs.org and re-run." -ForegroundColor Red
|
|
16
|
+
exit 1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# 2. Install dependencies
|
|
20
|
+
Write-Host "Installing dependencies (npm install)..." -ForegroundColor Cyan
|
|
21
|
+
npm install
|
|
22
|
+
if ($LASTEXITCODE -ne 0) { Write-Host "ERROR: npm install failed." -ForegroundColor Red; exit 1 }
|
|
23
|
+
|
|
24
|
+
# 3. Build (dist/ ships prebuilt, but rebuild to be safe if src/ is present)
|
|
25
|
+
if (Test-Path "tsconfig.json") {
|
|
26
|
+
Write-Host "Building (npm run build)..." -ForegroundColor Cyan
|
|
27
|
+
npm run build
|
|
28
|
+
if ($LASTEXITCODE -ne 0) { Write-Host "WARNING: build failed; prebuilt dist/ will be used if present." -ForegroundColor Yellow }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Write-Host ""
|
|
32
|
+
Write-Host "Done." -ForegroundColor Green
|
|
33
|
+
Write-Host "Next: register the server with your MCP client. See README.md -> Install." -ForegroundColor Green
|
|
34
|
+
Write-Host "The free edition needs no external paths - ugm.config.json ships with empty placeholders."
|