; PAGE  ,132
;=============================================================================
;  FILBRK.ASM  -  TSR for DOSBox-X debugging of WordPerfect 6 document reads
;
;  Purpose:
;    1) Freeze system date/time (AH=2Ah / AH=2Ch) so encryption salt is stable
;    2) Watch handle-based opens of *.WP / *.WPD files (AH=3Dh)
;    3) On every Read (AH=3Fh) of a watched handle, call the real DOS read,
;       then execute INT 3 so DOSBox-X debugger stops with the buffer filled
;    4) Clear the watch on Close (AH=3Eh)
;
;  Why handle API (not FCB):
;    DOSBox log of WP6 opening an encrypted .WP shows AH=3D/3F/42/3E only.
;    FCB functions AH=0Fh/14h never appear.
;
;  Usage (DOSBox-X):
;    FILBRK.COM          ; load TSR
;    WP                  ; run WordPerfect
;    open encrypted .WP, type password, Enter
;    debugger traps after each document read (CS:IP = return into WP)
;    dump DS:DX for CX bytes, then Continue
;
;  Assemble (examples):
;    TASM  FILBRK.ASM
;    TLINK /t FILBRK.OBJ
;    or:  WASM FILBRK.ASM  /  WASM-compatible toolchain producing .COM
;
;  Notes:
;    - INT 3 fires AFTER the read succeeds, so DS:DX already holds data
;    - First read is often the 16-byte WPC header; skip it in the debugger
;      or set SKIP_FIRST_READS below
;    - Overlay files (WP.FIL) are not .WP, so they are not watched
;=============================================================================

PROGRAM SEGMENT
        ASSUME  CS:PROGRAM, DS:PROGRAM, ES:PROGRAM
        ORG     100h

START:  JMP     INSTALL

;-----------------------------------------------------------------------------
; Resident data
;-----------------------------------------------------------------------------
OLD_INT3        DD      0               ; original INT 3 vector
OLD_INT21       DD      0               ; original INT 21 vector

WATCH_HANDLE    DW      0FFFFh          ; BX handle of watched .WP, or FFFFh
WATCH_DS        DW      0               ; DS at open time (optional check)

READ_COUNT      DW      0               ; number of reads on watched handle
SKIP_FIRST_READS EQU    1               ; skip this many reads before INT 3
                                        ; (1 = skip header-sized first read)

; Frozen date/time returned for AH=2Ah / 2Ch
; Adjust if you need a specific salt; zeros are fine for reproducibility
FROZEN_CX       DW      0               ; 2Ch: CH=hour CL=minute
FROZEN_DX       DW      0               ; 2Ch: DH=second DL=1/100s
FROZEN_DATE_CX  DW      0               ; 2Ah: CX=year
FROZEN_DATE_DX  DW      0               ; 2Ah: DH=month DL=day

;-----------------------------------------------------------------------------
; INT 21 handler
;-----------------------------------------------------------------------------
NEW_INT21 PROC  FAR
        ;--- freeze time/date ------------------------------------------------
        CMP     AH, 2Ch                 ; Get system time
        JE      DO_TIME
        CMP     AH, 2Ah                 ; Get system date
        JE      DO_DATE

        ;--- open file (handle) ----------------------------------------------
        CMP     AH, 3Dh
        JE      DO_OPEN

        ;--- read file (handle) ----------------------------------------------
        CMP     AH, 3Fh
        JE      DO_READ

        ;--- close file (handle) ---------------------------------------------
        ; short JE cannot reach DO_CLOSE (beyond ±128); use near JMP
        CMP     AH, 3Eh
        JNE     PASS
        JMP     DO_CLOSE

        ; everything else -> original INT 21
PASS:
        JMP     DWORD PTR CS:[OLD_INT21]

;----- AH=2Ch Get Time -------------------------------------------------------
DO_TIME:
        MOV     CX, CS:[FROZEN_CX]
        MOV     DX, CS:[FROZEN_DX]
        XOR     AL, AL                  ; success
        IRET

;----- AH=2Ah Get Date -------------------------------------------------------
DO_DATE:
        MOV     CX, CS:[FROZEN_DATE_CX]
        MOV     DX, CS:[FROZEN_DATE_DX]
        XOR     AL, AL
        IRET

;----- AH=3Dh Open -----------------------------------------------------------
; Entry: DS:DX -> ASCIZ filename, AL = access mode
; Exit:  CF clear, AX = handle on success
DO_OPEN:
        PUSH    AX
        PUSH    BX
        PUSH    SI
        PUSH    DS

        ; Scan filename for ".WP" or ".WPD" (case-insensitive)
        MOV     SI, DX
        CLD
