Last active
August 31, 2021 09:41
-
-
Save thentenaar/7050651 to your computer and use it in GitHub Desktop.
DOS 6+ Fix for PC/GEOS 1.x (and an example of an extremely simply file patcher.)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; | |
; DOS 6+ Fix for PC/GEOS 1.x | |
; (and an example of an extremely simply file patcher) | |
; | |
; Copyright (C) 2013 Tim Hentenaar, http://hentenaar.com | |
; All rights reserved. | |
; | |
; Redistribution and use in source and binary forms, with or without | |
; modification, are permitted provided that the following conditions are met: | |
; | |
; 1. Redistributions of source code must retain the above copyright notice, this | |
; list of conditions and the following disclaimer. | |
; 2. Redistributions in binary form must reproduce the above copyright notice, | |
; this list of conditions and the following disclaimer in the documentation | |
; and/or other materials provided with the distribution. | |
; | |
; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | |
; ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
; | |
; Assemble with TASM: | |
; tasm geos6fix.asm | |
; tlink geos6fix.obj | |
; | |
; For an explanation of what this patch does, see: | |
; http://hentenaar.com/geos1-and-dos6 | |
; | |
.model tiny | |
.stack 16 | |
.data | |
target db 'KERNEL.EXE',0 | |
patch_count db 11 | |
; | |
; The format of these patches is as follows: | |
; | |
; Offset Length Description | |
; ----------------------------------------------------------- | |
; 0 4 File offset to patch | |
; 4 1 Number of bytes to patch | |
; 5 1 Verify? 1=Yes, 0=No | |
; 6 * Existing Bytes (for verification) or Patch | |
; * * Patch | |
; | |
patches: | |
; Append the dos_version_check subroutine -> seg004:1CDB | |
dd 11C0Bh | |
db 38 | |
db 0 ; Don't verify (since we write past the end of the file.) | |
db 50h ; push ax | |
db 0A1h, 0ABh, 05h ; mov ax, word ptr [dos_version] | |
db 3Ch, 06h ; cmp al, 6 | |
db 7Ch, 06h ; jl $+8 | |
db 0C7h, 06h, 0ABh, 05h, 05h, 00h ; mov word ptr [dos_version], 5 | |
db 58h ; pop ax | |
db 0E9h, 44h, 0F0h ; jmp init_drive_module | |
db 0C3h ; ret | |
dw 9090h ; 2x nop (padding) | |
dw 0 ; 1CF0 | |
dw 0 ; 1CF2 | |
db 13 dup(0) ; Segment padding bytes (til end of paragraph @ 1CFF) | |
; seg004:0220 call init_drive_module -> call dos_version_check | |
dd 10151h | |
db 2 | |
db 1 | |
db 0Eh, 0Bh | |
db 0B8h, 1Ah | |
; seg004:048E mov cs:tmp_address.offset, si (Correct offset to 1CF0) | |
dd 103C1h | |
db 2 | |
db 1 | |
db 0E0h, 1Ch | |
db 0F0h, 1Ch | |
; seg004:0493 mov cs:temp_address.segment, ds (Correct offset to 1CF2) | |
dd 103C6h | |
db 2 | |
db 1 | |
db 0E2h, 1Ch | |
db 0F2h, 1Ch | |
; seg004:04C3 lds si, dword ptr cs:temp_address (Correct offset to 1CF0) | |
dd 103F6h | |
db 2 | |
db 1 | |
db 0E0h, 1Ch | |
db 0F0h, 1Ch | |
; seg004:0543 lds si, dword ptr cs:temp_address (Correct offset to 1CF0) | |
dd 10476h | |
db 2 | |
db 1 | |
db 0E0h, 1Ch | |
db 0F0h, 1Ch | |
; seg004:0913 mov dx, offset geos_ini_buffer (Correct offset to 1CF0) | |
dd 10844h | |
db 2 | |
db 1 | |
db 0E0h, 1Ch | |
db 0F0h, 1Ch | |
; seg004:0928 mov byte ptr cs:geos_ini_buffer[bx], 1Ah (Correct offset to 1CF0) | |
dd 1085Bh | |
db 2 | |
db 1 | |
db 0E0h, 1Ch | |
db 0F0h, 1Ch | |
; seg004:099D mov ax, offset geos_ini_buffer (Correct offset to 1CF0) | |
dd 108CEh | |
db 2 | |
db 1 | |
db 0E0h, 1Ch | |
db 0F0h, 1Ch | |
; Fix the routine that relocates seg004 so that it includes | |
; the new subroutine. This size should be paragraph aligned. | |
; | |
; seg004:0046 mov cx, SEG004_SIZE -> mov cx, SEG004_SIZE + 16 | |
dd 0FF67h | |
db 2 | |
db 1 | |
db 0E0h, 1Ch | |
db 0F0h, 1Ch | |
; Adjust the number of bytes at the end of the last page in the | |
; EXE header to account for the new subroutine / padding bytes. | |
dd 02h | |
db 2 | |
db 1 | |
db 20h, 00h | |
db 30h, 00h | |
; Messages | |
status db 'Applying Patch: ' | |
run dw 0 | |
db '/' | |
total dw 0 | |
db '...', 0Dh, 0Ah, '$' | |
open_failed db 'Open failed', 0Dh, 0Ah, '$' | |
seek_failed db 'Seek failed', 0Dh, 0Ah, '$' | |
read_failed db 'Read failed', 0Dh, 0Ah, '$' | |
verify_failed db 'Verify failed', 0Dh, 0Ah, '$' | |
write_failed db 'Write failed', 0Dh, 0Ah, '$' | |
patch_successful db 'Patch successful', 0Dh, 0Ah, '$' | |
patch_too_big db 'Patch too big', 0Dh, 0Ah, '$' | |
too_many_patches db 'Too many patches', 0Dh, 0Ah, '$' | |
MSG_OPEN_FAILED equ 0 | |
MSG_SEEK_FAILED equ 1 | |
MSG_READ_FAILED equ 2 | |
MSG_VERIFY_FAILED equ 3 | |
MSG_WRITE_FAILED equ 4 | |
MSG_PATCH_SUCCESSFUL equ 5 | |
MSG_PATCH_TOO_BIG equ 6 | |
MSG_TOO_MANY_PATCHES equ 7 | |
MSG_TABLE_SIZE equ 8 | |
msg_table: | |
dw offset open_failed | |
dw offset seek_failed | |
dw offset read_failed | |
dw offset verify_failed | |
dw offset write_failed | |
dw offset patch_successful | |
dw offset patch_too_big | |
dw offset too_many_patches | |
; I/O buffer | |
MAX_PATCH_SIZE equ 50 | |
buffer db 50 dup(?) | |
.code | |
.8086 | |
; | |
; Convert a binary number <= 99 to its ASCII representation | |
; | |
; Input: | |
; AL: Number to convert | |
; | |
; Output: | |
; AX: Ascii representation of number | |
; | |
itoa: | |
push bx | |
push dx | |
; Check if the number is >= 99 | |
xor ah, ah | |
cmp al, 99 | |
jge .number_too_large | |
; Convert the number in al to ASCII by dividing by 10 | |
; and adding 30h. | |
mov bl, 10 | |
div bl | |
add ax, 3030h | |
jmp .itoa_return | |
.number_too_large: | |
mov ax, 3939h | |
.itoa_return: | |
pop dx | |
pop bx | |
ret | |
; | |
; Verify the bytes we're about to overwrite | |
; with our patch byte by byte. | |
; | |
; Input: | |
; BX - File Handle | |
; CX - Count | |
; | |
; Clobbers: AX, CX, DX, DI | |
; | |
verify_patch: | |
; Read the bytes | |
mov dx, offset buffer | |
mov ax, 3F00h | |
int 21h | |
cmp ax, cx | |
je .verify | |
mov dx, MSG_READ_FAILED | |
call print_message_and_exit | |
.verify: | |
; Verify (byte-by-byte) | |
push cx | |
mov di, dx | |
.verify_loop: | |
lodsb | |
cmp al, [di] | |
jne .verify_failed | |
inc di | |
loop .verify_loop | |
.verify_done: | |
pop cx | |
mov ax, cx | |
ret | |
.verify_failed: | |
mov dx, MSG_VERIFY_FAILED | |
call print_message_and_exit | |
; | |
; Write the patch | |
; | |
; Input: | |
; AX: Number of bytes to write | |
; BX: File Handle | |
; CX/DX: File offset to patch | |
; SI: Pointer to bytes to write | |
; | |
write_patch: | |
; Seek back to where we want to patch | |
push ax | |
mov ax, 4200h | |
int 21h | |
jnc .write_patch | |
mov dx, MSG_SEEK_FAILED | |
call print_message_and_exit | |
.write_patch: | |
; Now, write our patch | |
pop cx | |
mov dx, si | |
mov ax, 4000h | |
int 21h | |
jc .write_failed | |
cmp ax, cx | |
jne .write_failed | |
; Loop | |
add si, ax | |
ret | |
.write_failed: | |
mov dx, MSG_WRITE_FAILED | |
call print_message_and_exit | |
; | |
; Print a message and exit | |
; | |
; Input: | |
; BX: File Handle | |
; DX: Index of message in message_table | |
; | |
; Doesn't return | |
; | |
print_message_and_exit: | |
push bx | |
mov bx, dx | |
xor bh, bh | |
; Make sure it doesn't overrun the table | |
cmp bl, MSG_TABLE_SIZE | |
jge .close_and_exit | |
; Print the message | |
shl bl, 1 | |
mov dx, word ptr msg_table[bx] | |
mov ax, 0900h | |
int 21h | |
.close_and_exit: | |
pop bx | |
test bx, bx | |
jz .do_exit | |
; Close the file | |
mov ax, 3E00h | |
int 21h | |
.do_exit: | |
mov ax, 4C00h | |
int 21h | |
; | |
; The main routine | |
; | |
start: | |
; Ensure ds points to our data segment (.data) | |
mov ax, @data | |
mov ds, ax | |
; Prepare our status message (and check our patch count) | |
xor ax, ax | |
mov si, offset patches | |
mov cl, [patch_count] | |
mov al, cl | |
cmp al, 100 | |
jl .open_file | |
mov dx, MSG_TOO_MANY_PATCHES | |
call print_message_and_exit | |
.open_file: | |
call itoa | |
mov di, offset total | |
mov [di], ax | |
; Open the target file (r/w) | |
mov ax, 3D02h | |
mov dx, offset target | |
int 21h | |
mov bx, ax | |
jnc .patch_loop | |
mov dx, MSG_OPEN_FAILED | |
call print_message_and_exit | |
.patch_loop: | |
cld | |
push cx | |
; Print the status message | |
mov al, [patch_count] | |
sub al, cl | |
inc al | |
call itoa | |
mov [run], ax | |
mov dx, offset status | |
mov ax, 0900h | |
int 21h | |
; Load the offset | |
lodsw | |
mov dx, ax | |
lodsw | |
mov cx, ax | |
push cx | |
push dx | |
; Seek | |
mov ax, 4200h | |
int 21h | |
jnc .check_patch_size | |
mov dx, MSG_SEEK_FAILED | |
call print_message_and_exit | |
.check_patch_size: | |
; Load the count | |
lodsb | |
xor cx, cx | |
mov cl, al | |
; Make sure it's not too big | |
cmp cl, MAX_PATCH_SIZE | |
jle .check_verify | |
mov dx, MSG_PATCH_TOO_BIG | |
call print_message_and_exit | |
.check_verify: | |
; Check if we want verification | |
lodsb | |
test al, al | |
mov ax, cx | |
jz .do_write | |
call verify_patch | |
.do_write: | |
pop dx | |
pop cx | |
call write_patch | |
pop cx | |
loop .patch_loop | |
mov dx, MSG_PATCH_SUCCESSFUL | |
call print_message_and_exit | |
end start | |
; vi:set ts=8 sw=8 expandtab sta ft=tasm: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment