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
269 lines
8.7 KiB
C
269 lines
8.7 KiB
C
/* TPWinCrt character output, input queue, and TextRec callbacks. */
|
|
|
|
#include "tdkpin_tpwincrt_text.h"
|
|
|
|
#include "tdkpin_borland_files.h"
|
|
|
|
/* 1008:1a39 -- draw [start_column,end_column) on the cursor row. */
|
|
void tpwincrt_draw_row_range(int16_t end_column, int16_t start_column)
|
|
{
|
|
if (start_column >= end_column) {
|
|
return;
|
|
}
|
|
|
|
tpwincrt_begin_drawing();
|
|
int16_t x = (int16_t)(uint16_t)(
|
|
(uint16_t)(start_column - (int16_t)g_tpwincrt_view_column) *
|
|
g_tpwincrt_character_width);
|
|
int16_t y = (int16_t)(uint16_t)(
|
|
(uint16_t)((int16_t)g_tpwincrt_cursor_row -
|
|
(int16_t)g_tpwincrt_view_row) *
|
|
g_tpwincrt_character_height);
|
|
(void)TextOut16(
|
|
g_tpwincrt_paint_dc,
|
|
x,
|
|
y,
|
|
tpwincrt_cell_pointer(g_tpwincrt_cursor_row, start_column),
|
|
(int16_t)(end_column - start_column));
|
|
tpwincrt_end_drawing();
|
|
}
|
|
|
|
/*
|
|
* 1008:1a82 -- carriage return/line advance. The binary aliases two locals
|
|
* in 1b11 through SS:BP; explicit pointers preserve the same updates without
|
|
* pretending those caller-frame addresses are ordinary Pascal arguments.
|
|
*/
|
|
void tpwincrt_advance_line(int16_t *changed_start, int16_t *changed_end)
|
|
{
|
|
tpwincrt_draw_row_range(*changed_end, *changed_start);
|
|
*changed_start = 0;
|
|
*changed_end = 0;
|
|
g_tpwincrt_cursor_column = 0;
|
|
|
|
uint16_t next_row = (uint16_t)(g_tpwincrt_cursor_row + 1);
|
|
if (next_row == g_tpwincrt_rows) {
|
|
next_row--;
|
|
g_tpwincrt_ring_row_origin++;
|
|
if (g_tpwincrt_ring_row_origin == g_tpwincrt_rows) {
|
|
g_tpwincrt_ring_row_origin = 0;
|
|
}
|
|
Win16FarPtr row = tpwincrt_cell_pointer(g_tpwincrt_cursor_row, 0);
|
|
borland_fill_bytes(row, g_tpwincrt_columns, ' ');
|
|
(void)ScrollWindow16(
|
|
g_tpwincrt_window,
|
|
0,
|
|
(INT16)(uint16_t)(0u - g_tpwincrt_character_height),
|
|
0,
|
|
0);
|
|
(void)UpdateWindow16(g_tpwincrt_window);
|
|
}
|
|
g_tpwincrt_cursor_row = next_row;
|
|
}
|
|
|
|
/* 1008:1b11 -- write control/printable bytes into the circular text screen. */
|
|
void tpwincrt_write_bytes(uint16_t count, Win16FarPtr bytes)
|
|
{
|
|
tpwincrt_create_window();
|
|
int16_t changed_start = (int16_t)g_tpwincrt_cursor_column;
|
|
int16_t changed_end = changed_start;
|
|
|
|
while (count != 0) {
|
|
uint8_t character = win16_read_u8(bytes);
|
|
if (character >= 0x20) {
|
|
win16_write_u8(
|
|
tpwincrt_cell_pointer(
|
|
g_tpwincrt_cursor_row,
|
|
(int16_t)g_tpwincrt_cursor_column),
|
|
character);
|
|
g_tpwincrt_cursor_column++;
|
|
if (changed_end < (int16_t)g_tpwincrt_cursor_column) {
|
|
changed_end = (int16_t)g_tpwincrt_cursor_column;
|
|
}
|
|
if (g_tpwincrt_cursor_column == g_tpwincrt_columns) {
|
|
tpwincrt_advance_line(&changed_start, &changed_end);
|
|
}
|
|
} else if (character == '\r') {
|
|
tpwincrt_advance_line(&changed_start, &changed_end);
|
|
} else if (character == '\b') {
|
|
if ((int16_t)g_tpwincrt_cursor_column > 0) {
|
|
g_tpwincrt_cursor_column--;
|
|
win16_write_u8(
|
|
tpwincrt_cell_pointer(
|
|
g_tpwincrt_cursor_row,
|
|
(int16_t)g_tpwincrt_cursor_column),
|
|
' ');
|
|
if ((int16_t)g_tpwincrt_cursor_column < changed_start) {
|
|
changed_start = (int16_t)g_tpwincrt_cursor_column;
|
|
}
|
|
}
|
|
} else if (character == '\a') {
|
|
(void)MessageBeep16(0);
|
|
}
|
|
|
|
bytes = win16_far_add_offset(bytes, 1);
|
|
count--;
|
|
}
|
|
|
|
tpwincrt_draw_row_range(changed_end, changed_start);
|
|
if (g_tpwincrt_auto_scroll != 0) {
|
|
tpwincrt_scroll_cursor_into_view();
|
|
}
|
|
}
|
|
|
|
/* 1008:1be9 -- one-byte stack wrapper around the bulk writer. */
|
|
void tpwincrt_write_character(uint8_t character)
|
|
{
|
|
uint8_t byte = character;
|
|
tpwincrt_write_bytes(1, win16_stack_pointer(&byte));
|
|
}
|
|
|
|
/* 1008:1c03 -- drain all queued Win16 messages and report input availability. */
|
|
bool tpwincrt_poll_input(void)
|
|
{
|
|
tpwincrt_create_window();
|
|
MSG16 message;
|
|
while (PeekMessage16(&message, 0, 0, 0, 1)) {
|
|
if (message.message == 0x0012) { /* WM_QUIT */
|
|
tpwincrt_abort_on_ctrl_c();
|
|
}
|
|
(void)TranslateMessage16(&message);
|
|
(void)DispatchMessage16(&message);
|
|
}
|
|
return (int16_t)g_tpwincrt_input_count > 0;
|
|
}
|
|
|
|
/* 1008:1c5f -- block until one queued character is available, then dequeue it. */
|
|
uint8_t tpwincrt_read_character(void)
|
|
{
|
|
tpwincrt_scroll_cursor_into_view();
|
|
if (!tpwincrt_poll_input()) {
|
|
g_tpwincrt_caret_enabled = 1;
|
|
if (g_tpwincrt_has_focus != 0) {
|
|
tpwincrt_create_caret();
|
|
}
|
|
do {
|
|
(void)WaitMessage16();
|
|
} while (!tpwincrt_poll_input());
|
|
if (g_tpwincrt_has_focus != 0) {
|
|
tpwincrt_destroy_caret();
|
|
}
|
|
g_tpwincrt_caret_enabled = 0;
|
|
}
|
|
|
|
g_tpwincrt_input_count--;
|
|
uint8_t character = g_tpwincrt_input_buffer[0];
|
|
borland_move_bytes(
|
|
win16_dgroup_pointer(0x4af8),
|
|
win16_dgroup_pointer(0x4af9),
|
|
g_tpwincrt_input_count);
|
|
return character;
|
|
}
|
|
|
|
/* 1008:1cc6 -- edited line input with CR/LF and optional Ctrl-Z termination. */
|
|
uint16_t tpwincrt_read_line(uint16_t capacity, Win16FarPtr destination)
|
|
{
|
|
uint16_t length = 0;
|
|
uint8_t character;
|
|
do {
|
|
character = tpwincrt_read_character();
|
|
if (character == '\b') {
|
|
if (length != 0) {
|
|
length--;
|
|
tpwincrt_write_character('\b');
|
|
}
|
|
} else if (
|
|
character >= 0x20 &&
|
|
(uint16_t)(capacity - 2) > length) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(destination, length), character);
|
|
length++;
|
|
tpwincrt_write_character(character);
|
|
}
|
|
} while (
|
|
character != '\r' &&
|
|
(g_tpwincrt_ctrl_z_is_eof == 0 || character != 0x1a));
|
|
|
|
win16_write_u8(win16_far_add_offset(destination, length), character);
|
|
length++;
|
|
if (character == '\r') {
|
|
win16_write_u8(win16_far_add_offset(destination, length), '\n');
|
|
length++;
|
|
tpwincrt_write_character('\r');
|
|
}
|
|
tpwincrt_scroll_cursor_into_view();
|
|
return length;
|
|
}
|
|
|
|
/* 1008:22fc -- TextRec output InOutFunc/FlushFunc. */
|
|
uint16_t tpwincrt_text_write_buffer(Win16FarPtr record)
|
|
{
|
|
uint16_t count = win16_read_u16(win16_far_add_offset(record, 8));
|
|
if (count != 0) {
|
|
tpwincrt_write_bytes(count, win16_read_far_pointer(record, 0x0c));
|
|
win16_write_u16(record, 8, 0);
|
|
(void)tpwincrt_poll_input();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* 1008:233c -- TextRec input InOutFunc. */
|
|
uint16_t tpwincrt_text_read_buffer(Win16FarPtr record)
|
|
{
|
|
uint16_t count = tpwincrt_read_line(
|
|
win16_read_u16(win16_far_add_offset(record, 4)),
|
|
win16_read_far_pointer(record, 0x0c));
|
|
win16_write_u16(record, 0x0a, count);
|
|
win16_write_u16(record, 8, 0);
|
|
return 0;
|
|
}
|
|
|
|
static void write_text_procedure(
|
|
Win16FarPtr record, uint16_t offset, Win16FarPtr procedure)
|
|
{
|
|
win16_write_u16(record, offset, win16_far_offset(procedure));
|
|
win16_write_u16(
|
|
record, (uint16_t)(offset + 2), win16_far_selector(procedure));
|
|
}
|
|
|
|
/* 1008:238b -- attach TPWinCrt callbacks to an input or output TextRec. */
|
|
uint16_t tpwincrt_open_text_record(Win16FarPtr record)
|
|
{
|
|
if (win16_read_u16(win16_far_add_offset(record, 2)) ==
|
|
BORLAND_FILE_INPUT) {
|
|
write_text_procedure(
|
|
record,
|
|
0x14,
|
|
win16_relocated_code_pointer(2, 0x233c));
|
|
write_text_procedure(record, 0x18, 0);
|
|
} else {
|
|
win16_write_u16(record, 2, BORLAND_FILE_OUTPUT);
|
|
Win16FarPtr write = win16_relocated_code_pointer(2, 0x22fc);
|
|
write_text_procedure(record, 0x14, write);
|
|
write_text_procedure(record, 0x18, write);
|
|
}
|
|
write_text_procedure(
|
|
record,
|
|
0x1c,
|
|
win16_relocated_code_pointer(2, 0x2374));
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 1008:23fd -- initialize only the fields written by the binary. BufPos,
|
|
* BufEnd, callbacks other than OpenFunc, and UserData deliberately retain
|
|
* their prior bytes until Reset/Rewrite invokes 238b.
|
|
*/
|
|
void tpwincrt_initialize_text_record(Win16FarPtr record)
|
|
{
|
|
win16_write_u16(record, 0, 0xffff);
|
|
win16_write_u16(record, 2, BORLAND_FILE_CLOSED);
|
|
win16_write_u16(record, 4, 0x80);
|
|
Win16FarPtr buffer = win16_far_add_offset(record, 0x80);
|
|
write_text_procedure(record, 0x0c, buffer);
|
|
write_text_procedure(
|
|
record,
|
|
0x10,
|
|
win16_relocated_code_pointer(2, 0x238b));
|
|
win16_write_u8(win16_far_add_offset(record, 0x30), 0);
|
|
}
|