Files
tdkpin/original/reconstructed/tdkpin_object_windows.c
T
ddidderr 8b99e9607c feat(reconstruction): complete binary-backed C recovery
Replace the partial mechanics transcriptions with a separate, readable C11
reconstruction of the complete Win16 image while preserving the original raw
Ghidra export as immutable evidence. Cover all ordinary and overlapping entry
points, Borland runtime behavior, Win16 imports, segmented data, callbacks,
resources, indirect control flow, physics, rendering, persistence, and
startup/shutdown lifecycles.

Add deterministic extraction and audit tooling plus address-linked ledgers for
functions, imports, DGROUP ranges and objects, relocations, resources, and
callbacks. The final gate records zero raw, partial, restored, unknown,
blocked, or unclassified required units. Keep the semantic-fidelity boundary
explicit: the portable C is not claimed to reproduce a byte-identical Borland
NE build.

Add strict focused harnesses for every reconstructed C unit, exact resource
round-trip checks, and a 16-bit Borland Real48 reference probe. No Rust source
or Cargo metadata is changed in this phase.

Test Plan:
- `bash original/tools/test_reconstructed_c.sh` -- passed
- `bash original/tools/probe_real48_reference.sh` -- passed bit-for-bit
- `python3 original/tools/audit_reconstruction.py --require-complete` -- passed
- `git diff --cached --check` -- passed
- `git diff HEAD -- '*.rs' Cargo.toml Cargo.lock` -- empty
2026-08-23 16:41:17 +02:00

358 lines
13 KiB
C

