diff --git a/AGENTS.md b/AGENTS.md index aa17147e..b2a3cccc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,10 +11,6 @@ primary evidence. The server binaries are **C++** (MSVC 2010, x64). See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the evidence and what is known about their structure. -All servers share one in-house framework (`Magi*`): server-kind enum, -startup modes, XML option files and DB access via DBC servers. See -[`docs/SHARED_FRAMEWORK.md`](docs/SHARED_FRAMEWORK.md) and check each new -binary against it instead of re-deriving it. ## Target runtime environment @@ -66,35 +62,19 @@ other licence-restricted vendor files. ## Analysis workflow -The full reproducible procedure, with commands, is in -[`docs/WORKFLOW.md`](docs/WORKFLOW.md). Follow it for every binary. In summary: - 1. Route the target first: native executables/libraries go through REA `open_binary` (never pass a Ghidra project file as the target — pass the original binary path under `binaries/`). 2. Work summary-first: `binary_overview` / summary detail before drilling into `analyze_function` or pseudocode. Reuse retained Evidence IDs instead of re-running identical analysis. -3. **Third-party libraries: every time one is discovered** (an imported DLL, a - version string, RTTI class prefix, library error strings, or a PDB path), - do the following before continuing analysis: - - pin its version; - - **search online for its source or headers** (official releases, git tags, - vendor repos, clean-room equivalents); - - download it into `third-party/`, or into the gitignored - `third-party/_local/` if its licence forbids redistribution; - - record it in `third-party/README.md`. - - If no source exists, record that and the acquisition routes. See - `docs/WORKFLOW.md` §3. -4. Export raw decompilation artifacts (pseudocode, listings) into `decomp/` +3. Export raw decompilation artifacts (pseudocode, listings) into `decomp/` using per-binary subdirectories, e.g. `decomp/GameServer_Release_USA_x64/`. Keep filenames stable and function-address-based so results are reproducible. - For full coverage, use the headless scripts in `decomp/scripts/`. -5. Track targets, artifact SHA-256 identities, and per-binary status in +4. Track targets, artifact SHA-256 identities, and per-binary status in `decomp/INVENTORY.md`; keep a per-binary finding ledger in `decomp//evidence.md`. -6. Close the REA session (`close_binary`) when the investigation is complete. +5. Close the REA session (`close_binary`) when the investigation is complete. ## Evidence and claims diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 58ca3558..8eb182d8 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -3,9 +3,6 @@ What is known about how the shipped server binaries are built and structured. Each claim is marked **observed**, **inference**, or **unknown** (see AGENTS.md). -Shared framework (config files, server kinds, DB access path): see -[`SHARED_FRAMEWORK.md`](SHARED_FRAMEWORK.md). - ## Implementation language: C++ All 16 server executables in `binaries/` are written in **C++**, compiled with diff --git a/docs/SHARED_FRAMEWORK.md b/docs/SHARED_FRAMEWORK.md deleted file mode 100644 index e664a335..00000000 --- a/docs/SHARED_FRAMEWORK.md +++ /dev/null @@ -1,251 +0,0 @@ -# Shared server framework ("Magi") — configuration and database access - -The server EXEs share one in-house framework. Its classes are named `Magi*`, -and the network sessions are `MG*Session`. This document defines the parts -every binary shares: -- server identity; -- startup modes; -- configuration files; -- the path to the database. - -Analyse each new binary against this document, and record in its own -`evidence.md` where it differs, rather than re-deriving the framework. - -Status: first pass. **Primary evidence:** `MessageServer_Release_x64.exe` -(`decomp/MessageServer_Release_x64/`). **Cross-binary checks:** strings and RTTI -only, unless a row says otherwise. Field offsets are from MessageServer. Other -binaries share the same code, so they are *expected* to match, but this hasn't -been verified. Every claim is static; nothing here was observed at runtime. - -Notation: **O** = observed in decompiled code, **S** = observed in -strings/RTTI only, **I** = inference, **?** = unknown. `MS:0x…` means a -function in `decomp/MessageServer_Release_x64/full/functions/0x….c`. - ---- - -## 1. Framework presence across binaries (S) - -| Marker | Present in | -|---|---| -| RTTI `MagiServerOption`, `MagiCustomOption`, `MagiServerFile`, `MagiServerManager` | all 14 server EXEs. The two SMMonitoring EXEs only have `MagiServerManager`. | -| Option-loader keys (`write_thread_count`, `each_option`), the `-AGNET MODE-` banner [sic], and the `DBC_CHAT`/`DBC_EMERGENCY` names | all 14 server EXEs; none in SMMonitoring | -| `Servers2DbcProxy` RTTI (client of the DBC servers) | Auction, Dungeon, GameGateway, Game, GM, Guild, Manager, Message, PVP | -| ADO / `_Connection` strings (direct SQL access) | DBCServer and DBCServer_LOG (4 hits each); 1–2 hits in Auction, Dungeon, Game, Guild, Manager and PVP (**?** — may be incidental) | - -**Inference:** only the DBC servers talk to SQL Server. Every other server -reaches the database through ProudNet RMI to a DBC server (§5). This needs -confirming by decompiling the ADO usage in the six non-DBC servers that have -hits. - -## 2. Server-kind enum (O, `MS:0x140002930`) - -The one-byte server type is stored in the option object (`+0x1e8`) and in -peer/session records. Names below are the wide strings the binary prints. - -| Value | Name | Value | Name | -|---|---|---|---| -| 1 | Lobby | 0x0c | DBC_GAME | -| 2 | Dungeon | 0x0d | Agent | -| 3 | PVP | 0x11 | DBC_LOG | -| 4 | WAR | 0x12 | DBC_CHAT | -| 5 | Guild | 0x13 | DBC_EMERGENCY | -| 6 | Message | 0x15 | Agentmanager | -| 7 | GM | 0x16 | MiniGame | -| 8 | Gate | 0x17 | `api` peer (no display name; `MS:0x14005dc20`) | -| 9 | Manager | 0x0e–0x10, 0x14 | ? | -| 0x0a | Auction | | | -| 0x0b | Match | | | - -Separately, servers also have a numeric **code** (the instance ID). It appears -in XML as `code="61"` and in file names such as `MSG_61.xml`. In the shipped -deployment config every kind has one code: DBC_GAME=1, DBC_LOG=2, DBC_CHAT=3, -DBC_EMERGENCY=4, Msg=61, GM=71, Gate=81, Manager=91, Auction=101, Match=111, -Agent=800, AgentManager=900, Dungeon=2001, PvP=3003, Guild=5001, Lobby=21001 -(S, `binaries/Option/*.xml`). - -## 3. Startup modes (O, `MS:0x14005c7b0`) - -`MagiServerOption::Init(argv1)` records the PID and the start time -(`+0x1ec`, `+0x1f0`), then branches on the first character of the argument: - -| `argv[1]` | Mode | What is loaded | -|---|---|---| -| does not start with a digit | **standalone** | `argv[1]` is a path to a per-server XML file, which `MS:0x14005dc20` parses (§4.1) | -| starts with a digit | **agent mode** (prints `-AGNET MODE-`) | `atoi(argv1)` is this server's **code**. `MS:0x14005ceb0` reads `option\total_option.xml` (§4.3). The full server list then arrives from the Agent over RMI: the `MGSAgentSession` vftable slot 7 is `MS:0x140065ab0`. The entry whose code equals our code configures this server (`MS:0x14005c9f0`); every other entry is added as a peer (`MS:0x14005ff50`). | - -This matches the Agent side: the Agent launches a server with the numeric -server code as its argument (`decomp/Agent_Release_x64/evidence.md` finding 3). - -**ServerInfo record** pushed by the Agent (O, copy constructor `MS:0x140065900`; offsets in bytes): - -| Off | Type | Field (I) | -|---|---|---| -| +0x00 | int | world | -| +0x04 | int | code | -| +0x08 | int | ? | -| +0x0c | u8 | server kind (§2) | -| +0x10 | int | private_port | -| +0x14 | int | public_port | -| +0x18 | int | public_udpport | -| +0x1c | int | udp_count | -| +0x20 | u8 | ? | -| +0x28 | `std::wstring` | private_ip | -| +0x50 | `std::wstring` | public_ip | -| +0x78 | `std::wstring` | name | - -## 4. Configuration files - -All of them are XML parsed with TinyXML 2.5.x (`third-party/tinyxml-2.5.3`). -Paths are relative to the working directory, under `option\`. A sample -deployment set ships in `binaries/Option/`. - -That sample set contains placeholders such as `YOUR_WAN_IP`, `DB_USER` and -`DB_SERVER`. It is probably a community deployment, not the original publisher -files (I). It also contains **plaintext DB passwords** — never copy them into -docs. - -### 4.1 Per-server file — `MagiServerOption` (O, `MS:0x14005dc20`) - -Default name per binary: `option\_serveroption.xml`. In MessageServer -this is the global `L"option\\msg_serveroption.xml"` (`MS:0x14012e5c0`). -Per-instance copies are `_.xml`, e.g. `MSG_61.xml`. - -Top-level elements, the attributes read from each, and where the value is -stored in the option object: - -| Element | Attribute → field | -|---|---| -| `server` | `name` → +0x08 wstr · `code` → +0x154 · `private_ip` → +0x30 wstr · `private_port` → +0x80 · `public_ip` → +0x58 wstr · `public_port` → +0x84 · `public_udpport` → +0x88 · `udp_count` → +0x8c · `thread_num` → +0x15c (clamped ≥1; defaults to 1 if missing) · `agent` → +0x150 (bool, `=="1"`) · `debug` → +0x165 (bool) · `world` → +0x140 · `private_dns` / `public_dns` → override +0x30 / +0x58 (I: hostname form) | -| `db_account`, `db_web`, `db_game`, `db_game_log` | `string` → +0x90 / +0xb8 / +0xe0 / +0x108 (std::string, ADO connection string) · `timeout` → +0x130 / +0x134 / +0x138 / +0x13c (`MS:0x14005fa80`) | -| `dbc` | `write_thread_count` → +0x14c | -| `db_cache` | `session_count` → +0x144 (also copied to +0x148) | -| `db_log_cache` | `session_count` → +0x148 | -| peer elements (table below) | `ip`, `port` → peer map at +0x1a8, keyed by kind (`MS:0x14005fb60`) | -| `dns` | `dns` → +0x151 (bool) | -| `setting` | `multi` → +0x166 (bool) · `lock` → +0x168 (double) | -| `auto` | `id`/`value` → +0x158 (auto-start flag) | -| `start_server` / `revive_server` / `open_channel` | → +0x178 (double) / +0x171 (bool) / +0x170 (bool). The element-to-field pairing is from the order of assignments: **I** | -| `send` | `name`, `minlev`, `maxlev`, `group`, `type`, `id` → ? (not yet traced) | - -Peer elements parsed by MessageServer, with the server kind each maps to: - -| Element | Kind | -|---|---| -| `agentmanager` | 0x15 | -| `db_cache` | 0x0c | -| `db_log_cache` | 0x11 | -| `msg_server` | 6 | -| `gm_server` | 7 | -| `manager_server` | 9 | -| `auction_server` | 0x0a | -| `match_server` | 0x0b | -| `agent_server` | 0x0d | -| `api` | 0x17 | - -`gate_server`, `dungeon_server`, `pvp_server`, `guild_server` and -`lobby_server` appear in the shipped `msg_serveroption.xml`, but -**MessageServer's loader never reads them** (O: no string compare for them). -Other binaries are expected to parse a different subset (I). - -Mapping the `db_*` connection strings to schema files in `schema/`, by their -`Initial Catalog` (S, sample config): - -| Element | Database | -|---|---| -| `db_account`, `db_web` | `RustyHearts_Auth` (`server_info.ini` points Web at `RustyHearts_Account` instead) | -| `db_game` | `RustyHearts` | -| `db_game_log` | `RustyHearts_Log` | - -### 4.2 `option\customoption.xml` — `MagiCustomOption` (O, ctor `MS:0x140062820`, parser `MS:0x140062910`) - -The constructor initialises 16 boolean flags to 1 (`+0x08..+0x17`), then -parses the file: -- **`` section:** `log_account`, `log_item`, `log_pet`, `log_loginout`, - `agentmanagers`, `agent`, `api`, `pvp`, `serveroption` toggle per-category - console logging. The parser prints `LOGVIEWER Option [%d] - … View.`. -- **`` section:** `dbc_game`, `dbc_log`, `dbc_chat`, `dbc_emergency`, - `auction`, `msg`, `manager`, `match`, `gm`, `gate`, `lobby`, `dungeon`, - `guild`, `agentmanager`, `agent` select which peers this server connects to. - The parser prints `Connect Option [%d] - …`. -- **`print`:** **?** - -The shipped `customoption.xml` has a different shape: flat `log_*`/`dbc_*` -elements carrying `string` and `timeout`, which look like connection strings. -It does **not** match the ``/`` structure this parser expects. -Either it was written for another binary, such as the DBCServer variants, or -it isn't actually consumed (**?**). Resolve this when analysing DBCServer. - -### 4.3 `option\total_option.xml` (O, `MS:0x14005ceb0` agent mode; `MS:0x14005c9f0` per-server apply) - -This is a single combined file, used in agent mode. -- **`MS:0x14005ceb0` (agent mode)** reads `agentmanager` (peer kind 0x15), - `each_option`, `dns`, `code`, `guild`, `api`, `code_start`, `setting` - (`multi`, `lock`), `start_server`, `revive_server` and `open_channel`. It - also reads the debug keys `develop`, `preload`, `preload_char`, - `enemy_debug`, `enemy_one`, `player_debug`, `lua_text` and `lua_send`. -- **`MS:0x14005c9f0` (per-server apply)** reads, for DBC kinds, `db`, `game`, - `log`, `chat` and `emergency` (each with `string`/`timeout`) plus `count`. - The section chosen depends on the server kind: 0x0c→`game`, 0x11→`log`, …. - For kind 5 (Guild) it sets the auto flag. - -The shipped `total_option.xml` is empty apart from its XML declaration. - -### 4.4 Other files referenced (S) - -| File | Reference | -|---|---| -| `option\agent.xml` | `MS:0x14012e6d0`, parsed by `MS:0x1400602b0`: `` entries and the agent peer (kind 0x0d) | -| `option\patch.xml` | `MS:0x14012e700`; no reader traced | -| `option\service_control.xml` | per-country auth/billing URLs and feature flags; reader not traced in MessageServer | -| `log_control.xml`, `content_control.xml` | in `binaries/Option/`; readers unknown | - -## 5. Database access path - -``` - Game/Guild/Msg/... server --ProudNet RMI (SERVERS2DBC)--> DBCServer[_LOG] --ADO/SQLOLEDB--> SQL Server - MGDBCMsgSession pool <--ProudNet RMI (DBC2SERVERS)-- -``` - -- **Session pools (O, `MS:0x140004f00` startup step `DB_LOAD`):** MessageServer - builds four pools of `MGDBCMsgSession`, one per DBC kind: 0x0c GAME, 0x11 - LOG, 0x12 CHAT and 0x13 EMERGENCY. Each pool has `db_cache session_count` - sessions (`+0x144`; `MS:0x140009330`). - - Each session embeds an `RNP::Servers2DbcProxy`, a subclass of the - PIDL-generated `SERVERS2DBC_PROTOCOL::Proxy` (`MS:0x140008e20`). - - The main loop pumps the pools (`DBCSessionManager->Process`) and retries - failed connects (`RetryConnectSession`). -- **Request protocol (O):** `SERVERS2DBC_PROTOCOL` contains **262 RMIs, IDs - 0x2ee1–0x2fe6**. The full name→ID table, extracted from the proxy marshal - functions, is in `decomp/shared/rmi_from_MessageServer.tsv`. - - The proxy code is generated, so every client binary is expected to contain - the same table (I). Verify by diffing the table extracted from each binary. - - Name groups include `UPDATE_*` (62), `READ_*`, `SAVE_*`, `INSERT_*`, - `REQ_LOG_*`, `GUILD_*`, `MAIL_*`, `PET_*`, `CHARACTER_*` and - `REQ_CHAT_LOG` (0x2fb2). -- **Response protocol:** `RNP::Dbc2ServersStub` has 85 slots. Its dispatcher - `MS:0x140039f20` **failed to decompile** (Ghidra headless timeout, and the - REA call timed out too). Recover it from DBCServer's `Dbc2Servers` proxy - instead. -- **SQL side:** not analysed yet. Mapping RMI → stored procedure/table needs - DBCServer, using `schema/*.mdf` to corroborate. - -## 6. Other shared protocols seen in MessageServer (O) - -| Protocol | IDs | Direction | -|---|---|---| -| `SERVERS2MSG` (stub) / `MSG2SERVERS` (proxy) | 0x3a99–0x3aa0 | servers ↔ MessageServer (`decomp/MessageServer_Release_x64/evidence.md`) | -| `SERVER2AGENT` | 12 proxy RMIs | see the TSV | -| `AGENT2SERVER` (stub) | ? | Agent pushes ServerInfo (§3) | - -## 7. Open questions (priority order) - -1. Decompile DBCServer: - - how it reads its DB connection strings; - - how RMIs map to SQL (stored procedure names); - - the `Dbc2Servers` response table. -2. Extract the `SERVERS2DBC` table from a second client binary to confirm the - protocol is identical across binaries. -3. Explain the mismatch between the `customoption.xml` shape and its parser. -4. Decode the semantics of the `send`, `auto`, `lock` and `each_option` - elements, and of the `api` peer. -5. Server kinds 0x0e–0x10 and 0x14. diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md deleted file mode 100644 index 1e9816b1..00000000 --- a/docs/WORKFLOW.md +++ /dev/null @@ -1,203 +0,0 @@ -# Workflow — reverse-engineering a server binary - -The repeatable process used for `Agent_Release_x64.exe`. Follow it for each -binary in `decomp/INVENTORY.md`. Ground rules, layout and tooling are in -`AGENTS.md`. What has been learned so far is in `docs/ARCHITECTURE.md`. - -Every finding is recorded as **observed**, **inference** or **unknown**, with an -Evidence ID or decomp file reference. Static analysis never establishes runtime -behaviour. - ---- - -## 0. Prerequisites (once per machine) - -- Ghidra is at `/opt/ghidra`, and Java 21 and Node come from `mise.toml`. - - REA MCP is registered project-locally in `opencode.json`. - - Don't reinstall any of these; repair the configuration instead. -- **Ghidra headless fails with `UnsupportedClassVersionError` (class file - 71.0):** its compiled-script cache was built by a newer JDK. - - Move `~/.config/ghidra/ghidra__DEV/osgi/compiled-bundles` aside. - - Ghidra rebuilds the cache on the next run. - -## 1. Identify the binary (cheap, before any decompilation) - -```sh -sha256sum binaries/.exe -objdump -p binaries/.exe | grep -E 'DLL Name|Subsystem|MajorLinker|pdb' -``` - -In REA: -1. Run `open_binary` with the original path under `binaries/`. -2. Run `binary_overview`. -3. Run `search_strings` (regex) for config paths, log text, protocol names and - class names. - -Record the SHA-256, linker version, PDB path, imports and procedure count in -`decomp//evidence.md`. - -## 2. Determine language and toolchain - -Check for these C++ markers: -- RTTI descriptors (`strings -a | grep '^\.?AV'`) -- `__CxxFrameHandler3` -- an `MSVCP*.dll` import - -All 16 servers are MSVC 2010 C++ (see `docs/ARCHITECTURE.md`). Re-check each -new binary and record the result. - -## 3. Discover third-party libraries and acquire their sources (**mandatory**) - -Any library you can match against upstream source saves you from -decompiling it, and gives names, struct layouts and intent that the binary -lacks. **Every time a library is found, search for its source online before -continuing.** - -### 3a. Find candidates - -| Signal | How | -|---|---| -| Imported DLLs | `objdump -p` DLL names, cross-checked against `binaries/*.dll` | -| DLL version resources | `strings -el \| grep -A1 -E '^(FileVersion\|ProductName\|CompanyName)$'` | -| Embedded version text | `strings -a \| grep -iE '/[0-9]\|part of OpenSSL\|libcurl/'` | -| Statically linked C++ libraries | RTTI class prefixes (`TiXml*`, `Proud::*`, `ATL::*`) | -| Library-specific strings | assert/error messages (`Proud::CNetServerImpl::…`, TinyXML error table) | -| PDB paths | e.g. `…\OpenLibrary\openssl-1.0.0d\…`, which names the exact version | - -### 3b. Pin the version - -Use, in this order of preference: -1. An explicit version string (VERSIONINFO, `"OpenSSL 1.0.0d 8 Feb 2011"`). -2. A version in the PDB path. -3. **Differential evidence:** download several candidate releases and compare a - distinctive artefact with the binary. Examples are error-string tables, - exported symbol lists and header constants. - - Example: the TinyXML error table contains `Memory allocation failed.`. - That entry exists in 2.5.x and was removed in 2.6, which gives 2.5.1–2.5.3. -4. If none of these work, choose the closest era-compatible release and mark it - **closest, not exact**. - -### 3c. Search for the source - -Search in this order: -1. Official release archives (project site or SourceForge; check `old/` and - `archeology/` paths). -2. Official git tags (`git ls-remote --tags `), or a GitHub tag archive - if the release tarball no longer exists. -3. Vendor package repositories. These often contain headers only. -4. Clean-room equivalents (for example mingw-w64 headers for Microsoft APIs) - or open reimplementations (for example Wine). -5. `gh search repos ` for forks or mirrors. Treat these as unverified and - label them that way. - -Verify each download against an official checksum where one exists. Always -record its SHA-256. - -### 3d. Check the licence and place the files - -| Licence | Location | Committed? | -|---|---|---| -| Open source (OpenSSL, curl/MIT, zlib, BSD, public domain) | `third-party/-/` | Yes | -| Proprietary or EULA-restricted (e.g. ProudNet) | `third-party/_local/-/` | **No.** It is gitignored. Ask the user before fetching. | -| No public source (XTrap, Apex, HASP runtime) | nothing downloaded | Record acquisition routes only | - -Other rules: -- Keep upstream files unmodified. If you must change one (for example curl's - version stamp), record exactly what changed. -- Read the licence before committing anything. If redistribution is - prohibited, it goes in `_local/` or isn't fetched. - -### 3e. Document it - -Add a row or section to `third-party/README.md` with: -- the observed identity in the binary, with where it was seen; -- the version and how it was pinned; -- the source URL, tag or commit, and SHA-256; -- match quality (exact / closest / headers only); -- the licence; -- any local edits. - -Also: -- Mention the library in `docs/ARCHITECTURE.md` if it is statically linked. -- Add it to the list in `AGENTS.md`. -- If no source exists, add it to the "No public source" and "Acquisition - routes" tables instead. - -### 3f. Use it - -- Label library functions in the decompiled output from known exports, class - method tables, error strings and the matched headers. -- Exclude labelled library code from application analysis. - -## 4. Decompile - -In REA, choose seeds from distinctive strings or imports and use -`trace_feature` to find the functions that reference them. Then run -`procedure_pseudo_code` / `batch_decompile` on those functions. - -For full coverage, run Ghidra headless with the repo scripts: - -```sh -analyzeHeadless /tmp/opencode/ghproj X -import binaries/.exe -overwrite \ - -scriptPath decomp/scripts -postScript ExportDecomp.java "$PWD/decomp//full" -deleteProject -analyzeHeadless /tmp/opencode/ghproj X -import binaries/.exe -overwrite \ - -scriptPath decomp/scripts -postScript VTableMap.java "$PWD/decomp//full/vtables.tsv" -deleteProject -``` - -This produces: -- `full/functions/.c` and `full/index.tsv` from `ExportDecomp.java`; -- `full/vtables.tsv` from `VTableMap.java`, which maps class vftable slots to - functions. - -Generate `cpp/classes.hpp` from `vtables.tsv`, then check that it compiles: -`g++ -fsyntax-only`. - -## 5. Record evidence - -In `decomp//`: -1. Run `export_evidence_bundle` to write `evidence-bundle.json`. -2. Extract the REA pseudocode to `.pseudo.c` and the overview to - `binary_overview.json`. -3. Write `evidence.md`: the findings table (conclusion → type → evidence), the - proposed function names, and the open questions. -4. Update the status and role of the binary in `decomp/INVENTORY.md`. - -## 6. Lift to readable C++ - -`decomp//cpp/*.cpp` holds hand-lifted code for the analysed functions: -- Cite the original address on each function. -- Mark every name as inferred. -- Record the original bugs you notice (leaks, races). - -## 7. Port to Linux - -`src//` builds the lifted code with Meson (see `src/README.md`). Keep a -`platform.{hpp,cpp}` layer that maps each Win32 call to its nearest POSIX -equivalent. - -The per-binary `README.md` must contain: -- a substitution table: original address → Win32 API → Linux replacement → - behaviour difference; -- what is stubbed or not ported, and why. - -Build with `ninja -C build` and smoke-test it. A passing test proves only that -the port works, not how the original behaves. - -## 8. Close - -Run `close_binary` when the investigation is complete. Before closing, export -the Evidence bundle, because session annotations are temporary. - ---- - -## Checklist per binary - -- [ ] SHA-256, imports, linker and PDB recorded -- [ ] Language confirmed -- [ ] **Every third-party library identified, its version pinned, its source - searched for and downloaded where licensing allows, and it documented in - `third-party/README.md`** -- [ ] Key functions decompiled; `full/` export done -- [ ] `evidence.md`, `INVENTORY.md`, `docs/ARCHITECTURE.md` updated -- [ ] Lift and Linux port updated, if in scope -- [ ] Evidence bundle exported; REA session closed