Workspace rules, target build, release chain, and elevation: see ../CLAUDE.md.
- Never move JVM or mod code into the game process. The game is 32-bit; the JVM runs as a separate
java.exe. - Native game calls and memory access stay in the injected agent. The agent never talks to the JVM directly.
- Read
docs/PROTOCOL.mdbefore changingsrc/codec.rsorsrc/router.rs. Where it describes planned behavior, the implementation wins. - Never add a game address here.
gen/addr.jscomes from coderpack'stools/addr.py.
- Agent to host: Frida
send(). Host to agent:Script::post()with JSON. - Host and JVM: newline-terminated UTF-8 frames over two named pipes the host creates before starting the JVM, base
\\.\pipe\ancaria-<random>passed as--pipe.<base>.incarries frames to the JVM,<base>.outback. - Keep one pipe per direction. A synchronous handle serialises operations on its file object, so a parked
ReadFileblocks every write; a duplicated handle shares the file object. The symptom is one frame, then silence. - Keep the stdin/stdout fallback used without
--pipe. The test harnesses and manual driving depend on it. - JVM stdout and stderr are inherited and are the path for Java diagnostics.
jvm.rsreports an unreadable line as[coderpack] Could not parse Coderpack output: <line>.
ENDhas no name token.END 3 cancel=1once parsedcancel=1as a name and dropped every verdict silently. Keepcodec::tests::verdicts_keep_their_fields.- The
LOG <level> <text>form indocs/PROTOCOL.mdis invalid on the wire: both decoders require a decimal sequence as the second token of every non-BYEline. Specify and test one form on both sides before using wireLOG. EVTuses sequence0;ASKuses a session-local increasing counter; coderpack countsCMDseparately.- Send
ASKonly while the agent can still cancel or rewrite the game operation. - Percent-encode only
%, space,=, LF, CR. Keep fields flat.flattenturns a nested value into one JSON field with a warning; that is a protocol bug. - The router also accepts
BYEfrom coderpack and posts{"type":"mode","ask":false}. Keep this unless both sides and the spec change together.
- Nothing in
router.rsmay wait for JVM work or a command result. AnASKblocks the game thread in Frida'srecv().wait(). - Frida's callback converts agent messages and sends frames to the JVM writer channel.
- The JVM reader thread decodes frames and queues work for
pump. - Only
pumpowns theScriptand callsScript::post(). VERDICT_DEADLINEis 250 ms. The watchdog scans every 125 ms and marks an ask overdue only when its age exceeds 250 ms, so the fallback is queued roughly 250 to 375 ms after the ask, plus scheduler and poster-loop delay. Never describe 250 ms as a strict worst case.- An overdue ask gets an allow verdict with no rewrites and the log line
[host] Coderpack did not answer request <seq> in time. Continuing. The slow mod loses its veto. - When
pumpsees the JVM exit, it posts{"type":"mode","ask":false}and returns; the attach loop then fails withCoderpack exited. Nothing left to run. This is a shutdown guard, not an observe-only mode. - Backpressure lives in coderpack's bounded dispatch queue. The Rust
mpscchannels are unbounded. Router::droppedcounts failed sends to a disconnected JVM writer (any frame) and is never reported. Never treat it as coderpack's event-drop counter.
- Only the JVM child is assigned to a kill-on-close job; the game never is.
Jvm::spawnignores the result ofJob::adopt, so assignment can fail silently. Never claim job protection is unconditional.Job::createreturnsNoneon non-Windows systems and when creation fails; the host then warns and continues.
build.rsminifies the agent intoprotocol.exe;bundleassembles the injected script in memory. Nothing writes JavaScript to disk.- The agent source is the first of:
$PROTOCOL_AGENT(whatlauncher/tools/build.ps1passes); sibling../coderpack/agent/src; theagent.zipof the coderpack release pinned independencies.json, cached underbuild/agent/<version>/. - A sibling without
gen/addr.jsfalls back to the pinned release with acargo:warning.agent::origin()names the source and the host prints it at startup. - Keep
js::compactminimal: strip comments, indentation, blank lines, and repeated spaces only. Never rename, reorder, drop declarations, or join lines. All modules share one scope, so a "smarter" minifier silently loses code, and line breaks carry automatic semicolon insertion. Duplicate helper names overwrite silently; filename order decides the winner. --agent <path>reads an unminified folder. Keep it an explicit flag, never a search, so a staleagentfolder from an old install cannot win.--hooksprints the manifestbuild.rsread from the unminified sources and exits. The launcher relies on it.Configfinds the distribution directory byzygote.jarbeing in it. Never test directory existence;.always exists.
- Module names are agent file names without the numeric prefix.
core,bus, andnamesalways load, including under--only. Unknown arguments are errors. - Without
--java, the host triesjava/bin/java.exebesideprotocol.exe, then%JAVA_HOME%\bin\java.exe, thenjavaonPATH. version()readsVERSIONbesideprotocol.exeand reportsdevwhen it is missing.- After a crash several candidate processes can exist.
wait_for_gametakes the first match in name-preference order; check the printed name and PID. - Restart the game between hook-isolation attempts. The agent is not reliably unloaded when only the host exits, and a second host would hook patched code.
- Keep the explicit Frida teardown: unload the script, drop it, sleep 250 ms, then loop. Implicit drop while the target died faulted the host.
- A hook exception without
fileNamearrives asMessage::Other. Keeprouter::other, which extracts description and stack from the nested JSON.
- Needs the MSVC Rust toolchain and LLVM:
frida-sysruns bindgen, which needs libclang..cargo/config.tomlsetsLIBCLANG_PATHtoC:\Program Files\LLVM\binwithforce = false, so an existing variable wins. - Commands:
cargo build --release,cargo test --release,cargo run --example message_check. - The
LNK4098warning aboutLIBCMTis expected (frida-core's static CRT). Never change the CRT setup to silence it. - Folder-reading bundler tests use the
tests/agentfixture and never../coderpack. - The two
codec::endtoendtests pick the newestapiandzygotejars by build time, never by name, from../coderpack/<part>/build/libsor~/.m2/repository/dev/ancaria/coderpack/. Without both they printSkipped: No Coderpack JARs found in ../coderpack or ~/.m2and pass, so a Rust-only checkout needs no JDK. message_checkneedspythononPATHand no game.tools/version.ps1writesCargo.tomland the matchingCargo.lockentry.
- Upstream
frida0.17.2 casts callbackuser_datato the wrong handler type for non-frida:rpcmessages; the first agentsend()faults the host with0xC0000005.vendor/fridareads the real handler fromCallbackHandler::script_handler.examples/message_check.rsguards the fix. - Never edit
vendor/fridabeyond that fix. Return to crates.io only after upstream ships it.