SCAN_NAME:
        LODSB
        OR      AL, AL
        JZ      OPEN_NOT_WP             ; end of string, no match
        CMP     AL, '.'
        JNE     SCAN_NAME
        ; possible extension
        LODSB
        CALL    UPCASE
        CMP     AL, 'W'
        JNE     SCAN_NAME
        LODSB
        CALL    UPCASE
        CMP     AL, 'P'
        JNE     SCAN_NAME
        ; optional third letter: D (WPD) or end / other
        LODSB
        OR      AL, AL
        JE      OPEN_IS_WP              ; ".WP",0
        CALL    UPCASE
        CMP     AL, 'D'
        JE      OPEN_IS_WP              ; ".WPD"
        ; not WP - continue scan from current (rare path)
        JMP     SCAN_NAME

OPEN_IS_WP:
        POP     DS
        POP     SI
        POP     BX
        POP     AX
        ; call original open
        PUSHF
        CALL    DWORD PTR CS:[OLD_INT21]
        JC      OPEN_FAIL
        ; AX = handle
        MOV     CS:[WATCH_HANDLE], AX
        MOV     CS:[READ_COUNT], 0
        ; CF already clear
OPEN_FAIL:
        RETF    2                       ; return to caller, keep flags

OPEN_NOT_WP:
        POP     DS
        POP     SI
        POP     BX
        POP     AX
        JMP     PASS

;----- AH=3Fh Read -----------------------------------------------------------
; Entry: BX = handle, CX = count, DS:DX = buffer
DO_READ:
        CMP     BX, CS:[WATCH_HANDLE]
        JE      DO_READ_MATCH
        JMP     PASS                    ; near jump (short JNE may be out of range)

DO_READ_MATCH:
        ; Call original read first so buffer is filled
        PUSHF
        CALL    DWORD PTR CS:[OLD_INT21]
        JC      READ_DONE_FAIL          ; error - do not break

        ; AX = bytes read
        INC     WORD PTR CS:[READ_COUNT]
        MOV     BX, CS:[READ_COUNT]     ; BX free enough here; handle already matched
        CMP     BX, SKIP_FIRST_READS
        JBE     READ_DONE_OK            ; still in skip zone

        ; Optional: ignore very small reads (header peeks)
        ; CMP  AX, 16
        ; JBE  READ_DONE_OK

        ; Break into DOSBox-X debugger.
        ; After INT 3: inspect DS:DX for AX bytes (data just read),
        ; then Continue. CS:IP will be inside WP on next step.

        ;INT     3			;int 3 might be blocked
	PUSHF
        CALL    DWORD PTR CS:[OLD_INT3]

READ_DONE_OK:
        CLC
READ_DONE_FAIL:
        RETF    2

;----- AH=3Eh Close ----------------------------------------------------------
DO_CLOSE:
        CMP     BX, CS:[WATCH_HANDLE]
        JE      DO_CLOSE_MATCH
        JMP     PASS                    ; near jump (short JNE out of range)

DO_CLOSE_MATCH:
        MOV     WORD PTR CS:[WATCH_HANDLE], 0FFFFh
        MOV     WORD PTR CS:[READ_COUNT], 0
        JMP     PASS

;----- helper: AL = upcase ASCII letter --------------------------------------
UPCASE  PROC    NEAR
        CMP     AL, 'a'
        JB      UPCASE_DONE
        CMP     AL, 'z'
        JA      UPCASE_DONE
        SUB     AL, 20h
UPCASE_DONE:
        RET
UPCASE  ENDP

NEW_INT21 ENDP

;=============================================================================
; Installer (not resident)
;=============================================================================
INSTALL PROC    NEAR
        MOV     AX, CS
        MOV     DS, AX

        MOV     DX, OFFSET MSG_BANNER
        MOV     AH, 9
        INT     21h

        ; Save old INT 3
        MOV     AX, 3503h
        INT     21h
        MOV     WORD PTR [OLD_INT3], BX
        MOV     WORD PTR [OLD_INT3], ES

        ; Save old INT 21
        MOV     AX, 3521h
        INT     21h
        MOV     WORD PTR [OLD_INT21], BX
        MOV     WORD PTR [OLD_INT21+2], ES

        ; Install our handler
        MOV     DX, OFFSET NEW_INT21
        MOV     AX, 2521h
        INT     21h

        MOV     DX, OFFSET MSG_OK
        MOV     AH, 9
        INT     21h

        ; TSR: keep resident from PSP to end of NEW_INT21 + data
        MOV     DX, OFFSET INSTALL      ; first byte NOT kept
        ADD     DX, 15
        MOV     CL, 4
        SHR     DX, CL                  ; paragraphs
        MOV     AX, 3100h               ; terminate and stay resident
        INT     21h

INSTALL ENDP

MSG_BANNER DB  0Dh,0Ah
        DB  'FILBRK: freeze time + INT3 on *.WP handle reads',0Dh,0Ah
        DB  'Hook INT21 AH=2A/2C/3D/3F/3E. Load before WP.',0Dh,0Ah
        DB  '$'
MSG_OK  DB  'Installed.',0Dh,0Ah,'$'

PROGRAM ENDS
        END     START
