/* * Address-linked Borland Win16 runtime termination support from TDKPIN.EXE. * Functions: 1020:0061, 1020:00d2, and 1020:00f0. */ #include "tdkpin_borland_runtime.h" enum { DGROUP_RUNTIME_ERROR_TEXT = 0x0728, RUNTIME_ERROR_DECIMAL_END = 0x0739, RUNTIME_ERROR_SEGMENT_END = 0x0741, RUNTIME_ERROR_OFFSET_END = 0x0746, RUNTIME_ERROR_MESSAGEBOX_FLAGS = 0x1010, }; extern Win16FarPtr g_borland_exit_proc; /* 1028:0712/0714 */ extern uint16_t g_borland_exit_code_word; /* 1028:0716/0717 */ extern uint16_t g_borland_error_offset; /* 1028:0718 */ extern uint16_t g_borland_error_segment; /* 1028:071a */ extern uint16_t g_borland_windows_mode; /* 1028:071c */ extern char g_borland_runtime_error_text[32]; /* 1028:0728..0747 */ /* 1020:012d — checked GetMem wrapper; allocation failure is Runtime Error 203. */ Win16FarPtr borland_get_mem(uint16_t bytes) { BorlandAllocationAttempt attempt = borland_try_get_mem(bytes); if (attempt.failed) { borland_runtime_error(203); } return attempt.block; } /* 1020:0147 — checked FreeMem wrapper; invalid release is Runtime Error 204. */ void borland_free_mem(Win16FarPtr block, uint16_t bytes) { if (borland_try_free_mem(block, bytes)) { borland_runtime_error(204); } } /* 1020:0222 — GlobalAlloc16 followed by GlobalLock16 on nonzero handle. */ Win16FarPtr borland_global_allocate(uint16_t bytes) { HGLOBAL16 allocation = GlobalAlloc16(g_borland_global_alloc_flags, bytes); return allocation == 0 ? 0 : GlobalLock16(allocation); } uint16_t borland_take_io_result(void) { return atomic_exchange_explicit( &g_borland_io_result, 0, memory_order_seq_cst); } void borland_require_io_success(void) { uint16_t result = atomic_load_explicit( &g_borland_io_result, memory_order_seq_cst); if (result != 0) { borland_runtime_error(result); } } void borland_check_int32_range( int32_t value, const BorlandInt32Range *range) { if (value < range->minimum || value > range->maximum) { borland_runtime_error(201); } } _Noreturn void borland_runtime_error_215(void) { borland_runtime_error(215); } /* 1008:2de0 — AL=1 exactly when the emergency reserve far pointer is null. */ bool borland_heap_reserve_is_unallocated(void) { return g_borland_heap_reserve == 0; } /* 1008:2dff — allocate the configured reserve only when it is absent. */ void borland_ensure_heap_reserve(void) { if (borland_heap_reserve_is_unallocated()) { g_borland_heap_reserve = borland_get_mem(g_borland_heap_reserve_bytes); } } /* * 1008:2e22 — Borland heap-error callback. * 0 means no reserve was available, 1 suppresses release, and 2 tells the * allocator to retry after the reserve has been freed. The zero-size entry is * not used by the allocator and returns the same indeterminate word as the * binary's uninitialized local. */ uint16_t borland_release_heap_reserve_on_oom(uint16_t requested_bytes) { if (requested_bytes == 0) { return win16_indeterminate_u16(); } if (g_borland_heap_reserve_disabled) { return 1; } if (borland_heap_reserve_is_unallocated()) { return 0; } borland_free_mem(g_borland_heap_reserve, g_borland_heap_reserve_bytes); g_borland_heap_reserve = 0; return 2; } /* 1008:2e76 — provision the reserve and publish the retry callback. */ void borland_initialize_heap_reserve(void) { borland_ensure_heap_reserve(); g_borland_heap_error_handler = win16_relocated_code_pointer(2, 0x2e22); } /* 1020:16c9 — CLD plus REP STOSB over one far destination. */ void borland_fill_bytes(Win16FarPtr destination, uint16_t count, uint8_t value) { win16_fill_bytes(destination, count, value); } /* * 1020:16a5 — Borland System.Move over two far pointers. * * The original compares only SI and DI, not their selectors, before choosing * CLD or STD. Copying backwards across distinct segments is harmless but is a * real observable detail of the routine. All address arithmetic wraps at the * 16-bit offset boundary exactly as the 8086 registers do. */ void borland_move_bytes( Win16FarPtr destination, Win16FarPtr source, uint16_t count) { uint16_t source_selector = win16_far_selector(source); uint16_t destination_selector = win16_far_selector(destination); uint16_t source_offset = win16_far_offset(source); uint16_t destination_offset = win16_far_offset(destination); if (source_offset >= destination_offset) { while (count != 0) { uint8_t byte = win16_read_u8( win16_make_far_pointer(source_selector, source_offset)); win16_write_u8( win16_make_far_pointer( destination_selector, destination_offset), byte); source_offset++; destination_offset++; count--; } return; } source_offset = (uint16_t)(source_offset + count - 1u); destination_offset = (uint16_t)(destination_offset + count - 1u); while (count != 0) { uint8_t byte = win16_read_u8( win16_make_far_pointer(source_selector, source_offset)); win16_write_u8( win16_make_far_pointer(destination_selector, destination_offset), byte); source_offset--; destination_offset--; count--; } } /* * 1020:046c — copy one compiler-described record. * * A near descriptor pointer lives at destination + descriptor_field_offset; * its first DGROUP word is the record byte length. The binary saves that near * pointer, performs a forward far copy onto the start of the destination, and * writes the descriptor pointer back because the copied bytes may overlap it. */ void borland_copy_typed_record( uint16_t descriptor_field_offset, Win16FarPtr destination, Win16FarPtr source) { uint16_t destination_selector = win16_far_selector(destination); uint16_t destination_offset = win16_far_offset(destination); uint16_t source_selector = win16_far_selector(source); uint16_t source_offset = win16_far_offset(source); uint16_t descriptor_address = (uint16_t)(destination_offset + descriptor_field_offset); Win16FarPtr descriptor_field = win16_make_far_pointer( destination_selector, descriptor_address); uint16_t descriptor_offset = win16_read_u16(descriptor_field); uint16_t count = win16_read_u16(win16_dgroup_pointer(descriptor_offset)); while (count != 0) { uint8_t byte = win16_read_u8( win16_make_far_pointer(source_selector, source_offset)); win16_write_u8( win16_make_far_pointer( destination_selector, destination_offset), byte); source_offset++; destination_offset++; count--; } win16_write_u16(descriptor_field, 0, descriptor_offset); } /* 1020:00f0 — prepend AX as unsigned digits in base CX before BX. */ char *borland_prepend_unsigned(char *end, uint16_t value, uint16_t base) { do { uint16_t quotient = value / base; uint8_t digit = (uint8_t)(value % base); digit = (uint8_t)(digit + (digit < 10 ? '0' : 'A' - 10)); *--end = (char)digit; value = quotient; } while (value != 0); return end; } /* * 1020:00d2 — execute the mutable Borland ExitProc chain. * The binary clears ExitProc and IOResult before each far call and returns each * callback to 00d2, so a callback may install the next procedure in the chain. */ void borland_run_exit_procedures(void) { while (g_borland_exit_proc != 0) { Win16FarPtr procedure = g_borland_exit_proc; g_borland_exit_proc = 0; atomic_store_explicit( &g_borland_io_result, 0, memory_order_seq_cst); win16_call_exit_proc(procedure); } } /* * 1020:0061 — register-entry Borland Halt. * The caller supplies the status in AX (only AL is eventually returned to DOS). */ static _Noreturn void borland_finish_runtime_exit( uint16_t status, uint16_t error_offset, uint16_t error_segment) { g_borland_exit_code_word = status; g_borland_error_offset = error_offset; g_borland_error_segment = error_segment; if (g_borland_windows_mode != 0) { borland_run_exit_procedures(); } if (g_borland_error_offset != 0 || g_borland_error_segment != 0) { (void)borland_prepend_unsigned( &g_borland_runtime_error_text[ RUNTIME_ERROR_DECIMAL_END - DGROUP_RUNTIME_ERROR_TEXT], (uint8_t)g_borland_exit_code_word, 10); (void)borland_prepend_unsigned( &g_borland_runtime_error_text[ RUNTIME_ERROR_SEGMENT_END - DGROUP_RUNTIME_ERROR_TEXT], g_borland_error_segment, 16); (void)borland_prepend_unsigned( &g_borland_runtime_error_text[ RUNTIME_ERROR_OFFSET_END - DGROUP_RUNTIME_ERROR_TEXT], g_borland_error_offset, 16); (void)MessageBox16( 0, win16_dgroup_pointer(DGROUP_RUNTIME_ERROR_TEXT), 0, RUNTIME_ERROR_MESSAGEBOX_FLAGS); } win16_dos_terminate((uint8_t)g_borland_exit_code_word); } _Noreturn void borland_runtime_exit(uint16_t status) { borland_finish_runtime_exit(status, 0, 0); } /* * 1020:005d — RuntimeError entry shim. * It consumes the caller's far return address. Except for selector ffff, the * word at selector:0000 is the NE logical segment tag recorded in the message. */ _Noreturn void borland_runtime_error(uint16_t code) { Win16FarPtr return_address = win16_current_return_address(); uint16_t selector = win16_far_selector(return_address); uint16_t segment = selector; if (return_address != 0 && selector != 0xffff) { segment = win16_read_u16(win16_make_far_pointer(selector, 0)); } borland_finish_runtime_exit( code, win16_far_offset(return_address), segment); }