- Implemented a Python script to extract data from the EUSECODE.FLX file format. - Defined data structures for candidate entries and extracted chunks using dataclasses. - Added functions to read and parse the FLX table, extract candidate data, and generate human-readable output files. - Included functionality for analyzing extracted data, including generating summaries, descriptors, and event family reports. - Implemented utilities for calculating printable ratios, zero ratios, and identifying text-like data. - Added support for writing various output formats, including JSON, TSV, and Markdown.
25 KiB
Raw 000e: Parser & RIFF/Animation Clusters
Content extracted from crusader_decompilation_notes.md. Covers the 000e: segment parser helper cluster and the RIFF/AVI animation streaming subsystem.
Raw 000e Parser Helper Cluster
A small helper cluster in the raw 000e: area implements a fixed-size CRLF record parser/table builder, likely used by startup/config or script-ish text data.
Newly renamed helpers
| Address | Name |
|---|---|
000e:345e |
record_table_init |
000e:34cc |
record_table_destroy |
000e:35c6 |
record_table_release_buffer |
000e:35ef |
record_table_next_slot |
000e:3639 |
record_table_parse_buffer |
000e:3798 |
record_parser_read_line |
000e:38a0 |
record_parser_seek_next_marker |
000e:38f8 |
record_parser_find_marker |
000e:39cc |
record_parser_dispatch_at_directive |
Behavior notes
record_table_initclears the table header and zeroes 300 words of inline storage.record_table_parse_bufferwalks a CRLF-separated text buffer, captures each line, splits around a marker helper path, and stores parsed entry state into0x0c-byte records.record_parser_read_lineadvances to the next CRLF-delimited line, rejects lines that start with@or with non-identifier punctuation, and terminates the line in-place with0.record_parser_seek_next_markerupdates the parser's current marker cursor at+0x18/+0x1aby callingrecord_parser_find_marker; returns1if another marker was found,0at end-of-data.record_parser_find_markerscans forward until an@marker or end-of-data; optionally consumes the remaining length from the parser state.record_parser_dispatch_at_directivereturns0unless the current substring begins with@; in the@case, it advances by 7 bytes and dispatches through a FAR thunk (0000:ffff).
EUSECODE.FLX extraction notes
USECODE/EUSECODE.FLXdoes not look like a loadable code image or plain text script. It is now validated as an indexed binary container.- Current table model:
- entry count at file offset
0x54 - entry table at
0x80 - 8-byte records:
<u32 data_offset, u32 declared_size> entry_count = 3074table_end = 0x6090, which matches the first non-zero payload offset403non-zero entries in the current file
- entry count at file offset
tools/extract_eusecode_flx.pynow parses the full validated table and emits all403non-zero entries underUSECODE/EUSECODE_extracted/, includingentry_index.tsv,descriptor_index.tsv,descriptor_neighborhoods.tsv,summary.json, per-chunk.bin, and.strings.txtsidecars.- The generated reports now expose lightweight descriptor summaries (
primary_label,field_names,field_tags) so the object lane can be searched by field grammar instead of only by raw names. - The extracted data now separates into at least two lanes:
- text-heavy records that fit the
000e:CRLF parser model, such asDATALINKmission/objective text andTEXTFIL1message banks - binary object/behavior descriptors whose sidecars expose object names and field names, such as
EVENT,NPCTRIG,CRUZTRIG,TRIGPAD,JELYHACK,JELYH2,SPECIAL,SURCAMNS, andSURCAMEW
- text-heavy records that fit the
- The descriptor lane also shows a repeatable tagged field trailer rather than raw trailing strings only. Current spot-checks show patterns like
69 xx 00 <name>and24 xx 02 <name>immediately before field names inNPCTRIG,CRUZTRIG,TRIGPAD,SPECIAL, andSFXTRIG. This is strong evidence that the field names belong to compact per-field metadata records, not accidental string leakage. - The strongest currently stable tag readings are:
69:0000 -> referent69:0A00 -> eventon event-capable classes such asEVENT,NPCTRIG,COR_BOOT,REE_BOOT,SFXTRIG,FLAMEBOX,NOSTRIL,VAR_BOOT, andSTEAMBOX24:FE02/24:FC02/24:FA02on object-reference-like fields such asitem,elev,door,source,dest,monster1,deadGuy, and related referent-style links24:0A02 -> eventTriggeronSURCAMNS/SURCAMEW
- The tag report is not a full type system yet, but it is already enough to separate scalar/event slots from pointer-like object links in many descriptor classes.
- Confirmed descriptor examples from the full index:
EVENT:referent,event,item,source,dest,door,counter,counter2,link,time,post1,post2,floor,flicManNPCTRIG:referent,event,item,item2,typeNpcCRUZTRIG:referent,item,elevTRIGPAD:referent,item,elevJELYHACK:referentJELYH2:referentSURCAMNS/SURCAMEW:referent,textFile,monit,valueBox,passcode,link,code,screen,cameraEgg,trueRef,therma,eventTrigger,foundGun
- Current immortality-lane status inside EUSECODE:
- the trigger/object namespace now clearly includes
JELYHACK,NPCTRIG,CRUZTRIG, andTRIGPAD JELYHACK/JELYH2sit in a local extraction neighborhood besideSPECIAL,TRIGPAD,DATALINK,HOFFMAN,REE_BOOT,SURCAMEW, andSFXTRIG, which looks more like a map/object grouping than random table order- that neighborhood does not make
JELYHACKitself event-bearing, but it does place it immediately beside multiple event-capable or trigger-adjacent classes (REE_BOOT,SFXTRIG,SURCAMEW.eventTrigger) - no extracted chunk has yet been tied directly to event
0x410 - one exact
0x410collision in compiled code is now explained away:000e:0953pushes0x410into importedASYLUM.27from the animation audio-subframe path immediately after setting the local audio-completion byte at+0xef1. SinceASYLUM.DLLis theASS_*audio/media library, treat this as a media ordinal/value collision rather than a second gameplay or USECODE event source. - the present best reading is that
0x410is likely carried by data relationships between generic event-capable descriptors (EVENT,NPCTRIG,SFXTRIG, etc.) and map/object references rather than by a plain-text script line
- the trigger/object namespace now clearly includes
- The
000e:record parser helpers still matter, but they now appear to cover only the text-oriented subset rather than the entire FLX payload. The strongest concrete caller so far is the raw window at000e:1b9f..1d49, whererecord_table_parse_bufferis invoked after setup of fields that match the known animation object layout (+0x117/+0x11b/+0x11f/+0x123,+0xeaf/+0xeb1,+0x10f/+0x111). That makes the currently verified000e:3639consumer part of the animation-object lane, not a clean standalone EUSECODE loader. - This shifts the current working model: treat
record_table_parse_bufferas a text/metadata helper used by at least one animation/resource object, while the EUSECODE binary descriptor lane is more likely consumed by the000dVM/object interpreter path. - That
000dpath is now materially less anonymous:- the global runtime object at
0x6611is now namedentity_vm_runtime_create/entity_vm_runtime_init_slots/entity_vm_runtime_release_slots/entity_vm_runtime_destroy - it owns the 0x80-entry slot table and a retained owner/resource object at
+0x1315/+0x1317 entity_vm_slot_index_from_entityandentity_vm_context_try_create_masked_for_entityshow that gameplay entities are filtered through one owner-side slot-mask table before a context is createdentity_vm_context_try_create_masked_for_entityis now better constrained too: after the owner-side mask check succeeds, an immediate-flagged context result clears the caller output word while an object-backed result returns the created object's low wordentity_vm_context_create_from_slot_indexthen seeds one0x6714context fromentity_vm_slot_load_value_plus_offset, while the large callers at000d:208band000d:21edcontinue by reading bytecode-like data from the seeded+0xd6/+0xd8lane
- the global runtime object at
- The context lane now also has a separate referent-registry subsystem:
entity_vm_set_field_da_to_globalwrites the current referent id to0x8c94from context field+0xdaand then enters the still-misaligned000c:3350bodyentity_vm_referent_registry_init/entity_vm_referent_registry_destroy/entity_vm_referent_registry_alloc/entity_vm_referent_registry_release_by_id/entity_vm_referent_registry_free_nodeshow that0x8c8c/0x8c8e/0x8c90/0x8c94implement one free-list-backed registry keyed by that current referent id- this is the first solid runtime mechanism showing how referent-only descriptors can still drive script state even when the actual event field lives in a separate neighboring descriptor
- the registry now also has a named chain container layer:
entity_vm_referent_chain_copy,entity_vm_referent_chain_append_unique_from,entity_vm_referent_chain_contains_entry,entity_vm_referent_chain_get_entry_data_at, andentity_vm_referent_chain_get_indirect_datashow that one referent can own copied/deduplicated payload chains with either inline fixed-size payloads or indirect string-like payloads
- That chain layer is now less one-sided than before:
entity_vm_referent_chain_remove_matching_from(000d:6a9a) removes entries from one chain when they match a second chain, using either inline compare or indirect string compare depending on the chain type byteentity_vm_referent_chain_set_entry_data_at(000d:6cf6) updates the payload of the Nth chain entry in place, freeing old indirect payload storage first when neededentity_vm_opcode_finish(000d:3350) is now identified as the common opcode epilogue that writes0x8c94from the current frame result and unwinds the temporary slot-array state before returning the opcode result
- That makes the emerging human-readable script model less ad hoc. A plausible future IR is now:
referent anchor -> payload chain(s) -> event-bearing attachment(s)rather than a flat list of isolated descriptor rows. - The opcode side now reinforces that IR too: at least one handler family around
000d:0988can either append unique payload entries or remove matching ones before returning through the same epilogue, which is a better fit for a graph-editing/object-attachment VM than for a pure linear trigger list. - That
000d:0988family is now classified more tightly at the opcode-id level:- opcode
0x19= append unique indirect/string-like payload entries into the referent chain - opcode
0x1a= remove matching indirect/string-like payload entries from the referent chain - opcode
0x1b= remove matching inline/fixed-size payload entries from the referent chain - the same helper body also implies the missing sibling
0x18as the inline/fixed-size append-unique form, because only0x19/0x1aset the indirect compare flag while only0x1a/0x1btake the removal path
- opcode
- The first concrete
000cto000dbridge inside that lane remainsentity_vm_set_value_from_slot_plus_offsetat000c:f95f: it callsentity_vm_slot_load_value_plus_offset, stores its return pair into object fields+0xd6/+0xd8, and sits immediately beside otherentity_vm_*helpers in the000c:f6b8..f9d9mini-VM cluster. On the000dside,entity_vm_slot_load_value_plus_offsetwrapsentity_vm_slot_load_value, andentity_vm_slot_load_valuecontains a concretePUSH 0x410event-emission path at000d:5290. - The two main
000dcaller blocks beneath that bridge now have a first stable byte/value reading too:- internal block
000d:208bis the simple materialize-or-forward path: it creates one VM context from the caller's stream state, checks the returned object flags, and either writes the returned value pair straight to the caller output slot or forwards the created object's low word through the shared opcode epilogue - internal block
000d:21edis the inline-payload path: it creates the same VM context, prepends the caller-owned blob into the backward-growing context buffer at+0x102, then consumes two bytes from the seeded+0xd6/+0xd8lane as small shape/count metadata before building anentity_linkclosure matrix from the following caller-stream words and pushing back the non-0x0400results - that is the first concrete evidence that the
+0xd6/+0xd8lane is not only carrying immediate event/value ids; it also carries compact metadata bytes that parameterize larger inline payloads copied from the caller stream
- internal block
- Current JELYHACK implication: because
JELYHACKandJELYH2still expose onlyreferent, the most defensible model is now that they provide map/object identity into the referent-registry lane, while one adjacent event-capable record (REE_BOOT,SURCAMEW.eventTrigger,SFXTRIG.event, or another nearby genericEVENT/NPCTRIG) carries the actual event semantics that can eventually reach0x410. - The immediate runtime-owner writer is now pinned down one step further too.
entity_vm_runtime_create(000d:4c99) is the only verified writer of runtime+0x1315/+0x1317, and it does so by calling newly recoveredentity_vm_runtime_owner_resource_create(000d:7000). That helper does not simply copy a caller-supplied owner table: it constructs one embedded seg069/070 helper object, queries the needed table size through vtable+0x04, allocates child+0x10/+0x12, then fills the0x0d-stride per-slot producer records through vtable+0x0c. The paired release path isentity_vm_runtime_owner_resource_destroy(000d:70fd). - That narrows the owner/resource classification safely but still stops short of speculative source-format naming. The embedded helper goes through the same seg069/070 object lifecycle used by other file/resource-style helpers (
0009:1c00init,0009:1800destroy), so the most defensible current description is stillruntime owner/resource helperrather thanUSECODE file loaderor a descriptor-specific name. - The first gameplay-side mask families around
entity_vm_context_try_create_masked_for_entityare also now explicit from instruction evidence:- local wrapper
0004:f033passes slot mask0x8000:0007 FUN_0004_f05cpasses slot mask0x2000:0015and is reached from0004:f2b3after overlap/proximity checks plus entity byte+0x32state togglingFUN_0005_27a4passes slot mask0x0001:0000and is reached from the000c:a09eentity+0x5bbit-0x0004branch
- local wrapper
- Those masks are enough to prove that the runtime is exposing multiple gameplay-side materialization lanes into the same owner/resource table, but they are not yet enough to tie one lane specifically to the
JELYHACK/JELYH2anchor pair instead of the neighboring event-bearing descriptors (REE_BOOT,SURCAMEW,SFXTRIG, or another local trigger record). - The extractor now emits a first graph-oriented view of that claim too:
referent_anchor_event_graph.tsvgroups referent-bearing rows with nearby event-bearing neighbors, andjelyhack_island_graph.mdrenders theJELYHACK/JELYH2island as edges to local descriptors. On the current data, the strongest event-bearing neighbors in that island areREE_BOOT(event),SURCAMEW(eventTrigger), andSFXTRIG(event). - The new focused comparison report (
jelyhack_descriptor_compare.tsv) makes one more structural point explicit:JELYHACKandJELYH2have identical first 16 header words and the same lonereferentfield tag, while differing only in the label string and one small trailingwx[...]literal. That strengthens the reading that they are sibling referent-anchor classes rather than separate event-bearing behavior records. - The same comparison also helps separate anchor classes from event-bearing neighbors:
REE_BOOT,SURCAMEW, andSFXTRIGall carry materially richer header/state patterns thanJELYHACK/JELYH2, which is consistent with them holding actual trigger or attachment semantics beside the anchor-only classes. - The
000d:21edcallee chain is now tighter too. The nested call at0008:7d27isentity_link, which appends one entity id into another entity's word-list and, unless bit0x0400is set, also updates the reciprocal pair-link slots. So the22bc..2433opcode block is best understood as building a bidirectional entity-link closure matrix from streamed entity ids, not merely copying opaque words around. - Ghidra now carries that interpretation as a conservative disassembly comment at
000d:22bc, but not yet as a symbol rename, because the surrounding000d:208b/21ed/22bcregion is still mis-split into artificial function bodies. - The new
EVENT-focused reports (event_island_graph.md,event_descriptor_compare.tsv) broaden the descriptor-side picture beyond the JELYHACK anchor case. The strongest second island is the compact local cluster at indices186..195, whereCOR_BOOT,EVENT, andNPCTRIGall expose explicit69:0A00 -> eventtags whileROLL_NS,CRUZTRIG,NPC_ONLY, andVMAILstay on the referent/link/text side. - That cluster looks structurally different from JELYHACK in a useful way:
EVENTis the large hub payload (0x20AA) carryingsource,dest,door,link,time,counter,post1,post2,floor, andflicMan, whileCOR_BOOTandNPCTRIGare smaller event-bearing satellites and the surrounding records (ROLL_NS,CRUZTRIG,NPC_ONLY,VMAIL) look like attached state/trigger/object descriptors rather than alternate event cores. - The first compare pass on that island is already informative.
COR_BOOT,EVENT,CRUZTRIG,NPC_ONLY, andVMAILshare the same leading0x00000000dword class shape,NPCTRIGmoves to a nearby0x00000001shape, andROLL_NSis the obvious outlier with first dword0x00000002plus rider/time/cargo fields. So the present best reading is one three-node event-bearing core embedded inside a wider referent-neighbor island, not one flat run of equivalent trigger records. - The extractor now also emits a global event-family pass (
event_family_index.tsv,event_family_summary.md), which turns the local island findings into a wider descriptor taxonomy. Current validated families are:event-hub:EVENTboot-event-core:AND_BOOT,BRO_BOOT,COR_BOOT,VAR_BOOT,REE_BOOTnpc-trigger:NPCTRIGminimal-event-core:SFXTRIGenvironmental-event:FLAMEBOX,NOSTRIL,STEAMBOXcallback-eventtrigger:SURCAMNS,SURCAMEW
- That split matters because it is the first extractor-backed distinction between active event carriers and callback-only trigger holders. The
69:0A00 -> eventclasses now look like the active event-bearing core of the descriptor system, while the surveillance classes with24:0A02 -> eventTriggerare better treated as callback/attachment endpoints rather than peer event hubs. - The next focused pass tightened the
_BOOTlane too.boot_family_compare.tsvnow shows that all five_BOOTevent cores (AND_BOOT,BRO_BOOT,COR_BOOT,VAR_BOOT,REE_BOOT) share the same header skeleton and the same compact field shape (referent,event,counter,item). The meaningful differences are payload size and local neighborhood, not descriptor schema. - The new
boot_frontier_graph.mdmakes the best early_BOOTfrontier explicit:AND_BOOTandBRO_BOOTsit in one compact referent-heavy neighborhood (OFFWORK,GUARD,GDOOR_N,GDOOR_E,BIGCAN,CRUMORPH,GUARDSQ,CARD_NS,CARD_EW,EWALLEW/EWALLNS) and also point directly at each other as adjacent event-bearing siblings. So the present best reading is a reusable boot-event core template instantiated in several different local object islands, not a set of unrelated boot scripts. - The environmental hazard lane is now similarly constrained.
environmental_family_compare.tsvshows thatFLAMEBOXandSTEAMBOXare close structural siblings with the same active-event backbone (referent,event,<hazard>,<hazard2>,direction,count) and matching24:0A02 / 24:FC02 / 24:FE02object-link pattern, whileNOSTRILis a smaller fire-specific variant that keeps the activeeventplus dual fire references and count fields but drops the direction/newType side. - Their neighborhoods are different enough to matter:
environmental_event_graph.mdshowsFLAMEBOXembedded among vent/door/bridge/copy records,NOSTRILamong flame/pad/desk/blaster/keypad records, andSTEAMBOXamong bounce/hover/fade/steam/flame box records. So this looks like one hazard-event descriptor family reused across distinct local object islands rather than one single environmental mega-cluster. - The callback lane is tighter too.
callback_trigger_compare.tsvconfirms thatSURCAMNSandSURCAMEWare effectively the same callback-trigger template: identical field set (referent,textFile,monit,valueBox,passcode,link,code,screen,cameraEgg,trueRef,therma,eventTrigger,foundGun) and identical tag grammar except for thethermaslot offset (24:F102vs24:F602). That keeps theeventTriggersplit credible as a true callback/attachment lane rather than only a spelling variation on activeeventcarriers. - The first runtime-side follow-through on those descriptor gains is now a little tighter too. Instruction search around
000d:ebe3confirms one fixed sequenced VM/opcode driver body, not just a vague constructor helper: it calls000d:177c,000d:1acb,000d:0988, the internal000d:22bclink-matrix block, then000d:1d4aand000d:2104in order. The key negative result is just as useful:000d:ec31is only the internalCALL 000d:22bcsite inside that body, not a standalone function entry. - Ghidra now carries that as a conservative disassembly comment at
000d:ebe3. That is still short of a safe rename, but it does promote the lane from “suspected constructor chain” to “verified ordered opcode/handler sequence,” which is the clearest current bridge from the descriptor-side event families back into the000dVM/object runtime.
Raw 000e RIFF/Animation Cluster
The 000e: segment contains a RIFF/AVI streaming animation subsystem.
Animation object field map
Field offsets relative to the object base pointer:
| Offset | Field |
|---|---|
+0xb0 |
active/valid flag |
+0xb4–+0xc2 |
constructor-initialized flags |
+0xd4 |
alive sentinel (must be -1 for "alive") |
+0xe4 |
paused flag (0 = running) |
+0xeaf/+0xeb1 |
far pointer to current RIFF chunk |
+0xedb |
animation frame stack depth counter (max 9) |
+0xee1 |
frame data from current chunk +4 |
+0xeef |
current subframe index |
+0x1b3 |
subframe count |
+0xef1 |
audio completion flag |
+0x11b |
ring buffer write pointer |
+0x11f |
ring buffer read pointer |
+0x117 |
ring buffer base |
+0x123 |
ring buffer end (capacity boundary) |
+0x102 |
resource pointer |
+0xde |
entry index (multiplied by 0x30 to reach per-entry data at +0x1c7) |
RIFF format notes
The game uses standard RIFF/IFF:
- LIST magic:
0x5453494c="LIST" - RIFF magic:
0x46464952="RIFF" "movi"FourCC subchunk for animation frames- Audio frames tagged
"01wb"(0x62773130) - Video frames handled through a separate path
Newly renamed functions
| Address | Name | Evidence |
|---|---|---|
000e:2a28 |
riff_find_chunk_by_type |
Walks RIFF LIST/RIFF chunk list; compares each node's FourCC at +8 vs param_2; returns pointer to matching chunk or NULL |
000e:2104 |
animation_start |
Finds "movi" chunk via riff_find_chunk_by_type, inits ring buffer ptrs at +0x11b from +0x117 + duration, calls animation_advance_frame, loops anim_load_audio_frame and a second frame-loader thunk path per subframe |
000e:12f4 |
animation_advance_frame |
Fixed-point 0x1000 timer arithmetic; checks +0xe4 (paused), advances ring buffer +0x11b/+0x11f/+0x117/+0x123; calls advance thunk |
000e:103f |
animation_tick |
Guard wrapper: checks param_1+0xd4 != -1, then calls animation_advance_frame(param_1, 0) |
000e:06f7 |
anim_load_audio_frame |
Checks chunk tag == 0x62773130 ("01wb" = audio stream 1); computes ring buffer free space; copies chunk payload via 0x0000:ffff thunk; increments subframe index at +0xeef; resets at subframe count +0x1b3 |
000e:053d |
anim_load_video_frame_wrapper |
Called once per subframe in animation_start immediately after anim_load_audio_frame; thin wrapper that forwards to 000e:ffb0 |
Unresolved callee
000e:ffb0remains unresolved (decompiles garbled due to overlapping instructions at000f:0085/000f:0086). Current evidence from theanimation_startloop suggests this path is the video-side subframe loader paired withanim_load_audio_frame.
Constructor pattern
All three constructor variants (000e:2777, 000e:2860, 000e:2969) follow the same layout:
- Call
FUN_000e_e935(allocator — produces garbled 11KB decompile, not renamed) - Set fields
+0xb4through+0xc2on the result - Call
000d:ebe3(multi-step chain initializer: calls177c,1acb,0988,22bc,1d4a,2104in sequence) - Call
assert_alive_sentinel(assertion: checks+0xd4 != -1) - Call
func_0x000eec83
The chain at 000d:ebe3 steps through VM opcode handlers (000d:177c, 000d:1acb, 000d:0988) that operate on a bytecode VM object with stack pointer at +0xcc (decremented by 2 per push) and segment base at +0xce.
Constructor variant renames
| Address | Name |
|---|---|
000e:223d |
assert_alive_sentinel |
000e:2777 |
animation_ctor_variant_a |
000e:2860 |
animation_ctor_variant_b |
000e:2969 |
animation_ctor_variant_c |