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,434 @@
|
|
|
1
|
+
import { detectEngine } from '../core/engine-detector.js';
|
|
2
|
+
import { session } from '../core/session.js';
|
|
3
|
+
import { extractGameName } from '../utils/path-utils.js';
|
|
4
|
+
import { loadConfig } from '../core/config.js';
|
|
5
|
+
import { childPool } from '../backends/child-pool.js';
|
|
6
|
+
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
7
|
+
import { join, basename } from 'path';
|
|
8
|
+
export function getWorkflowTools() {
|
|
9
|
+
return [
|
|
10
|
+
{
|
|
11
|
+
name: 'mod_this_game',
|
|
12
|
+
description: 'End-to-end game modding assistant. Detects engine, analyzes game structure, and returns a modding plan with available tools and next steps. Start here when modding a new game.',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
game_path: { type: 'string', description: 'Full path to the game directory' },
|
|
17
|
+
objective: { type: 'string', description: 'What you want to mod (e.g. "infinite health", "speed hack", "item spawner")' },
|
|
18
|
+
},
|
|
19
|
+
required: ['game_path'],
|
|
20
|
+
},
|
|
21
|
+
handler: async (args) => {
|
|
22
|
+
const gamePath = args.game_path;
|
|
23
|
+
const objective = args.objective;
|
|
24
|
+
const gameName = extractGameName(gamePath);
|
|
25
|
+
// Step 1: Detect engine
|
|
26
|
+
const detection = detectEngine(gamePath);
|
|
27
|
+
session.setGame(gamePath, gameName, detection.engine, detection.runtime);
|
|
28
|
+
const plan = {
|
|
29
|
+
gameName,
|
|
30
|
+
gamePath,
|
|
31
|
+
engine: detection.engine,
|
|
32
|
+
runtime: detection.runtime,
|
|
33
|
+
confidence: detection.confidence,
|
|
34
|
+
details: detection.details,
|
|
35
|
+
primaryAssembly: detection.primaryAssembly,
|
|
36
|
+
};
|
|
37
|
+
// Step 2: Determine modding strategy
|
|
38
|
+
switch (detection.engine) {
|
|
39
|
+
case 'unity-mono':
|
|
40
|
+
plan.strategy = {
|
|
41
|
+
framework: 'BepInEx 5 + Harmony',
|
|
42
|
+
steps: [
|
|
43
|
+
`1. Load assembly: load_assembly("${detection.primaryAssembly}")`,
|
|
44
|
+
'2. Search for target classes: list_types(assembly, filter) or search_code(assembly, pattern)',
|
|
45
|
+
'3. Inspect target type: inspect_type(assembly, type_name)',
|
|
46
|
+
'4. Decompile to verify: decompile_type(assembly, type_name)',
|
|
47
|
+
'5. Generate patch: generate_harmony_patch(assembly, type, method)',
|
|
48
|
+
'6. Generate plugin: generate_plugin(name, guid)',
|
|
49
|
+
'7. Compile: compile_plugin(source, managed_dir, bepinex_dir, output)',
|
|
50
|
+
'8. Deploy to BepInEx/plugins/',
|
|
51
|
+
'9. Test and debug: analyze_bepinex_log(log_path)',
|
|
52
|
+
],
|
|
53
|
+
deployPath: join(gamePath, 'BepInEx', 'plugins'),
|
|
54
|
+
};
|
|
55
|
+
session.setModFramework('bepinex5');
|
|
56
|
+
break;
|
|
57
|
+
case 'unity-il2cpp':
|
|
58
|
+
plan.strategy = {
|
|
59
|
+
framework: 'MelonLoader',
|
|
60
|
+
steps: [
|
|
61
|
+
`1. Dump metadata: unity_il2cpp_dump_metadata("${detection.metadataPath}")`,
|
|
62
|
+
'2. Search classes: unity_il2cpp_find_class(metadata_path, class_name)',
|
|
63
|
+
'3. Check anti-cheat: actk_scan_obscured_values(metadata_path)',
|
|
64
|
+
'4. Analyze GameAssembly.dll: analyze_pe_full(path)',
|
|
65
|
+
'5. Pattern scan for values: pattern_scan(path, pattern)',
|
|
66
|
+
'6. Generate MelonLoader mod code',
|
|
67
|
+
'7. Compile with dotnet build',
|
|
68
|
+
'8. Deploy to Mods/',
|
|
69
|
+
],
|
|
70
|
+
deployPath: join(gamePath, 'Mods'),
|
|
71
|
+
};
|
|
72
|
+
session.setModFramework('melonloader');
|
|
73
|
+
break;
|
|
74
|
+
case 'unreal':
|
|
75
|
+
plan.strategy = {
|
|
76
|
+
framework: 'Unreal Asset Editing / UE4SS',
|
|
77
|
+
steps: [
|
|
78
|
+
`1. Open game: open_game("${gamePath}")`,
|
|
79
|
+
'2. Search assets: search_assets(query)',
|
|
80
|
+
'3. Read target assets: read_asset(path) or read_blueprint(path)',
|
|
81
|
+
'4. Read datatables: read_datatable(path)',
|
|
82
|
+
'5. Modify values via binary patching or UE4SS Lua scripts',
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
break;
|
|
86
|
+
case 'java':
|
|
87
|
+
plan.strategy = {
|
|
88
|
+
framework: 'JAR Editor (direct class modification)',
|
|
89
|
+
steps: [
|
|
90
|
+
`1. Open JAR: jar_open("${detection.primaryAssembly}")`,
|
|
91
|
+
'2. List contents: jar_list(session_id)',
|
|
92
|
+
'3. Search for targets: jar_search(session_id, query)',
|
|
93
|
+
'4. Read target class: jar_read_class(session_id, class_path)',
|
|
94
|
+
'5. Inspect constants: jar_inspect_constant_pool(session_id, class_path)',
|
|
95
|
+
'6. Edit values: jar_edit_class_constants(session_id, class_path, replacements)',
|
|
96
|
+
'7. Repack JAR: jar_repack(session_id)',
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
session.setModFramework('jar-edit');
|
|
100
|
+
break;
|
|
101
|
+
case 'godot':
|
|
102
|
+
plan.strategy = {
|
|
103
|
+
framework: 'PCK Editor',
|
|
104
|
+
steps: [
|
|
105
|
+
`1. Analyze PCK: analyze_godot_pck("${detection.primaryAssembly}")`,
|
|
106
|
+
'2. Extract and read GDScript files',
|
|
107
|
+
'3. Modify scripts',
|
|
108
|
+
'4. Repack PCK',
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
session.setModFramework('pck-edit');
|
|
112
|
+
break;
|
|
113
|
+
default:
|
|
114
|
+
plan.strategy = {
|
|
115
|
+
framework: 'Binary analysis + patching',
|
|
116
|
+
steps: [
|
|
117
|
+
`1. Analyze executable: analyze_pe_full("${detection.primaryAssembly}")`,
|
|
118
|
+
'2. Extract strings: extract_strings_advanced(path)',
|
|
119
|
+
'3. Search patterns: pattern_scan(path, pattern)',
|
|
120
|
+
'4. Hex edit values: hex_replace(path, offset, data)',
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
session.setModFramework('binary-patch');
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
if (objective) {
|
|
127
|
+
plan.objective = objective;
|
|
128
|
+
plan.searchTerms = generateSearchTerms(objective);
|
|
129
|
+
}
|
|
130
|
+
return JSON.stringify(plan, null, 2);
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'find_gameplay_values',
|
|
135
|
+
description: 'Search for gameplay-related values (health, damage, speed, money, etc.) across the game code. Works with any engine by using the appropriate search tools.',
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: {
|
|
139
|
+
search_terms: {
|
|
140
|
+
type: 'array',
|
|
141
|
+
items: { type: 'string' },
|
|
142
|
+
description: 'Terms to search for (e.g. ["Health", "Damage", "Speed", "Money"])',
|
|
143
|
+
},
|
|
144
|
+
assembly_path: { type: 'string', description: 'Path to the game assembly/binary to search' },
|
|
145
|
+
},
|
|
146
|
+
required: ['search_terms', 'assembly_path'],
|
|
147
|
+
},
|
|
148
|
+
handler: async (args) => {
|
|
149
|
+
const terms = args.search_terms;
|
|
150
|
+
const assemblyPath = args.assembly_path;
|
|
151
|
+
const state = session.getState();
|
|
152
|
+
const results = {
|
|
153
|
+
searchTerms: terms,
|
|
154
|
+
target: assemblyPath,
|
|
155
|
+
engine: state.engine,
|
|
156
|
+
};
|
|
157
|
+
// Use appropriate tool based on engine
|
|
158
|
+
try {
|
|
159
|
+
if (state.engine === 'unity-mono') {
|
|
160
|
+
// Use unity-decompiler search_code
|
|
161
|
+
const searchResult = await childPool.call('unity-decompiler', 'search_code', {
|
|
162
|
+
assembly: basename(assemblyPath, '.dll'),
|
|
163
|
+
pattern: terms.join('|'),
|
|
164
|
+
scope: 'all',
|
|
165
|
+
limit: 200,
|
|
166
|
+
});
|
|
167
|
+
results.unitySearchResults = searchResult.content.map(c => c.text).join('\n');
|
|
168
|
+
}
|
|
169
|
+
else if (state.engine === 'java') {
|
|
170
|
+
// Use jar_search
|
|
171
|
+
const searchResult = await childPool.call('jar-editor', 'jar_search', {
|
|
172
|
+
session_id: state.openJarSessions[0]?.sessionId || '',
|
|
173
|
+
search_term: terms.join(' '),
|
|
174
|
+
search_type: 'content',
|
|
175
|
+
});
|
|
176
|
+
results.jarSearchResults = searchResult.content.map(c => c.text).join('\n');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
results.delegateError = err instanceof Error ? err.message : String(err);
|
|
181
|
+
}
|
|
182
|
+
// Always try native string search as fallback/complement
|
|
183
|
+
// This will be handled by the native tools once ported
|
|
184
|
+
results.suggestion = `For deeper analysis, try: extract_strings_advanced("${assemblyPath}", "${terms[0]}")`;
|
|
185
|
+
return JSON.stringify(results, null, 2);
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'build_and_deploy',
|
|
190
|
+
description: 'Compile a mod project and deploy the output to the game mods directory.',
|
|
191
|
+
inputSchema: {
|
|
192
|
+
type: 'object',
|
|
193
|
+
properties: {
|
|
194
|
+
project_path: { type: 'string', description: 'Path to the mod project directory' },
|
|
195
|
+
deploy_path: { type: 'string', description: 'Path to deploy the built mod (auto-detected if omitted)' },
|
|
196
|
+
build_command: { type: 'string', description: 'Custom build command (auto-detected if omitted)' },
|
|
197
|
+
},
|
|
198
|
+
required: ['project_path'],
|
|
199
|
+
},
|
|
200
|
+
handler: async (args) => {
|
|
201
|
+
const projectPath = args.project_path;
|
|
202
|
+
const state = session.getState();
|
|
203
|
+
const result = {
|
|
204
|
+
projectPath,
|
|
205
|
+
modFramework: state.modFramework,
|
|
206
|
+
gamePath: state.gamePath,
|
|
207
|
+
};
|
|
208
|
+
// Determine deploy path
|
|
209
|
+
let deployPath = args.deploy_path;
|
|
210
|
+
if (!deployPath && state.gamePath) {
|
|
211
|
+
switch (state.modFramework) {
|
|
212
|
+
case 'bepinex5':
|
|
213
|
+
case 'bepinex6':
|
|
214
|
+
deployPath = join(state.gamePath, 'BepInEx', 'plugins');
|
|
215
|
+
break;
|
|
216
|
+
case 'melonloader':
|
|
217
|
+
deployPath = join(state.gamePath, 'Mods');
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
result.deployPath = deployPath || 'Not auto-detected. Specify deploy_path parameter.';
|
|
222
|
+
// Determine build command
|
|
223
|
+
let buildCommand = args.build_command;
|
|
224
|
+
if (!buildCommand) {
|
|
225
|
+
if (existsSync(join(projectPath, '*.csproj')) || existsSync(join(projectPath, '*.sln'))) {
|
|
226
|
+
buildCommand = `dotnet build "${projectPath}" -c Release`;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
result.buildCommand = buildCommand || 'Could not auto-detect. Specify build_command parameter.';
|
|
230
|
+
result.instructions = 'Run the build command above, then copy the output DLL to the deploy path.';
|
|
231
|
+
return JSON.stringify(result, null, 2);
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: 'debug_mod',
|
|
236
|
+
description: 'Analyze mod logs to diagnose errors and suggest fixes.',
|
|
237
|
+
inputSchema: {
|
|
238
|
+
type: 'object',
|
|
239
|
+
properties: {
|
|
240
|
+
log_path: { type: 'string', description: 'Path to the mod log file' },
|
|
241
|
+
game_path: { type: 'string', description: 'Game directory (for auto-detecting log location)' },
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
handler: async (args) => {
|
|
245
|
+
let logPath = args.log_path;
|
|
246
|
+
const gamePath = args.game_path || session.getState().gamePath;
|
|
247
|
+
// Auto-detect log path if not provided
|
|
248
|
+
if (!logPath && gamePath) {
|
|
249
|
+
const candidates = [
|
|
250
|
+
join(gamePath, 'BepInEx', 'LogOutput.log'),
|
|
251
|
+
join(gamePath, 'MelonLoader', 'Latest.log'),
|
|
252
|
+
];
|
|
253
|
+
logPath = candidates.find(p => existsSync(p));
|
|
254
|
+
}
|
|
255
|
+
if (!logPath || !existsSync(logPath)) {
|
|
256
|
+
return JSON.stringify({
|
|
257
|
+
error: 'Log file not found',
|
|
258
|
+
triedPaths: logPath ? [logPath] : [],
|
|
259
|
+
suggestion: 'Provide log_path or ensure game has been run with mod framework installed.',
|
|
260
|
+
}, null, 2);
|
|
261
|
+
}
|
|
262
|
+
// Try to use BepInEx log analyzer via delegate
|
|
263
|
+
const state = session.getState();
|
|
264
|
+
if (state.modFramework === 'bepinex5' || state.modFramework === 'bepinex6') {
|
|
265
|
+
try {
|
|
266
|
+
const result = await childPool.call('unity-decompiler', 'analyze_bepinex_log', { log_path: logPath });
|
|
267
|
+
return result.content.map(c => c.text).join('\n');
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// Fall through to basic analysis
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Basic log analysis
|
|
274
|
+
const logContent = readFileSync(logPath, 'utf-8');
|
|
275
|
+
const lines = logContent.split('\n');
|
|
276
|
+
const errors = lines.filter(l => /error|exception|fail/i.test(l));
|
|
277
|
+
const warnings = lines.filter(l => /warn/i.test(l));
|
|
278
|
+
return JSON.stringify({
|
|
279
|
+
logPath,
|
|
280
|
+
totalLines: lines.length,
|
|
281
|
+
errors: errors.slice(0, 20),
|
|
282
|
+
warnings: warnings.slice(0, 10),
|
|
283
|
+
suggestion: errors.length > 0 ? 'Review the errors above. Common fixes: check assembly references, verify method signatures, ensure correct framework version.' : 'No errors found in log.',
|
|
284
|
+
}, null, 2);
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
name: 'scaffold_mod',
|
|
289
|
+
description: 'Generate a mod project skeleton from templates. Creates project files, plugin class, and build configuration.',
|
|
290
|
+
inputSchema: {
|
|
291
|
+
type: 'object',
|
|
292
|
+
properties: {
|
|
293
|
+
mod_name: { type: 'string', description: 'Mod name (used for class name and project)' },
|
|
294
|
+
mod_guid: { type: 'string', description: 'Unique mod identifier (e.g. com.author.modname)' },
|
|
295
|
+
output_dir: { type: 'string', description: 'Where to create the mod project' },
|
|
296
|
+
framework: { type: 'string', description: 'Mod framework: bepinex5, melonloader, jar (auto-detected from loaded game if omitted)' },
|
|
297
|
+
game_managed_dir: { type: 'string', description: 'Path to game Managed directory (for .csproj references)' },
|
|
298
|
+
},
|
|
299
|
+
required: ['mod_name', 'output_dir'],
|
|
300
|
+
},
|
|
301
|
+
handler: async (args) => {
|
|
302
|
+
const modName = args.mod_name;
|
|
303
|
+
const modGuid = args.mod_guid || `com.modder.${modName.toLowerCase().replace(/\s+/g, '')}`;
|
|
304
|
+
const outputDir = args.output_dir;
|
|
305
|
+
const framework = args.framework || session.getState().modFramework || 'bepinex5';
|
|
306
|
+
const managedDir = args.game_managed_dir;
|
|
307
|
+
const config = loadConfig();
|
|
308
|
+
const result = {
|
|
309
|
+
modName,
|
|
310
|
+
modGuid,
|
|
311
|
+
outputDir,
|
|
312
|
+
framework,
|
|
313
|
+
templateDir: config.modTemplatesDir,
|
|
314
|
+
};
|
|
315
|
+
// Check if templates exist
|
|
316
|
+
if (config.modTemplatesDir && existsSync(config.modTemplatesDir)) {
|
|
317
|
+
const available = readdirSync(config.modTemplatesDir, { withFileTypes: true })
|
|
318
|
+
.filter(e => e.isDirectory())
|
|
319
|
+
.map(e => e.name);
|
|
320
|
+
result.availableTemplates = available;
|
|
321
|
+
result.instructions = [
|
|
322
|
+
`Templates available at: ${config.modTemplatesDir}`,
|
|
323
|
+
`Create the project at: ${outputDir}`,
|
|
324
|
+
'Use generate_plugin tool for BepInEx plugins with auto-generated code.',
|
|
325
|
+
'Or read templates and customize manually.',
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
result.instructions = [
|
|
330
|
+
'No template directory configured or found.',
|
|
331
|
+
'For BepInEx: use generate_plugin tool to create plugin code.',
|
|
332
|
+
'For Java: use jar_scaffold_mod tool to create mod structure.',
|
|
333
|
+
];
|
|
334
|
+
}
|
|
335
|
+
if (managedDir) {
|
|
336
|
+
result.managedDir = managedDir;
|
|
337
|
+
}
|
|
338
|
+
return JSON.stringify(result, null, 2);
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: 'list_available_tools',
|
|
343
|
+
description: 'List all tools available for the currently loaded game engine, grouped by category.',
|
|
344
|
+
inputSchema: {
|
|
345
|
+
type: 'object',
|
|
346
|
+
properties: {
|
|
347
|
+
engine: { type: 'string', description: 'Engine to list tools for (uses current game if omitted)' },
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
handler: async (args) => {
|
|
351
|
+
const engine = args.engine || session.getState().engine;
|
|
352
|
+
const toolGroups = {
|
|
353
|
+
'Always Available': [
|
|
354
|
+
'detect_engine', 'load_game', 'game_status', 'find_steam_games',
|
|
355
|
+
'mod_this_game', 'find_gameplay_values', 'build_and_deploy',
|
|
356
|
+
'debug_mod', 'scaffold_mod', 'list_available_tools',
|
|
357
|
+
],
|
|
358
|
+
'Binary Analysis (any game)': [
|
|
359
|
+
'analyze_pe_full', 'analyze_dll_exports', 'analyze_dll_imports',
|
|
360
|
+
'analyze_file_format', 'hex_read', 'hex_write', 'hex_search', 'hex_replace',
|
|
361
|
+
'pattern_scan', 'pattern_scan_all', 'extract_strings_advanced',
|
|
362
|
+
'extract_dll_classes', 'search_binary_pattern', 'extract_strings',
|
|
363
|
+
'disassemble_function', 'calculate_checksums', 'compare_binaries_detailed',
|
|
364
|
+
],
|
|
365
|
+
};
|
|
366
|
+
switch (engine) {
|
|
367
|
+
case 'unity-mono':
|
|
368
|
+
toolGroups['Unity Mono (Decompilation & Patching)'] = [
|
|
369
|
+
'load_assembly', 'list_types', 'inspect_type', 'decompile_type',
|
|
370
|
+
'decompile_method', 'get_method_il', 'search_code', 'find_references',
|
|
371
|
+
'get_inheritance_tree', 'list_monobehaviours', 'list_scriptableobjects',
|
|
372
|
+
'get_serialized_fields', 'generate_harmony_patch', 'generate_plugin',
|
|
373
|
+
'compile_plugin', 'validate_patch_target', 'list_patchable_methods',
|
|
374
|
+
'analyze_bepinex_log', 'validate_assembly', 'verify_patches',
|
|
375
|
+
];
|
|
376
|
+
break;
|
|
377
|
+
case 'unity-il2cpp':
|
|
378
|
+
toolGroups['Unity IL2CPP (Metadata & Analysis)'] = [
|
|
379
|
+
'unity_il2cpp_dump_metadata', 'unity_il2cpp_find_class',
|
|
380
|
+
'actk_scan_obscured_values', 'actk_decrypt_value', 'actk_struct_info',
|
|
381
|
+
'unity_playerprefs_read', 'unity_playerprefs_write',
|
|
382
|
+
];
|
|
383
|
+
break;
|
|
384
|
+
case 'unreal':
|
|
385
|
+
toolGroups['Unreal Engine (Asset Reading)'] = [
|
|
386
|
+
'open_game', 'unreal_game_status', 'list_assets', 'search_assets',
|
|
387
|
+
'read_asset', 'read_asset_export', 'list_exports', 'read_blueprint',
|
|
388
|
+
'find_blueprints_of_type', 'read_datatable', 'get_class_hierarchy',
|
|
389
|
+
];
|
|
390
|
+
break;
|
|
391
|
+
case 'java':
|
|
392
|
+
toolGroups['Java (JAR Editing)'] = [
|
|
393
|
+
'jar_open', 'jar_close', 'jar_list', 'jar_search', 'jar_read_class',
|
|
394
|
+
'jar_read_file', 'jar_inspect_constant_pool', 'jar_edit_class_constants',
|
|
395
|
+
'jar_edit_constant_pool', 'jar_compile_java', 'jar_repack',
|
|
396
|
+
'jar_scaffold_mod', 'jar_diff', 'jar_restore_file',
|
|
397
|
+
];
|
|
398
|
+
break;
|
|
399
|
+
case 'godot':
|
|
400
|
+
toolGroups['Godot (PCK Analysis)'] = ['analyze_godot_pck'];
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
return JSON.stringify({ engine: engine || 'none', toolGroups }, null, 2);
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
];
|
|
407
|
+
}
|
|
408
|
+
function generateSearchTerms(objective) {
|
|
409
|
+
const lower = objective.toLowerCase();
|
|
410
|
+
const terms = [];
|
|
411
|
+
const mappings = {
|
|
412
|
+
health: ['Health', 'HP', 'HitPoint', 'HealthManager', 'PlayerHealth', 'maxHealth', 'currentHealth'],
|
|
413
|
+
damage: ['Damage', 'Attack', 'DamageManager', 'baseDamage', 'attackPower'],
|
|
414
|
+
speed: ['Speed', 'MoveSpeed', 'WalkSpeed', 'RunSpeed', 'PlayerMovement', 'moveSpeed'],
|
|
415
|
+
money: ['Money', 'Gold', 'Currency', 'Coins', 'Cash', 'Balance', 'Wallet'],
|
|
416
|
+
ammo: ['Ammo', 'Ammunition', 'Bullet', 'Magazine', 'maxAmmo', 'currentAmmo'],
|
|
417
|
+
inventory: ['Inventory', 'Item', 'ItemManager', 'Backpack', 'Storage'],
|
|
418
|
+
stamina: ['Stamina', 'Energy', 'Endurance', 'Fatigue'],
|
|
419
|
+
level: ['Level', 'Experience', 'XP', 'LevelUp', 'PlayerLevel'],
|
|
420
|
+
cooldown: ['Cooldown', 'Timer', 'CooldownManager', 'AbilityCooldown'],
|
|
421
|
+
};
|
|
422
|
+
for (const [key, values] of Object.entries(mappings)) {
|
|
423
|
+
if (lower.includes(key)) {
|
|
424
|
+
terms.push(...values);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// If no specific terms matched, try to extract keywords
|
|
428
|
+
if (terms.length === 0) {
|
|
429
|
+
const words = objective.split(/\s+/).filter(w => w.length > 3);
|
|
430
|
+
terms.push(...words.map(w => w.charAt(0).toUpperCase() + w.slice(1)));
|
|
431
|
+
}
|
|
432
|
+
return [...new Set(terms)];
|
|
433
|
+
}
|
|
434
|
+
//# sourceMappingURL=workflow-tools.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
2
|
+
export declare function setLogLevel(level: LogLevel): void;
|
|
3
|
+
export declare const logger: {
|
|
4
|
+
debug: (msg: string, data?: unknown) => void;
|
|
5
|
+
info: (msg: string, data?: unknown) => void;
|
|
6
|
+
warn: (msg: string, data?: unknown) => void;
|
|
7
|
+
error: (msg: string, data?: unknown) => void;
|
|
8
|
+
toolStart(tool: string, backend: string): void;
|
|
9
|
+
toolComplete(tool: string, backend: string, duration: number, success: boolean): void;
|
|
10
|
+
};
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { eventBus } from '../core/event-bus.js';
|
|
2
|
+
const LOG_LEVELS = {
|
|
3
|
+
debug: 0,
|
|
4
|
+
info: 1,
|
|
5
|
+
warn: 2,
|
|
6
|
+
error: 3,
|
|
7
|
+
};
|
|
8
|
+
let minLevel = 'info';
|
|
9
|
+
export function setLogLevel(level) {
|
|
10
|
+
minLevel = level;
|
|
11
|
+
}
|
|
12
|
+
function log(level, message, data) {
|
|
13
|
+
if (LOG_LEVELS[level] < LOG_LEVELS[minLevel])
|
|
14
|
+
return;
|
|
15
|
+
const entry = {
|
|
16
|
+
timestamp: new Date().toISOString(),
|
|
17
|
+
level,
|
|
18
|
+
message,
|
|
19
|
+
...(data !== undefined ? { data } : {}),
|
|
20
|
+
};
|
|
21
|
+
// Write to stderr (stdout is reserved for MCP stdio transport)
|
|
22
|
+
process.stderr.write(JSON.stringify(entry) + '\n');
|
|
23
|
+
}
|
|
24
|
+
export const logger = {
|
|
25
|
+
debug: (msg, data) => log('debug', msg, data),
|
|
26
|
+
info: (msg, data) => log('info', msg, data),
|
|
27
|
+
warn: (msg, data) => log('warn', msg, data),
|
|
28
|
+
error: (msg, data) => log('error', msg, data),
|
|
29
|
+
toolStart(tool, backend) {
|
|
30
|
+
log('info', `Tool call: ${tool} [${backend}]`);
|
|
31
|
+
eventBus.emit('tool:start', {
|
|
32
|
+
tool,
|
|
33
|
+
args: null,
|
|
34
|
+
timestamp: Date.now(),
|
|
35
|
+
backend,
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
toolComplete(tool, backend, duration, success) {
|
|
39
|
+
log(success ? 'info' : 'warn', `Tool ${success ? 'completed' : 'failed'}: ${tool} [${backend}] (${duration}ms)`);
|
|
40
|
+
eventBus.emit('tool:complete', {
|
|
41
|
+
tool,
|
|
42
|
+
result: '',
|
|
43
|
+
duration,
|
|
44
|
+
backend,
|
|
45
|
+
success,
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAIhD,MAAM,UAAU,GAA6B;IAC3C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,IAAI,QAAQ,GAAa,MAAM,CAAC;AAEhC,MAAM,UAAU,WAAW,CAAC,KAAe;IACzC,QAAQ,GAAG,KAAK,CAAC;AACnB,CAAC;AAED,SAAS,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,IAAc;IAC3D,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO;IAErD,MAAM,KAAK,GAAG;QACZ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,KAAK;QACL,OAAO;QACP,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxC,CAAC;IAEF,+DAA+D;IAC/D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,CAAC,GAAW,EAAE,IAAc,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;IAC/D,IAAI,EAAE,CAAC,GAAW,EAAE,IAAc,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IAC7D,IAAI,EAAE,CAAC,GAAW,EAAE,IAAc,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IAC7D,KAAK,EAAE,CAAC,GAAW,EAAE,IAAc,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;IAE/D,SAAS,CAAC,IAAY,EAAE,OAAe;QACrC,GAAG,CAAC,MAAM,EAAE,cAAc,IAAI,KAAK,OAAO,GAAG,CAAC,CAAC;QAC/C,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE;YAC1B,IAAI;YACJ,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,OAAO;SACR,CAAC,CAAC;IACL,CAAC;IAED,YAAY,CAAC,IAAY,EAAE,OAAe,EAAE,QAAgB,EAAE,OAAgB;QAC5E,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,KAAK,OAAO,MAAM,QAAQ,KAAK,CAAC,CAAC;QACjH,QAAQ,CAAC,IAAI,CAAC,eAAe,EAAE;YAC7B,IAAI;YACJ,MAAM,EAAE,EAAE;YACV,QAAQ;YACR,OAAO;YACP,OAAO;SACR,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a Windows path for consistent handling.
|
|
3
|
+
* Converts forward slashes to backslashes for Windows APIs,
|
|
4
|
+
* but keeps forward slashes for display/internal use.
|
|
5
|
+
*/
|
|
6
|
+
export declare function normalizePath(p: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Convert to Windows-native backslash path for subprocess calls.
|
|
9
|
+
*/
|
|
10
|
+
export declare function toWindowsPath(p: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Extract a human-readable game name from a path.
|
|
13
|
+
* Handles Steam-style paths like "C:/Steam/steamapps/common/GameName"
|
|
14
|
+
*/
|
|
15
|
+
export declare function extractGameName(gamePath: string): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { basename } from 'path';
|
|
2
|
+
/**
|
|
3
|
+
* Normalize a Windows path for consistent handling.
|
|
4
|
+
* Converts forward slashes to backslashes for Windows APIs,
|
|
5
|
+
* but keeps forward slashes for display/internal use.
|
|
6
|
+
*/
|
|
7
|
+
export function normalizePath(p) {
|
|
8
|
+
return p.replace(/\\/g, '/');
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Convert to Windows-native backslash path for subprocess calls.
|
|
12
|
+
*/
|
|
13
|
+
export function toWindowsPath(p) {
|
|
14
|
+
return p.replace(/\//g, '\\');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Extract a human-readable game name from a path.
|
|
18
|
+
* Handles Steam-style paths like "C:/Steam/steamapps/common/GameName"
|
|
19
|
+
*/
|
|
20
|
+
export function extractGameName(gamePath) {
|
|
21
|
+
const normalized = normalizePath(gamePath).replace(/\/+$/, '');
|
|
22
|
+
return basename(normalized);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=path-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../../src/utils/path-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAEhC;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,CAAS;IACrC,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,CAAS;IACrC,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC/D,OAAO,QAAQ,CAAC,UAAU,CAAC,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readdirSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { loadConfig } from '../core/config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Search configured Steam library paths for installed games.
|
|
6
|
+
*/
|
|
7
|
+
export function findSteamGames(filter) {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
const games = [];
|
|
10
|
+
for (const steamPath of config.steamPaths) {
|
|
11
|
+
if (!existsSync(steamPath))
|
|
12
|
+
continue;
|
|
13
|
+
try {
|
|
14
|
+
const entries = readdirSync(steamPath, { withFileTypes: true });
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
if (!entry.isDirectory())
|
|
17
|
+
continue;
|
|
18
|
+
if (filter && !entry.name.toLowerCase().includes(filter.toLowerCase()))
|
|
19
|
+
continue;
|
|
20
|
+
games.push({
|
|
21
|
+
name: entry.name,
|
|
22
|
+
path: join(steamPath, entry.name),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Skip inaccessible directories
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return games.sort((a, b) => a.name.localeCompare(b.name));
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=steam-finder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startWebServer(port: number): void;
|