/* Borland Pascal generic file open/close dispatcher. */ #include "tdkpin_borland_files.h" enum { FILE_MODE_OFFSET = 2, FILE_BUFFER_POSITION_OFFSET = 8, FILE_BUFFER_END_OFFSET = 10, FILE_OPEN_PROCEDURE_OFFSET = 0x10, FILE_IN_OUT_PROCEDURE_OFFSET = 0x14, FILE_CLOSE_PROCEDURE_OFFSET = 0x1c, IO_ERROR_NOT_CLOSED = 0x66, IO_ERROR_NOT_OPEN = 0x67, }; /* 1020:05c7 — invoke one far FileProc and publish nonzero AX to IOResult. */ uint16_t borland_call_file_procedure( Win16FarPtr record, uint16_t procedure_offset) { Win16FarPtr procedure = win16_read_far_pointer(record, procedure_offset); uint16_t result = win16_call_file_procedure(procedure, record); if (result != 0) { atomic_store(&g_borland_io_result, result); } return result; } /* 1020:0586 — close input/output files; output files flush first. */ void borland_close_file(Win16FarPtr record) { uint16_t mode = win16_read_u16(win16_far_add_offset( record, FILE_MODE_OFFSET)); if (mode == BORLAND_FILE_OUTPUT) { (void)borland_call_file_procedure( record, FILE_IN_OUT_PROCEDURE_OFFSET); } else if (mode != BORLAND_FILE_INPUT) { atomic_store(&g_borland_io_result, IO_ERROR_NOT_OPEN); return; } (void)borland_call_file_procedure(record, FILE_CLOSE_PROCEDURE_OFFSET); win16_write_u16(record, FILE_MODE_OFFSET, BORLAND_FILE_CLOSED); } /* 1020:0534 — common Reset/Rewrite entry selected by DX. */ void borland_open_file(Win16FarPtr record, uint16_t mode) { uint16_t current = win16_read_u16(win16_far_add_offset( record, FILE_MODE_OFFSET)); if (current == BORLAND_FILE_INPUT || current == BORLAND_FILE_OUTPUT) { borland_close_file(record); } else if (current != BORLAND_FILE_CLOSED) { atomic_store(&g_borland_io_result, IO_ERROR_NOT_CLOSED); return; } win16_write_u16(record, FILE_MODE_OFFSET, mode); win16_write_u16(record, FILE_BUFFER_POSITION_OFFSET, 0); win16_write_u16(record, FILE_BUFFER_END_OFFSET, 0); if (borland_call_file_procedure( record, FILE_OPEN_PROCEDURE_OFFSET) != 0) { win16_write_u16(record, FILE_MODE_OFFSET, BORLAND_FILE_CLOSED); } } /* 1020:0527/052c — five-byte mode-selecting tail entries. */ void borland_reset_file(Win16FarPtr record) { borland_open_file(record, BORLAND_FILE_INPUT); } void borland_rewrite_file(Win16FarPtr record) { borland_open_file(record, BORLAND_FILE_OUTPUT); } /* 1020:05d8 — DOS AH=3f buffered read. */ uint16_t borland_file_read_buffer(Win16FarPtr record) { uint16_t handle = win16_read_u16(record); uint16_t count = win16_read_u16(win16_far_add_offset(record, 4)); Win16FarPtr buffer = win16_read_far_pointer(record, 12); Win16DosResult result = win16_dos_read(handle, buffer, count); win16_write_u16(record, 10, result.carry ? 0 : result.ax); win16_write_u16(record, 8, 0); return result.carry ? result.ax : 0; } static Win16DosResult write_pending_bytes(Win16FarPtr record) { uint16_t count = win16_exchange_u16( win16_far_add_offset(record, 8), 0); uint16_t handle = win16_read_u16(record); Win16FarPtr buffer = win16_read_far_pointer(record, 12); Win16DosResult result = win16_dos_write(handle, buffer, count); if (!result.carry && result.ax != count) { result.ax = 0x65; result.carry = true; } return result; } /* 1020:0608 — buffered flush with short-write detection. */ uint16_t borland_file_flush_buffer(Win16FarPtr record) { Win16DosResult result = write_pending_bytes(record); return result.carry ? result.ax : 0; } /* 1020:062d — same DOS write, but only carry is an error. */ uint16_t borland_file_write_buffer(Win16FarPtr record) { uint16_t count = win16_exchange_u16( win16_far_add_offset(record, 8), 0); Win16DosResult result = win16_dos_write( win16_read_u16(record), win16_read_far_pointer(record, 12), count); return result.carry ? result.ax : 0; } /* 1020:064d — preserve DOS standard handles 0..4; close larger handles. */ uint16_t borland_file_close_handle(Win16FarPtr record) { uint16_t handle = win16_read_u16(record); if (handle <= 4) { return 0; } Win16DosResult result = win16_dos_close(handle); return result.carry ? result.ax : 0; } /* 1020:0751 — generic files must be in open sentinel mode 0xd7b3. */ bool borland_validate_open_file(Win16FarPtr record) { if (win16_read_u16(win16_far_add_offset(record, 2)) == 0xd7b3) { return true; } atomic_store(&g_borland_io_result, 0x67); return false; } /* 1020:0710 — DOS zero-byte write truncates at the current file position. */ void borland_truncate_file(Win16FarPtr record) { if (!borland_validate_open_file(record)) { return; } uint16_t indeterminate = win16_indeterminate_u16(); Win16DosResult result = win16_dos_write( win16_read_u16(record), win16_make_far_pointer(indeterminate, indeterminate), 0); if (result.carry) { atomic_store(&g_borland_io_result, result.ax); } } /* 1020:072c — close a valid generic file and always mark it closed. */ void borland_close_generic_file(Win16FarPtr record) { if (!borland_validate_open_file(record)) { return; } uint16_t handle = win16_read_u16(record); if (handle > 4) { Win16DosResult result = win16_dos_close(handle); if (result.carry) { atomic_store(&g_borland_io_result, result.ax); } } win16_write_u16(record, 2, BORLAND_FILE_CLOSED); } static void transfer_one_record( Win16FarPtr buffer, Win16FarPtr record, bool write) { if (!borland_validate_open_file(record)) { return; } uint16_t bytes = win16_read_u16(win16_far_add_offset(record, 4)); Win16DosResult result = write ? win16_dos_write(win16_read_u16(record), buffer, bytes) : win16_dos_read(win16_read_u16(record), buffer, bytes); if (result.carry) { atomic_store(&g_borland_io_result, result.ax); } else if (result.ax != bytes) { atomic_store(&g_borland_io_result, write ? 0x65 : 0x64); } } void borland_file_read_record(Win16FarPtr buffer, Win16FarPtr record) { transfer_one_record(buffer, record, false); } void borland_file_write_record(Win16FarPtr buffer, Win16FarPtr record) { transfer_one_record(buffer, record, true); } static void transfer_record_block( Win16FarPtr result_count, uint16_t requested_records, Win16FarPtr buffer, Win16FarPtr record, bool write) { uint16_t records_transferred = 0; if (borland_validate_open_file(record)) { uint16_t record_size = win16_read_u16(win16_far_add_offset(record, 4)); if (requested_records != 0) { uint16_t byte_count = (uint16_t)((uint32_t)requested_records * record_size); Win16DosResult result = write ? win16_dos_write( win16_read_u16(record), buffer, byte_count) : win16_dos_read( win16_read_u16(record), buffer, byte_count); if (result.carry) { atomic_store(&g_borland_io_result, result.ax); goto publish; } if (record_size == 0) { win16_integer_divide_fault(); } records_transferred = (uint16_t)(result.ax / record_size); } if (result_count == 0 && records_transferred != requested_records) { atomic_store(&g_borland_io_result, write ? 0x65 : 0x64); } } publish: if (result_count != 0) { win16_write_u16(result_count, 0, records_transferred); } } void borland_file_block_read( Win16FarPtr result_count, uint16_t requested_records, Win16FarPtr buffer, Win16FarPtr record) { transfer_record_block( result_count, requested_records, buffer, record, false); } void borland_file_block_write( Win16FarPtr result_count, uint16_t requested_records, Win16FarPtr buffer, Win16FarPtr record) { transfer_record_block( result_count, requested_records, buffer, record, true); } void borland_file_seek_record(uint32_t record_index, Win16FarPtr record) { if (!borland_validate_open_file(record)) { return; } uint16_t record_size = win16_read_u16(win16_far_add_offset(record, 4)); uint32_t byte_offset = record_index * record_size; Win16DosResult result = win16_dos_seek(win16_read_u16(record), byte_offset, 0); if (result.carry) { atomic_store(&g_borland_io_result, result.ax); } } static void open_or_create_generic_file( uint16_t record_size, Win16FarPtr record, bool create) { uint16_t mode = win16_read_u16(win16_far_add_offset(record, 2)); if (mode == 0xd7b3) { borland_close_generic_file(record); } else if (mode != BORLAND_FILE_CLOSED) { atomic_store(&g_borland_io_result, 0x66); return; } Win16FarPtr name = win16_far_add_offset(record, 48); Win16DosResult result; if (win16_read_u8(name) == 0) { result = (Win16DosResult){create ? 1 : 0, false}; } else { result = create ? win16_dos_create(name, 0) : win16_dos_open(name, g_borland_file_mode); if (result.carry) { atomic_store(&g_borland_io_result, result.ax); return; } } win16_write_u16(record, 2, 0xd7b3); win16_write_u16(record, 0, result.ax); win16_write_u16(record, 4, record_size); } void borland_open_generic_file(uint16_t record_size, Win16FarPtr record) { open_or_create_generic_file(record_size, record, false); } void borland_create_generic_file(uint16_t record_size, Win16FarPtr record) { open_or_create_generic_file(record_size, record, true); } /* * 1020:0c76 -- when Append opens an ordinary DOS file, inspect only its last * 128 bytes and remove the first DOS text EOF marker (Ctrl-Z) found there. * Every INT 21h error is deliberately ignored by the original helper. */ void borland_text_truncate_at_ctrl_z(Win16FarPtr record) { uint16_t handle = win16_read_u16(record); Win16DosExtendedResult end = win16_dos_seek_extended(handle, 0, 2); uint32_t file_size = ((uint32_t)end.dx << 16) | end.ax; uint32_t scan_start = file_size >= 128 ? file_size - 128 : 0; (void)win16_dos_seek_extended(handle, scan_start, 0); Win16FarPtr buffer = win16_far_add_offset(record, 0x80); Win16DosResult read = win16_dos_read(handle, buffer, 128); uint16_t bytes_read = read.carry ? 0 : read.ax; for (uint16_t index = 0; index != bytes_read; index++) { if (win16_read_u8(win16_far_add_offset(buffer, index)) != 0x1a) { continue; } /* CX:DX = FFFF:(index-bytes_read), exactly as the 16-bit SUB forms it. */ uint32_t relative_offset = 0xffff0000u | (uint16_t)(index - bytes_read); Win16DosExtendedResult position = win16_dos_seek_extended(handle, relative_offset, 2); Win16FarPtr ignored_buffer = win16_make_far_pointer( win16_far_selector(record), position.dx); (void)win16_dos_write(handle, ignored_buffer, 0); return; } } 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)); } /* * 1020:0be9 -- TextRec OpenFunc. The incoming mode selects Reset (input), * Rewrite (create output), or Append (read/write output). On success it * installs the relocated Borland buffer callbacks used by the generic text * dispatcher. DOS character devices use the unbuffered write callback in * both InOutFunc and FlushFunc; ordinary files use the buffered flush callback * in InOutFunc and a null FlushFunc. */ uint16_t borland_open_text_record(Win16FarPtr record) { enum { TEXTREC_MODE_OFFSET = 2, TEXTREC_NAME_OFFSET = 0x30, TEXTREC_IN_OUT_PROCEDURE_OFFSET = 0x14, TEXTREC_FLUSH_PROCEDURE_OFFSET = 0x18, TEXTREC_CLOSE_PROCEDURE_OFFSET = 0x1c, BORLAND_READ_BUFFER_OFFSET = 0x05d8, BORLAND_FLUSH_BUFFER_OFFSET = 0x0608, BORLAND_WRITE_BUFFER_OFFSET = 0x062d, BORLAND_CLOSE_HANDLE_OFFSET = 0x064d, }; uint16_t mode = win16_read_u16( win16_far_add_offset(record, TEXTREC_MODE_OFFSET)); uint16_t default_handle = mode == BORLAND_FILE_INPUT ? 0 : 1; win16_write_u16(record, 0, default_handle); Win16FarPtr name = win16_far_add_offset(record, TEXTREC_NAME_OFFSET); if (win16_read_u8(name) != 0) { Win16DosResult opened; if (mode == BORLAND_FILE_INPUT) { opened = win16_dos_open(name, 0); } else if (mode == BORLAND_TEXT_APPEND) { opened = win16_dos_open(name, 2); } else { opened = win16_dos_create(name, 0); } if (opened.carry) { return opened.ax; } win16_write_u16(record, 0, opened.ax); } Win16FarPtr in_out_procedure = win16_relocated_code_pointer( 5, BORLAND_READ_BUFFER_OFFSET); Win16FarPtr flush_procedure = 0; if (mode != BORLAND_FILE_INPUT) { Win16DosExtendedResult device = win16_dos_get_device_info( win16_read_u16(record)); if ((device.dx & 0x0080u) != 0) { in_out_procedure = win16_relocated_code_pointer( 5, BORLAND_WRITE_BUFFER_OFFSET); flush_procedure = in_out_procedure; } else { if (mode == BORLAND_TEXT_APPEND) { borland_text_truncate_at_ctrl_z(record); } in_out_procedure = win16_relocated_code_pointer( 5, BORLAND_FLUSH_BUFFER_OFFSET); } win16_write_u16(record, TEXTREC_MODE_OFFSET, BORLAND_FILE_OUTPUT); } write_text_procedure( record, TEXTREC_IN_OUT_PROCEDURE_OFFSET, in_out_procedure); write_text_procedure( record, TEXTREC_FLUSH_PROCEDURE_OFFSET, flush_procedure); write_text_procedure( record, TEXTREC_CLOSE_PROCEDURE_OFFSET, win16_relocated_code_pointer(5, BORLAND_CLOSE_HANDLE_OFFSET)); return 0; } /* 1008:2374 — stored FileProc callback; receiver ignored, AX=0. */ uint16_t tdkpin_file_close_noop(Win16FarPtr record) { (void)record; return 0; }