The FA server's own command names, read out of the binary¶
Date: 2026-08-06
Binary: DOC/COSMOS-RE/carve/COS-FA-SERV-E04_PROG.bin (231424 bytes = 115712 words)
Goal: learn what FA operations 0x01, 0x04 and 0x0D are, since no capture carries them.
Outcome: got their NAMES. Did NOT get their behaviour. Also found a 14th handler with no name.
1. What was established¶
The command-name table is plain ASCII, run together with no separators, at file word 100401. In table order, these are the server's own names:
| Code | Name in the binary | Our C# name |
|---|---|---|
0x01 |
File-entry-disconnect |
FileEntryDisconnect |
0x02 |
Reserve-file-entry |
ReserveFileEntry |
0x03 |
Release-file-entry |
ReleaseFileEntry |
0x04 |
Change-file-entry-id |
ChangeFileEntryId |
0x05 |
Open-file |
OpenFile |
0x06 |
Close-file |
CloseFile |
0x07 |
Set-block-size |
SetBlockSize |
0x08 |
Read-file |
ReadFile |
0x09 |
Write-file |
WriteFile |
0x0A |
Create-file |
CreateFile |
0x0B |
Delete-file |
DeleteFile |
0x0C |
SIII-special |
SiiiSpecial |
0x0D |
Device-function |
DeviceFunction |
Every C# name matches. Three of them were chosen before these strings were read, so this is a real check rather than a restatement.
2. How the addresses were anchored¶
Two independent anchors agree, which is the only reason the base is stated as fact:
- The
"FA-server"status text is at file word 99628. The analysis records it atBANK2::842c(= 33836). So the bank-2 base is99628 - 33836 = 65792file words. - Applying that base, the name table at word 100401 lands on
BANK2::8731- exactly where the analysis puts it, and a value not used to derive the base.
The handler address table sits immediately after the names, at file word 100493, big-endian, and
matches the transcription already in FaOperation.cs word for word:
idx 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1fb0 1fb0 1ead 1ecc 1edd 1eee 1eff 1f10 1f21 1f32 1f43 1f6c 1f7d 1f8e 1f9f 1fb0
Slots 0, 1 and 15 hold 1fb0, which is the padding value. That 0x01 shares it is the evidence
for the existing note that File-entry-disconnect is not dispatched down the normal path.
3z. RESOLVED - read this instead of sections 3, 3a, 3b and 4¶
Everything below section 2 was written while the 1ead row was still unread. Ghidra has now
disassembled it (the region needed FORCING - no analysis pass had touched it, which is exactly why
two earlier readings of it were wrong). The answer:
Every entry is the same 17-word stub, and it is a STRING DESCRIPTOR.
SAA <n> ; n = the command name's length MINUS ONE
SWAP CLD SA, DD
COPY SB, DT
AAT -0x76
AAT 0x3
STF -0x62,B
SAA <n> ; again
SWAP CLD SA, DD
LDT *<addr> ; the word at addr holds the ADDRESS of the name's ASCII text
LDX -0x7e,B
STF 0x6,X
LDF -0x62,B
STF 0x9,X
LDD *<addr+1>
JPL / JPL / JMP ; shared tails
So the row is a command-name table - length plus pointer for each name, which is what a parser
that matches a typed command looks like. It is not a wire-handler table. The real request
handlers are ordinary named functions at ram:2xxx..ram:4xxx.
It is also off by one against the names¶
Entry i serves name i-1. Two independent fields agree in all six checked cases:
| Stub | SAA |
Name pointer | Name it resolves to | Length |
|---|---|---|---|---|
1ead |
20 | 8731 |
File-entry-disconnect |
21 |
1edd |
17 | 8745 |
Release-file-entry |
18 |
1eee |
19 | - | Change-file-entry-id |
20 |
1f21 |
13 | 8762 |
Set-block-size |
14 |
1f8e |
11 | 877f |
SIII-special |
12 |
1f9f |
14 | 8785 |
Device-function |
15 |
SAA is length-1 every time - the usual PLANC upper-bound-of-a-0-based-array form.
Two of this document's own claims die here:
- "The operation code is the index into that table" - wrong by one.
- "Slot 14 has a handler and no name" (section 3) - dead. Slot 14 is
Device-function. There is no fourteenth operation. That "finding" was an artifact of the off-by-one.
The operation CODES are untouched: they come from captured traffic, never from this table.
The lead in section 3b is exhausted¶
Section 3b proposed following the per-command pointers in the hope they reached parameter
descriptors, which would have given the missing wire layouts. They reach the command's own name
text. That is a definite answer, not a stalled one: this route cannot produce a wire layout, so
0x01, 0x04 and 0x0D still need a capture.
Section 4's "the disassembly is nonsense" is also explained - the region simply had not been disassembled, in the flat carve or in Ghidra. Sections 3a and 3b's "uniform DATA records" reading was wrong for the same reason; the uniformity is real, but they are code.
3. NEW: slot 14 has a handler and no name (SUPERSEDED - see 3z)¶
Slot 14 holds 1f9f. That is distinct from the 1fb0 padding, so it is a real separate address -
but the name table stops at thirteen entries.
What this is, is UNKNOWN. It could be an operation ND did not name in this table, or a
default / error arm that happens to sit past the last command. It is deliberately NOT added to
FaOperation: a code with no name and no captured traffic is not an operation we can claim
exists. Recorded so the next person sees it rather than rediscovering it.
3a. CORRECTION, same day: those addresses are not code¶
Section 4 below records a failed attempt to disassemble 1ead..1f9f. The reason it failed is
that they are not instructions. With the Ghidra MCP back, cos-fa-serv-e04.prog is loaded
properly as an ND-100 :PROG with real segments (BANK1 0000-a895, BANK2 0000-c2ea), and that
settles it:
BANK2::1f21andBANK2::1eddare zero. The addresses are not bank-2 at all.ram:1ead(BANK1) holds data, and a hexdump shows fixed-size records that repeat: a block beginningf1 14 c8 69 cc 5e f6 8a f6 03 31 9e, then1f b0, then a near-identical block beginningf1 11 c8 69 cc 5e f6 8a f6 03 31 9e. Uniform records, with1fb0sitting inside them - not a function prologue.
So the run of FSB / FAD / FDV that nd100-dis produced was correct behaviour on its part: it
was decoding a data table. The stride-17 structure I found is real, and it is a record stride,
not a code layout. The description of this table as "a table of handler addresses", carried in
FaOperation.cs from the earlier analysis, is not supported by what the loaded image shows.
What the table actually is, is unresolved.
The real handlers are named functions, already carved¶
The FA operation handlers live at ram:2xxx..ram:4xxx as proper functions. Relevant here:
| Op | Handler | State |
|---|---|---|
0x04 Change-file-entry-id |
fa_change_file_entry_id @ ram:2e12 |
carved and commented |
0x01 File-entry-disconnect |
not found | no function named for it |
0x0D Device-function |
not found | no function named for it |
Operation 0x04, from the comment left by the earlier Ghidra pass (their words, not a fresh
reading of mine - flagged because I have not re-derived it):
Finds the entry via
fa_find_global_entry_by_key(0x28b2); requires it to be reserved (entry[+0xa]reservation bits set, else error0xd) and of type 8 (entry[+1]). Then, gated by a flag: either ACCUMULATES the request's 2-word value into the entry's sub-blockentry[+0xb+2](ADD), or REPLACES it. Finally writes the new id/attributes intoentry[+0xb]andentry[+0xb+1]and processes theentry[+5]chain to build the reply.
Note the decompiled body runs from 2e12 all the way to 315b, where fa_file_data_transfer
begins - so the function bounds look over-extended, and the tail of that listing probably belongs
to a neighbour. Treat the comment as the reliable part and the body as suspect until the bounds
are fixed.
Its only caller is fa_registry_helper_4056, NOT a dispatch table - which is another reason to
doubt that 1ead is a handler-address table.
This still does not give a wire layout. Knowing that 0x04 renumbers a reserved entry does not
say how the request is encoded, and 0x01 and 0x0D remain unidentified in the carve. They stay
refused.
3b. The record structure, measured¶
Each command's record is 17 words (Reserve is 31 and Create 41 - those two carry extra). Comparing
records across commands, only 7 of the 17 words differ; the other ten are byte-identical
everywhere. Measured on Open-file (1eee), Read-file (1f21), Device-function (1f8e) and
the unnamed slot 14 (1f9f):
| Word | Open | Read | Device-fn | slot 14 | |
|---|---|---|---|---|---|
| 0 | 13f1 |
0df1 |
0bf1 |
0ef1 |
varies |
| 1-5 | 69c8 5ecc 8af6 03f6 9e31 |
same | same | same | identical |
| 6 | 13f1 |
0df1 |
0bf1 |
0ef1 |
varies, = word 0 |
| 7 | 69c8 |
same | same | same | identical |
| 8 | 6250 |
3850 |
5150 |
4350 |
varies |
| 9-12 | 8259 0634 9e39 0934 |
same | same | same | identical |
| 13 | 5e28 |
3428 |
4d28 |
3f28 |
varies |
| 14 | ceba |
9bba |
43ba |
32ba |
varies |
| 15 | c4ba |
91ba |
b7ba |
a6ba |
varies |
| 16 | cdaa |
9aaa |
12a8 |
01a8 |
varies |
The varying words all have the shape of memory-reference instructions with a per-command
displacement in the low byte - i.e. the records point at something different per command, through
an identical frame. The per-command values are not the operation codes (Read is op 8 but carries
0d), so they are displacements or table indices, not the code itself.
Whether these records are executed or interpreted is UNRESOLVED. Against them being code: the
ten identical words decode as a fixed pattern of FSB / FDV / FAD floating-point instructions,
which no compiler emits as the common body of thirteen command routines. For them being code: the
uniform-frame-with-varying-displacement shape is exactly what generated per-command thunks look
like. Not settled, and not guessed.
Why this matters if anyone picks it up: if the varying words are pointers to per-command
parameter descriptors, following them for a command whose wire layout we already know
(Open-file, Read-file) would validate the reading, and following them for 0x01, 0x04 and
0x0D would then give those layouts. That is the one route from here to the missing encodings that
does not need a capture. It is a lead, not a result.
4. What FAILED, and why it is written down¶
The handler bodies were not read. The addresses (1ead .. 1f9f) are 16-bit, and the
image's code region does not disassemble into anything sensible at them under either offset tried:
K = 0(address = file word): produced runs ofFSB/FAD/FDV- floating-point instructions in a pattern no compiler emits.K = 256(the 256-word header the data region demonstrably uses, since the bank-2 base is65536 + 256): same character of output.
The documented code anchor fa_dispatch_by_type3bits = 0x08b1 decodes as noise under both.
One structural fact IS solid and survives: searching for words repeating at a stride of 17
finds a run at file word ~8140, and 8140 - 256 = 0x1ecc, the Release-file-entry address.
So the handlers really are a family of 17-word stubs and the K = 256 mapping really does point
at them - the addressing is right and the decoding is what fails. That points at the image
being other than a flat big-endian word dump in that region (byte order, or a container the
carve did not flatten), not at the mapping being wrong.
Next step is fixing the image, not more searching. Compare this carve against how the
:PROG container is laid out, or re-carve from COS-FA-SERV-E04.PROG with the loader's own
placement, then disassemble 1ead and check it against the known-good Read-file at 1f21
before trusting anything read from 1edd, 1f8e or 1f9f.
5. What this does not change¶
Nothing is now served that was not served before. 0x01, 0x04 and 0x0D remain refused, which
is still the correct answer: a name is not a wire layout. The value here is that the next attempt
starts from thirteen confirmed names, a verified table position, a proven bank-2 base, and a
written-down record of which mapping already failed.