/*
* Borland ObjectWindows callback bootstrap reconstructed from TDKPIN.EXE.
*
* Target: a9022f1894e3e6e21fc42e8f6c932f7c549ca77f63aaa0c488bb9d55d9d0174c
* Functions: 1018:0045 and 1018:018b (NE export ordinals 208 and 211)
*/
#include "tdkpin_object_windows.h"
#include "tdkpin_borland_runtime.h"
enum {
OBJECT_WINDOW_VMT_OFFSET = 0,
OBJECT_WINDOW_HANDLE_OFFSET = 4,
OBJECT_WINDOW_BOUND_PROC_OFFSET = 0x12,
WIN16_GWL_WNDPROC = -4,
DGROUP_OBJECT_SELECTOR_PROPERTY = 0x067a, /* "OW1" */
DGROUP_OBJECT_OFFSET_PROPERTY = 0x067e, /* "OW2" */
};
enum {
VMT_MESSAGE_TABLE_OFFSET = 4,
VMT_DEFAULT_MESSAGE_SLOT = 0x0c,
MESSAGE_TABLE_PARENT_OFFSET = 0,
MESSAGE_TABLE_CACHE_ID_OFFSET = 2,
MESSAGE_TABLE_CACHE_SLOT_OFFSET = 4,
MESSAGE_TABLE_COUNT_OFFSET = 6,
MESSAGE_TABLE_IDS_OFFSET = 8,
};
/* DGROUP 1028:0676/0678; valid only while CreateWindow16 is in progress. */
extern Win16FarPtr g_object_windows_creating_object;
LRESULT16 SetWindowLong16(HWND16 window, INT16 index, int32_t value);
BOOL16 SetProp16(HWND16 window, Win16FarPtr name, Win16Handle value);
Win16Handle RemoveProp16(HWND16 window, Win16FarPtr name);
Win16Handle GetProp16(HWND16 window, Win16FarPtr name);
BOOL16 IsWindow16(HWND16 window);
int32_t GetWindowLong16(HWND16 window, INT16 index);
/*
* 1018:0045 — associate a far ObjectWindows object with one HWND.
*
* Win16 SetProp stores only a 16-bit HANDLE, so ObjectWindows deliberately
* splits the far pointer into two properties. OW1 holds the selector and OW2
* holds the offset; the inverse lookup routine recombines them.
*/
void object_windows_bind_window(HWND16 window, Win16FarPtr object)
{
(void)SetProp16(
window,
win16_dgroup_pointer(DGROUP_OBJECT_SELECTOR_PROPERTY),
win16_far_selector(object));
(void)SetProp16(
window,
win16_dgroup_pointer(DGROUP_OBJECT_OFFSET_PROPERTY),
win16_far_offset(object));
}
/* 1018:0071 — remove both halves of the HWND/object association. */
void object_windows_unbind_window(HWND16 window)
{
(void)RemoveProp16(
window, win16_dgroup_pointer(DGROUP_OBJECT_SELECTOR_PROPERTY));
(void)RemoveProp16(
window, win16_dgroup_pointer(DGROUP_OBJECT_OFFSET_PROPERTY));
}
/*
* 1018:0097 — recover the bound ObjectWindows object for one HWND.
* Generated thunks carry the object at +3/+5; subclassed/native procedures use
* the OW1 selector and OW2 offset properties installed by 1018:0045.
*/
Win16FarPtr object_windows_object_from_window(HWND16 window)
{
if (!IsWindow16(window)) {
return 0;
}
Win16FarPtr procedure =
(Win16FarPtr)GetWindowLong16(window, WIN16_GWL_WNDPROC);
uint16_t procedure_offset = win16_far_offset(procedure);
bool generated_thunk =
win16_read_u8(procedure) == 0xe8 &&
win16_read_u16(win16_far_add_offset(procedure, 1)) ==
(uint16_t)(0xffff - procedure_offset) &&
win16_read_u16(win16_make_far_pointer(
win16_far_selector(procedure), 2)) == 0x2e5b;
if (generated_thunk) {
return win16_read_far_pointer(procedure, 3);
}
uint16_t selector = GetProp16(
window, win16_dgroup_pointer(DGROUP_OBJECT_SELECTOR_PROPERTY));
uint16_t offset = GetProp16(
window, win16_dgroup_pointer(DGROUP_OBJECT_OFFSET_PROPERTY));
return win16_make_far_pointer(selector, offset);
}
/*
* 1018:018b — class bootstrap WNDPROC16.
*
* The initial class procedure is used for the first message only. It replaces
* itself with the object's generated 7-byte bound thunk, publishes the HWND to
* object association, then tail-dispatches the unchanged 10-byte WNDPROC frame.
* AX:DX from that direct far call is returned unchanged.
*/
LRESULT16 object_window_bootstrap_proc(
HWND16 window, UINT16 message, WPARAM16 wparam, LPARAM16 lparam)
{
Win16FarPtr object = g_object_windows_creating_object;
Win16FarPtr bound_proc =
win16_read_far_pointer(object, OBJECT_WINDOW_BOUND_PROC_OFFSET);
(void)SetWindowLong16(window, WIN16_GWL_WNDPROC, (int32_t)bound_proc);
object_windows_bind_window(window, object);
return win16_call_window_proc(bound_proc, window, message, wparam, lparam);
}
/*
* 1018:0002 — resolve a nonnegative ObjectWindows message id.
*
* The most-derived VMT points at a linked dynamic-message table. Every node
* contains a parent pointer, ids, and far handler slots in the same compact
* layout as Borland dynamic methods. Unlike 1020:16e6, a miss selects a caller-
* supplied VMT fallback slot, and a parent hit is cached in the most-derived
* table rather than in the node where it was found.
*/
Win16FarPtr object_windows_resolve_message_handler(
uint16_t vmt, uint16_t message, uint16_t fallback_slot)
{
uint16_t root_table = win16_read_u16(win16_dgroup_pointer(
(uint16_t)(vmt + VMT_MESSAGE_TABLE_OFFSET)));
if (root_table == 0) {
return win16_dgroup_pointer((uint16_t)(vmt + fallback_slot));
}
Win16FarPtr root = win16_dgroup_pointer(root_table);
if (message == win16_read_u16(win16_far_add_offset(
root, MESSAGE_TABLE_CACHE_ID_OFFSET))) {
uint16_t cached_slot = win16_read_u16(win16_far_add_offset(
root, MESSAGE_TABLE_CACHE_SLOT_OFFSET));
return win16_dgroup_pointer(cached_slot);
}
uint16_t table = root_table;
while (table != 0) {
Win16FarPtr node = win16_dgroup_pointer(table);
uint16_t count = win16_read_u16(win16_far_add_offset(
node, MESSAGE_TABLE_COUNT_OFFSET));
for (uint16_t index = 0; index < count; index++) {
uint16_t id = win16_read_u16(win16_far_add_offset(
node,
(uint16_t)(MESSAGE_TABLE_IDS_OFFSET + index * 2u)));
if (id != message) {
continue;
}
uint16_t slot = (uint16_t)(
table + MESSAGE_TABLE_IDS_OFFSET + count * 2u +
index * 4u);
win16_write_u16(
root, MESSAGE_TABLE_CACHE_ID_OFFSET, message);
win16_write_u16(
root, MESSAGE_TABLE_CACHE_SLOT_OFFSET, slot);
return win16_dgroup_pointer(slot);
}
table = win16_read_u16(win16_far_add_offset(
node, MESSAGE_TABLE_PARENT_OFFSET));
}
return win16_dgroup_pointer((uint16_t)(vmt + fallback_slot));
}
/* 1018:0103 — invoke the optional process-wide message instrumentation hook. */
void object_windows_call_message_hook(
Win16FarPtr object,
Win16FarPtr handler,
const ObjectWindowsMessage *message)
{
win16_call_object_message_hook(
g_object_windows_message_hook,
object,
handler,
message->window,
message->message,
message->wparam,
message->lparam);
}
/*
* 1018:0133 — shared WNDPROC16/DLGPROC16 reached by every generated thunk.
* The thunk supplies ES:BX = object. The Pascal Win16 argument order places
* HWND at BP+0e and LPARAM at BP+06/+08; the explicit C signature restores the
* logical API order while preserving all original field widths.
*/
LRESULT16 object_dispatch_window_or_dialog_proc(
Win16FarPtr object,
HWND16 window,
UINT16 message,
WPARAM16 wparam,
LPARAM16 lparam)
{
win16_write_u16(object, OBJECT_WINDOW_HANDLE_OFFSET, window);
ObjectWindowsMessage record = {
.window = window,
.message = message,
.wparam = wparam,
.lparam = lparam,
.result = 1,
};
uint16_t vmt = win16_read_u16(win16_far_add_offset(
object, OBJECT_WINDOW_VMT_OFFSET));
Win16FarPtr handler_slot;
if ((int16_t)message < 0) {
handler_slot = win16_dgroup_pointer(
(uint16_t)(vmt + VMT_DEFAULT_MESSAGE_SLOT));
} else {
handler_slot = object_windows_resolve_message_handler(
vmt, message, VMT_DEFAULT_MESSAGE_SLOT);
}
Win16FarPtr handler = win16_read_far_pointer(handler_slot, 0);
if (win16_far_selector(g_object_windows_message_hook) != 0) {
object_windows_call_message_hook(object, handler, &record);
}
win16_call_object_message_handler(handler, object, &record);
return record.result;
}
/*
* 1018:08c3 — generic ObjectWindows event dispatch used by command/focus/menu
* paths. DX supplies the fallback VMT slot and AX the dynamic method id. The
* existing far event record is passed unchanged to the selected handler.
*/
void object_windows_dispatch_event(
uint16_t fallback_slot,
uint16_t method_id,
Win16FarPtr event,
Win16FarPtr object)
{
uint16_t vmt = win16_read_u16(object);
Win16FarPtr handler_slot = object_windows_resolve_message_handler(
vmt, method_id, fallback_slot);
Win16FarPtr handler = win16_read_far_pointer(handler_slot, 0);
if (win16_far_selector(g_object_windows_message_hook) != 0) {
ObjectWindowsMessage hook_record = {
.window = win16_read_u16(event),
.message = win16_read_u16(win16_far_add_offset(event, 2)),
.wparam = win16_read_u16(win16_far_add_offset(event, 4)),
.lparam = (LPARAM16)(uint32_t)(
win16_read_u16(win16_far_add_offset(event, 6)) |
((uint32_t)win16_read_u16(
win16_far_add_offset(event, 8))
<< 16)),
.result = 0,
};
object_windows_call_message_hook(object, handler, &hook_record);
}
win16_call_object_event_handler(handler, object, event);
}
enum {
BOUND_THUNK_POOL_BYTES = 0x0100,
BOUND_THUNK_SHARED_CODE_OFFSET = 2,
BOUND_THUNK_SHARED_CODE_BYTES = 5,
BOUND_THUNK_DISPATCH_PROC_OFFSET = 7,
BOUND_THUNK_FIRST_OFFSET = 0x000b,
BOUND_THUNK_BYTES = 7,
BOUND_THUNK_OBJECT_OFFSET = 3,
DGROUP_BOUND_THUNK_TEMPLATE = 0x0686,
};
static void write_far_pointer(
Win16FarPtr destination, uint16_t byte_offset, Win16FarPtr value)
{
win16_write_u16(destination, byte_offset, win16_far_offset(value));
win16_write_u16(
destination,
(uint16_t)(byte_offset + 2),
win16_far_selector(value));
}
/*
* Build one 256-byte executable block. Offset 2 contains the shared sequence
* `POP BX; CS: LES BX,[BX]; JMP FAR dispatch_proc`. Each seven-byte thunk is
* `CALL block:0002` followed by a mutable far object/free-list pointer. The
* near displacement -1-offset makes every CALL land at block offset 2.
*/
static void allocate_bound_thunk_pool(void)
{
HGLOBAL16 allocation = GlobalAlloc16(0, BOUND_THUNK_POOL_BYTES);
Win16FarPtr block = GlobalLock16(allocation);
uint16_t selector = win16_far_selector(block);
win16_write_u16(block, 0, g_object_windows_thunk_code_selector);
borland_move_bytes(
win16_far_add_offset(block, BOUND_THUNK_SHARED_CODE_OFFSET),
win16_dgroup_pointer(DGROUP_BOUND_THUNK_TEMPLATE),
BOUND_THUNK_SHARED_CODE_BYTES);
write_far_pointer(
block,
BOUND_THUNK_DISPATCH_PROC_OFFSET,
g_object_windows_dispatch_proc);
uint16_t offset = BOUND_THUNK_FIRST_OFFSET;
do {
Win16FarPtr thunk = win16_make_far_pointer(selector, offset);
win16_write_u8(thunk, 0xe8);
win16_write_u16(
thunk, 1, (uint16_t)(0xffffu - offset));
write_far_pointer(
thunk, BOUND_THUNK_OBJECT_OFFSET, g_object_windows_free_thunk);
g_object_windows_free_thunk = thunk;
offset = (uint16_t)(offset + BOUND_THUNK_BYTES);
} while (offset != BOUND_THUNK_POOL_BYTES);
g_object_windows_thunk_code_selector = selector;
(void)PrestoChangoSelector16(selector, selector);
}
/*
* 1018:01dc — pop a generated WNDPROC thunk and bind its embedded far object.
* Executable selectors are modified only through a temporary CS-to-DS alias.
*/
Win16FarPtr object_windows_allocate_bound_thunk(Win16FarPtr object)
{
if (g_object_windows_free_thunk == 0) {
allocate_bound_thunk_pool();
}
Win16FarPtr thunk = g_object_windows_free_thunk;
uint16_t alias =
AllocCStoDSAlias16(win16_far_selector(thunk));
Win16FarPtr writable = win16_make_far_pointer(
alias, win16_far_offset(thunk));
g_object_windows_free_thunk =
win16_read_far_pointer(writable, BOUND_THUNK_OBJECT_OFFSET);
write_far_pointer(writable, BOUND_THUNK_OBJECT_OFFSET, object);
(void)FreeSelector16(alias);
return thunk;
}
/* 1018:02fa — push one bound WNDPROC thunk back onto the global free list. */
void object_windows_release_bound_thunk(Win16FarPtr thunk)
{
uint16_t alias =
AllocCStoDSAlias16(win16_far_selector(thunk));
Win16FarPtr writable = win16_make_far_pointer(
alias, win16_far_offset(thunk));
write_far_pointer(
writable,
BOUND_THUNK_OBJECT_OFFSET,
g_object_windows_free_thunk);
(void)FreeSelector16(alias);
g_object_windows_free_thunk = thunk;
}