9.6 KiB
Outreach: the Brave Fencer Musashi Archipelago world (P33 E2)
What this is (2026-09-07). The note Drew sends to the author of the BFM Archipelago world after the public flip, the facts behind every sentence in it, and what we do with the reply. Everything numeric below was read from the world's repository on 2026-09-07 (as data, X2) or derived from our own source and disc.
1. What Archipelago is, and what the BFM world is
Archipelago (archipelago.gg, ArchipelagoMW/Archipelago) is a multi-game multiworld randomizer: several players play
several different games at once; each game's items are shuffled into the item locations of all the games in the session,
so finding a chest in one game may send an item to another player's game. Each supported game is a world (an
.apworld plugin): the randomization logic (items, locations, regions, rules) plus a client that reads and writes the
running game's memory to detect checks and grant items. Console games are driven through an emulator connector; for
PlayStation that is BizHawk's Lua bridge, which exposes MAIN_RAM (the 2 MB of PS1 RAM, offset 0 = 0x80000000).
The BFM world — github.com/AegeusEvander/Brave-Fencer-Musashi-AP-World, author AegeusEvander, world version 0.8.1,
minimum_ap_version 0.6.7, BizHawk 2.7+ only, US and JP discs (a jp_version flag applies per-block deltas), no license
file, actively developed (commits 2026-05-20 → 2026-09-07), 2 stars. Randomization: which Bincho holds which NPC is
shuffled; the goal is rescuing every NPC; castle NPCs can sit in other players' worlds; options include Time Sanity and a
BP Sanity mode. Technically it is a RAM-driven world: client.py (5,522 lines) polls 342 distinct addresses, and since
v0.3.0 it needs no disc patch — it injects code at runtime: compiled MIPS blobs (patch/en/*c.bin, e.g. setTimec.bin,
fixBinchoIDc.bin, writeBinchoc.bin) written into RAM plus one-word *Hooks.bin jumps patched into the game's own
functions. That is exactly the surface a matching decompilation serves: every hook site and every polled address now has a
named function or variable behind it, in C.
Why they matter to us: client.py was our richest US RAM source in Phase 3 (memory-map §3.4–§3.6 and §4.2 cite it in
14 rows, region-tagged per G5); the README's Special-thanks section credits the world. PROJECT_CONTEXT.md's parking lot
asked for this contact at Gen2 ("their BFM world has unpublished US RAM research").
2. What we can hand them (derived today, each with its evidence)
Their datum (client.py v0.8.1, MAIN_RAM offsets) |
What our source says | Evidence |
|---|---|---|
0x078eba "#32 day of the week" |
Correct. The shared rollover function func_8014B084 does day = day + 1 (u16 at 0x078eac, wraps to 1 at 0x16E = 366) and dow = (dow + 1) % 7 (u8 at 0x078eba). Our memory map had listed the day counter as a u8 and "dow not yet separable" — corrected by this reading. |
src/shared/engine_core.h DEFINE_func_8014B084 (line ≈114752); memory-map §3.4 rows 0x80078EAC / 0x80078EBA |
max-BP patch: nop the addu $v0,$a1 at 0x14bcf8 |
Inside func_8014BCEC(a0, a1), 14 instructions: bp_max (u16 at 0x078eb6) += a1, clamped to 0x662. The nop removes the increase. |
engine_core.h line ≈115082; sig registry: the site falls in func_8014bcec in all 141 location-overlay images scanned |
scroll-cursor patch site 0x13f430 |
Inside func_8013F350 (490 ins) |
same scan, 141/141 |
town-ID check andi $v0,$s0,0x4000 at 0x15a7e4 |
Inside func_8015A3C8 (493 ins). No longer in client.py v0.8.1 (town/portal handling now writes portal-table entries via fix_town_id), which also retires our memory map's "JP delta +0x2A8 vs +0x33C" inconsistency as historic. |
same scan; grep 15a7e4 client.py = 0 hits |
Time-Sanity hook: 0x146280 (time_hook, JP +0x2D0) |
Inside func_80146128 (142 ins): a jump rewritten to the injected setTimec.bin routine |
sig scan |
entrance byte 0x18e096 (1 → 0 "to prevent softlock") |
Per-map code: seven location overlays carry code there, seven different functions — a byte inside an instruction immediate, not a shared body | sig scan (7 hits, 7 names) |
version check: SLUS-00726MUSASHI at 0x00ba94, fallbacks 0x009f9a, 0x072e02, then SLUS_007.26;1 at eight addresses, then JP SLPS_014.90;1 |
The game's own copy is the product-code string BASLUS-00726MUSASHI at 0x072dfc (EXE .data; disc Track 1 sector 222). So the 0x072e02 fallback reads from the string's 5th character and can never match on the US disc — harmless, the kernel-area check (a BIOS/emulator-dependent copy of the disc ID) hits first. This closes our memory-map open question #13. |
extracted/retail/SLUS_007.26 fileoff 0x635FC; the disc scan |
The three shared sites (func_8013F350, func_8014BCEC, func_8015A3C8) and the hook function func_80146128 are engine
bodies that are byte-identical in every location overlay — one C definition in src/shared/engine_core.h covers every map.
The scan behind the "141/141" rows: for each .run/sig.ov_*.jsonl, the function whose [addr, addr+nbytes) contains the
site (tools/… one-off in the S89 log; re-run from the registries, never from memory).
3. What we ask them
- US RAM research beyond
client.py— notes, spreadsheets, refuted addresses. We record each datum as a memory-map row with provenance (G5: source, region, confidencereporteduntil verified in Ghidra or live) and name our symbols after their findings where they are the discoverer. - Attribution consent — their repository has no license file. We already cite
client.pyaddresses as facts with attribution; the note asks whether that is fine and offers a link back (already in the README). - Nothing else. No request to change their code; the version-check offset is offered as information.
4. The message (Drew sends; GitHub issue on their repo, which has issues enabled)
Authorship rule (Drew, S89, 2026-09-07): written the way a developer writes an issue, short and plain, no code spans, no bullet walls, no "evidence" scaffolding. Rewritten from the §2 facts, not a model draft with the tells taken out (the permuter maintainer spotted the de-telled version at once; the dense backtick formatting was the likely giveaway). No AI acknowledgement. Edit freely.
Title: Decomp of BFM (US) is public, might be useful for the AP world
Body:
Hey, I've been working on a matching decompilation of Brave Fencer Musashi (USA) and it's public
now: https://github.com/Druthulu/BFM-decomp. The whole exe and all the streamed modules rebuild
byte for byte from C, so basically every address your client pokes at has a function or a
variable behind it now.
Your client.py was a big help early on for US RAM addresses (you're in the README credits), so a
few things I found going back through it against the source:
- day of week at 0x078eba is correct. The rollover function bumps the day counter (u16 at
0x078eac, wraps back to 1 at 366) and does dow = (dow + 1) % 7.
- the max BP patch at 0x14bcf8 is in a tiny 14 instruction function that does
bp_max (u16 at 0x078eb6) += arg and clamps at 0x662. Nopping the addu just kills the increase,
so that all checks out.
- the scroll cursor site (0x13f430), the old town id check (0x15a7e4) and your time sanity hook
(0x146280) are all in shared engine code that's identical in every location overlay, so one C
file covers all of them. The entrance byte at 0x18e096 is different per map though.
- minor: the exe's copy of the id string is actually "BASLUS-00726MUSASHI" starting at 0x072dfc,
so the fallback read at 0x072e02 starts 4 bytes into it and won't ever match on US. Doesn't
matter since the 0xba94 check hits first, just thought you'd want to know.
If you've got US RAM notes beyond what's in client.py I'd be glad to fold them in with credit, and
if you want any address resolved to its function just ask. Also wanted to check you're ok with us
citing client.py addresses in our memory map (the repo has no license so figured I'd ask).
Function names are still func_XXXXXXXX style for now, naming is the next big job.
Short form (Archipelago Discord, the game's channel, if preferred): "BFM (USA) has a full matching decomp now: https://github.com/Druthulu/BFM-decomp. Went through client.py against the source, your day of week address checks out, the max BP patch is in a 14 instruction function, every hook site has a named function now. Happy to look up any address."
Send only after the flip (the message links the public repository).
5. After the reply
- Every address or note they share → a memory-map row with
Source/Provenance= "AP-world (AegeusEvander), , " andConfidence=reported, promoted toverifiedonly by Ghidra or a live datapoint (R10/G5). Names they coined go intoconfig/symbols.us.txt(R15) and Ghidra viatools/ghidra_apply_symbols.sh. - If they decline attribution, the AP-sourced rows keep their facts (addresses are facts) but drop the citation text; the README thanks line stays unless they ask otherwise.
- Record the issue URL and the outcome in the phase log and in
docs/SETUP.md's P33 E2 section; if no reply within a month, note that and move on — the deliverable is the offer, not the answer (P9).
6. Related
docs/memory-map.md §3.4 (player state block), §4.2 (overlay-resident addresses, the patch sites), "Region deltas" and
"Address-base conversion" (their MAIN_RAM convention), open question #13 · README.md Special thanks · PROJECT_CONTEXT.md
parking lot ("Randomizer / Archipelago collaboration") · docs/decompme-preset.md (the other post-flip outreach, E1).