Created
June 15, 2025 04:37
-
-
Save widberg/781bc7fa00c777a881e572f4b14ebf48 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // The Witness Console Command Runner Frida Agent | |
| // Usage: | |
| // launch the game with "frida -l "the_witness_console.js" -f witness64_d3d11.exe" | |
| // * Run print_commands() in the frida repl to print registered command names. (the help command | |
| // doesn't list them all) | |
| // * Run something like cmd("command argument0 argument1") to run commands. | |
| // Some commands I tried: | |
| // * The "go" command accepts marker names, run print_gotos() to print a list of marker names the | |
| // game knows about, they don't all have names. | |
| // * If you are clicked into a panel and drawing a line "solve_panel" will print out a shit tonne of | |
| // info. You can also add 1 to any of the values here | |
| // https://github.com/sigma144/witness-randomizer/blob/master/Source/Panels.h, converted to decimal | |
| // since the command parser doesn't like hex and use that as an argument. It doesn't actually change | |
| // the panel state. | |
| // * The "ls" command lists variable "directories", "cd" can be used to navigate them, "pwd" prints | |
| // the current working directory. | |
| // * "set" and "print" can poke and peak the variable names in the current working directory. | |
| // * These variables correspond to the ones in the All.variables.raw and Local.variables files. Note | |
| // that the game checks certain variables only on startup so changing them like this might not do | |
| // anything. | |
| // * The agent will print out text from the console. | |
| // Sorry, hardcoded values from steam latest 64-bit (sha256: 8d672d444df6a6df7130f25a517bdab1af92731fe2138dc5b324d519561d55a5) | |
| const PASSED_TO_COMMAND_STUFF_PTR = ptr(0x000000014062d4b8); | |
| const RUN_COMMAND_PTR = ptr(0x00000001402f69d0); | |
| const REGISTER_COMMAND_PTR = ptr(0x00000001402f58b0); | |
| const CONSOLE_LOG_PTR = ptr(0x0000000140072d40); | |
| const GLOBALS_PTR = ptr(0x0000000140652ee8); | |
| const REGISTER_COMMANDS_PTR = ptr(0x0000000140071b20); | |
| const CALLBACK_WALK_MONSTER_PTR = ptr(0x00000001402d7b50); // https://www.youtube.com/watch?v=YE8MVNMzpbo | |
| const WALK_MONSTER_COMMAND_NAME = "walk_monster"; | |
| const GOTO_STUFF_0_PTR = ptr(0x000000014062e700); | |
| const GOTO_STUFF_1_PTR = ptr(0x000000014062d0a0); | |
| const GET_GOTOS_VECTOR_PTR = ptr(0x00000001401875d0); | |
| function get_command_manager() { | |
| return GLOBALS_PTR.readPointer().add(0x10).readPointer(); | |
| } | |
| var run_command = new NativeFunction( | |
| RUN_COMMAND_PTR, | |
| "void", | |
| ["pointer", "pointer", "pointer", "pointer"], | |
| "win64" | |
| ); | |
| // Add a "cmd" function to the repl to run command strings | |
| globalThis.cmd = (s) => { | |
| var command_manager = get_command_manager(); | |
| var command_thing = PASSED_TO_COMMAND_STUFF_PTR.readPointer() | |
| .add(0x28) | |
| .readPointer(); | |
| var result_ptr = Memory.alloc(4); | |
| run_command( | |
| command_manager, | |
| command_thing, | |
| Memory.allocAnsiString(s), | |
| result_ptr | |
| ); | |
| // console.log("Command result:", result_ptr.readUInt()); | |
| }; | |
| // Log stuff that would have been printed out | |
| Interceptor.attach(CONSOLE_LOG_PTR, { | |
| onEnter(args) { | |
| console.log(args[0].readAnsiString()); | |
| }, | |
| }); | |
| // Log registered command | |
| Interceptor.attach(REGISTER_COMMAND_PTR, { | |
| onEnter(args) { | |
| globalThis.registered_commands = globalThis.registered_commands ?? []; | |
| globalThis.registered_commands.push(args[2].readAnsiString()); | |
| }, | |
| }); | |
| // Add a "print_commands" function to the repl to print registered commands | |
| globalThis.print_commands = () => { | |
| globalThis.registered_commands = globalThis.registered_commands ?? []; | |
| console.log(globalThis.registered_commands); | |
| }; | |
| var register_command = new NativeFunction( | |
| REGISTER_COMMAND_PTR, | |
| "void", | |
| ["pointer", "pointer", "pointer", "uint", "uint"], | |
| "win64" | |
| ); | |
| // Restore removed command | |
| Interceptor.attach(REGISTER_COMMANDS_PTR, { | |
| onLeave(retval) { | |
| var command_manager = get_command_manager(); | |
| register_command( | |
| command_manager, | |
| CALLBACK_WALK_MONSTER_PTR, | |
| Memory.allocAnsiString(WALK_MONSTER_COMMAND_NAME), | |
| 0, | |
| -1 | |
| ); | |
| globalThis.registered_commands = globalThis.registered_commands ?? []; | |
| globalThis.registered_commands.push(WALK_MONSTER_COMMAND_NAME); | |
| }, | |
| }); | |
| var get_gotos_vector = new NativeFunction( | |
| GET_GOTOS_VECTOR_PTR, | |
| "pointer", | |
| ["pointer", "pointer"], | |
| "win64" | |
| ); | |
| // Add a "print_gotos" function to the repl to print registered markers | |
| globalThis.print_gotos = () => { | |
| var gotos = get_gotos_vector( | |
| GOTO_STUFF_1_PTR.readPointer(), | |
| GOTO_STUFF_0_PTR | |
| ); | |
| var length = gotos.add(0x0).readUInt(); | |
| var entries = gotos.add(0x8).readPointer(); | |
| for (var i = 0; i < length; ++i) { | |
| var entry = entries.add(i * Process.pointerSize).readPointer(); | |
| var name = entry.add(0x58).readPointer().readAnsiString(); | |
| var gotoable = (entry.add(0x10).readU64() & 0x100000000) == 0; | |
| if (gotoable) { | |
| console.log(name); | |
| } | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment