; 小貓中文 - XMS helpers, interrupt stubs and BIOS wrappers ; Copyright (C) 2026 HelloSun ; Author: HelloSun First version: 1.0, 2026-10-25 ; SPDX-License-Identifier: GPL-3.0-only ; Additional terms under GPLv3 section 7(b): the author attribution "HelloSun" and this copyright ; notice must be preserved in all copies and modified versions, including the notice the program ; displays when it starts. See LICENSE. ; See ../LICENSE for license terms. ; ; Watcom large model, register calling convention: args AX,DX,BX,CX; far return. ; Every helper preserves all registers except AX (return value). ; All variables live in the code segment so interrupt handlers never depend on DS. bits 16 segment _TEXT public class=CODE global xms_init_ global xms_query_ global xms_largest_ global xms_total_ global xms_alloc_ global xms_free_ global xms_move_ global install_hooks_ global save_vectors_ global set_handlers_ global is_installed_ global bios_peek_ global bios_getkey_ global bios10_call_ global get_rom_font_ global cpu_idle_ xms_entry: dd 0 xms_freekb: dw 0 xms_totalkb: dw 0 old16: dd 0 old10: dd 0 old2f: dd 0 c_key: dd 0 c_vid: dd 0 s_ss: dw 0 s_sp: dw 0 busy16: db 0 busy10: db 0 res10: dw 0 %macro SAVE 0 push bx push cx push dx push si push di push bp push ds push es %endmacro %macro REST 0 pop es pop ds pop bp pop di pop si pop dx pop cx pop bx %endmacro ; ---------------------------------------------------------------- XMS xms_init_: SAVE mov ax, 0x4310 int 0x2F mov [cs:xms_entry], bx mov [cs:xms_entry+2], es REST mov ax, 1 retf xms_query_: SAVE mov ah, 0x08 call far [cs:xms_entry] cld mov [cs:xms_freekb], ax mov [cs:xms_totalkb], dx REST retf xms_largest_: mov ax, [cs:xms_freekb] retf xms_total_: mov ax, [cs:xms_totalkb] retf ; unsigned xms_alloc(unsigned kb): returns handle or 0 xms_alloc_: SAVE mov dx, ax mov ah, 0x09 call far [cs:xms_entry] cld cmp ax, 1 jne .fail mov ax, dx jmp .out .fail: xor ax, ax .out: REST retf xms_free_: SAVE mov dx, ax mov ah, 0x0A call far [cs:xms_entry] cld REST retf ; unsigned xms_move(unsigned seg, unsigned off): move-struct at seg:off xms_move_: SAVE mov si, dx mov ds, ax mov ah, 0x0B call far [cs:xms_entry] cld REST retf ; ---------------------------------------------------------------- BIOS helpers ; unsigned bios_peek(void): key (AX) of pending key or 0 when none bios_peek_: SAVE mov ah, 1 pushf call far [cs:old16] jnz .have xor ax, ax .have: REST retf ; unsigned bios_getkey(void): wait for key bios_getkey_: SAVE xor ah, ah pushf call far [cs:old16] REST retf ; unsigned bios10_call(unsigned ax, unsigned bx): call previous INT 10h, returns AX bios10_call_: push cx push dx push si push di push bp push ds push es mov bx, dx ; second argument arrives in DX pushf call far [cs:old10] pop es pop ds pop bp pop di pop si pop dx pop cx retf ; void far *get_rom_font(void): ROM 8x16 font pointer (DX:AX), via INT 10h AX=1130h BH=6 get_rom_font_: push bx push cx push si push di push bp push ds push es mov ax, 0x1130 mov bx, 0x0600 int 0x10 mov ax, bp mov dx, es pop es pop ds pop bp pop di pop si pop cx pop bx retf ; void cpu_idle(void): wait for next interrupt cpu_idle_: sti hlt retf ; void set_handlers(key_off, key_seg, vid_off, vid_seg) set_handlers_: mov [cs:c_key], ax mov [cs:c_key+2], dx mov [cs:c_vid], bx mov [cs:c_vid+2], cx retf ; unsigned is_installed(void) is_installed_: SAVE mov ax, 0xD7A0 int 0x2F cmp ax, 0x584D mov ax, 0 jne .no mov ax, 1 .no: REST retf ; void save_vectors(void): remember the previous INT 10h/16h/2Fh handlers save_vectors_: SAVE mov ax, 0x3516 int 0x21 mov [cs:old16], bx mov [cs:old16+2], es mov ax, 0x3510 int 0x21 mov [cs:old10], bx mov [cs:old10+2], es mov ax, 0x352F int 0x21 mov [cs:old2f], bx mov [cs:old2f+2], es REST retf ; void install_hooks(void) install_hooks_: SAVE push cs pop ds mov dx, int16_hook mov ax, 0x2516 int 0x21 mov dx, int10_hook mov ax, 0x2510 int 0x21 mov dx, int2f_hook mov ax, 0x252F int 0x21 REST retf ; ---------------------------------------------------------------- INT 2Fh int2f_hook: cmp ax, 0xD7A0 jne .chain mov ax, 0x584D iret .chain: jmp far [cs:old2f] ; ---------------------------------------------------------------- INT 16h int16_hook: cmp ah, 0 je .mine cmp ah, 1 je .mine cmp ah, 0x10 je .mine cmp ah, 0x11 je .mine .chain: jmp far [cs:old16] .mine: cmp byte [cs:busy16], 0 jne .chain mov byte [cs:busy16], 1 SAVE mov [cs:s_ss], ss mov [cs:s_sp], sp mov bx, cs cli mov ss, bx mov sp, stack_top sti cld call far [cs:c_key] cli mov ss, [cs:s_ss] mov sp, [cs:s_sp] sti REST mov byte [cs:busy16], 0 or ax, ax retf 2 ; ---------------------------------------------------------------- INT 10h ; The C handler receives a far pointer to the saved register frame ; (es ds bp di si dx cx bx ax ip cs flags) and returns 0 = handled, 1 = chain. int10_hook: cmp byte [cs:busy10], 0 jne .chain mov byte [cs:busy10], 1 push ax push bx push cx push dx push si push di push bp push ds push es mov [cs:s_ss], ss mov [cs:s_sp], sp mov ax, cs cli mov ss, ax mov sp, stack_top sti cld mov ax, [cs:s_sp] mov dx, [cs:s_ss] call far [cs:c_vid] mov [cs:res10], ax cli mov ss, [cs:s_ss] mov sp, [cs:s_sp] sti pop es pop ds pop bp pop di pop si pop dx pop cx pop bx pop ax mov byte [cs:busy10], 0 cmp word [cs:res10], 0 jne .chain iret .chain: jmp far [cs:old10] ; private stack for interrupt work stack: times 2048 db 0 stack_top: