.code16
.intel_syntax noprefix
.section .text
.global _start

.equ PSP_COMMAND_LENGTH, 0x80
.equ PSP_COMMAND_TAIL, 0x81
.equ SCRATCH_BYTES, 260

/*
 * RIALIAS.COM - session-scoped DOS pathname drive aliases.
 *
 * Mapping data is supplied on the command line. For example, /A=C /B=D maps
 * explicit A: paths to C: and explicit B: paths to D:. There are no built-in
 * source or target drives.
 *
 * The INT 21h hook copies mapped paths into resident scratch storage. It never
 * edits the caller's pathname buffer. Unsupported APIs are chained unchanged.
 */
_start:
    push cs
    pop ds
    xor bp, bp
    xor ch, ch
    mov cl, byte ptr [PSP_COMMAND_LENGTH]
    mov si, PSP_COMMAND_TAIL

parse_next:
    jcxz parse_done
    mov al, byte ptr [si]
    cmp al, ' '
    je consume_space
    cmp al, 9
    je consume_space
    cmp al, '/'
    jne parse_source
    inc si
    dec cx
    jcxz parse_error

parse_source:
    cmp cx, 3
    jb parse_error
    mov al, byte ptr [si]
    and al, 0xdf
    cmp al, 'A'
    jb parse_error
    cmp al, 'Z'
    ja parse_error
    sub al, 'A'
    xor bx, bx
    mov bl, al
    inc si
    dec cx
    cmp byte ptr [si], '='
    jne parse_error
    inc si
    dec cx
    mov al, byte ptr [si]
    and al, 0xdf
    cmp al, 'A'
    jb parse_error
    cmp al, 'Z'
    ja parse_error
    mov byte ptr cs:[drive_map + bx], al
    inc bp
    inc si
    dec cx
    jcxz parse_done
    mov al, byte ptr [si]
    cmp al, ' '
    je consume_space
    cmp al, 9
    jne parse_error

consume_space:
    inc si
    dec cx
    jmp parse_next

parse_done:
    test bp, bp
    jz parse_error
    mov ax, 0x3521
    int 0x21
    mov word ptr cs:[old_int21], bx
    mov word ptr cs:[old_int21 + 2], es
    push cs
    pop ds
    mov dx, offset int21_handler
    mov ax, 0x2521
    int 0x21
    mov dx, offset resident_end
    add dx, 15
    shr dx, 4
    mov ax, 0x3100
    int 0x21

parse_error:
    mov ax, 0x4c02
    int 0x21

int21_handler:
    pushf
    cmp byte ptr cs:[busy], 0
    jne chain_unchanged
    cmp ah, 0x39                 /* mkdir DS:DX */
    je inspect_dx_path
    cmp ah, 0x3a                 /* rmdir DS:DX */
    je inspect_dx_path
    cmp ah, 0x3b                 /* chdir DS:DX */
    je inspect_dx_path
    cmp ah, 0x3c                 /* create/truncate DS:DX */
    je inspect_dx_path
    cmp ah, 0x3d                 /* open DS:DX */
    je inspect_dx_path
    cmp ah, 0x41                 /* unlink DS:DX */
    je inspect_dx_path
    cmp ah, 0x43                 /* get/set attributes DS:DX */
    je inspect_dx_path
    cmp ah, 0x4b                 /* exec DS:DX */
    je inspect_dx_path
    cmp ah, 0x4e                 /* find first DS:DX */
    je inspect_dx_path
    cmp ah, 0x56                 /* rename DS:DX to ES:DI */
    je inspect_rename_paths
    cmp ah, 0x5b                 /* create new DS:DX */
    je inspect_dx_path
    cmp ah, 0x60                 /* truename DS:SI to ES:DI */
    je inspect_si_path
    cmp ah, 0x6c                 /* extended open DS:SI */
    je inspect_si_path

chain_unchanged:
    popf
    jmp DWORD PTR cs:[old_int21]

inspect_dx_path:
    push ax
    push bx
    push cx
    push si
    push di
    mov si, dx
    mov di, offset scratch_one
    call copy_mapped_path
    pop di
    pop si
    pop cx
    pop bx
    pop ax
    jc dx_path_is_mapped
    jmp chain_unchanged

dx_path_is_mapped:
    popf
    push bp
    mov bp, sp
    push ds
    push dx
    push cs
    pop ds
    mov dx, offset scratch_one
    mov byte ptr cs:[busy], 1
    pushf
    call DWORD PTR cs:[old_int21]
    pushf
    pop word ptr ss:[bp + 6]
    mov byte ptr cs:[busy], 0
    pop dx
    pop ds
    pop bp
    iret

inspect_si_path:
    push ax
    push bx
    push cx
    push si
    push di
    mov di, offset scratch_one
    call copy_mapped_path
    pop di
    pop si
    pop cx
    pop bx
    pop ax
    jc si_path_is_mapped
    jmp chain_unchanged

si_path_is_mapped:
    popf
    push bp
    mov bp, sp
    push ds
    push si
    push cs
    pop ds
    mov si, offset scratch_one
    mov byte ptr cs:[busy], 1
    pushf
    call DWORD PTR cs:[old_int21]
    pushf
    pop word ptr ss:[bp + 6]
    mov byte ptr cs:[busy], 0
    pop si
    pop ds
    pop bp
    iret

inspect_rename_paths:
    mov byte ptr cs:[source_mapped], 0
    mov byte ptr cs:[target_mapped], 0
    push ax
    push bx
    push cx
    push si
    push di
    push bp
    mov bp, di                  /* preserve caller's ES:DI target offset */
    mov si, dx
    mov di, offset scratch_one
    call copy_mapped_path
    jnc inspect_rename_target
    mov byte ptr cs:[source_mapped], 1
inspect_rename_target:
    push ds
    push es
    pop ds
    mov si, bp
    mov di, offset scratch_two
    call copy_mapped_path
    pop ds
    jnc rename_copy_done
    mov byte ptr cs:[target_mapped], 1
rename_copy_done:
    pop bp
    pop di
    pop si
    pop cx
    pop bx
    pop ax
    cmp byte ptr cs:[source_mapped], 0
    jne rename_has_mapping
    cmp byte ptr cs:[target_mapped], 0
    je chain_unchanged
rename_has_mapping:
    popf
    push bp
    mov bp, sp
    push ds
    push dx
    push es
    push di
    cmp byte ptr cs:[source_mapped], 0
    je rename_source_ready
    push cs
    pop ds
    mov dx, offset scratch_one
rename_source_ready:
    cmp byte ptr cs:[target_mapped], 0
    je rename_target_ready
    push cs
    pop es
    mov di, offset scratch_two
rename_target_ready:
    mov byte ptr cs:[busy], 1
    pushf
    call DWORD PTR cs:[old_int21]
    pushf
    pop word ptr ss:[bp + 6]
    mov byte ptr cs:[busy], 0
    pop di
    pop es
    pop dx
    pop ds
    pop bp
    iret

/* DS:SI = caller path, DI = resident scratch destination. CF=1 if copied. */
copy_mapped_path:
    push ax
    push bx
    push cx
    push si
    push di
    cmp byte ptr [si + 1], ':'
    jne copy_failed
    mov al, byte ptr [si]
    and al, 0xdf
    cmp al, 'A'
    jb copy_failed
    cmp al, 'Z'
    ja copy_failed
    sub al, 'A'
    xor bx, bx
    mov bl, al
    mov ah, byte ptr cs:[drive_map + bx]
    test ah, ah
    jz copy_failed
    mov byte ptr cs:[di], ah
    inc di
    inc si
    mov cx, SCRATCH_BYTES - 1
copy_loop:
    lodsb
    mov byte ptr cs:[di], al
    inc di
    test al, al
    jz copy_succeeded
    loop copy_loop
copy_failed:
    pop di
    pop si
    pop cx
    pop bx
    pop ax
    clc
    ret
copy_succeeded:
    pop di
    pop si
    pop cx
    pop bx
    pop ax
    stc
    ret

old_int21:
    .word 0, 0
busy:
    .byte 0
source_mapped:
    .byte 0
target_mapped:
    .byte 0
drive_map:
    .fill 26, 1, 0
scratch_one:
    .fill SCRATCH_BYTES, 1, 0
scratch_two:
    .fill SCRATCH_BYTES, 1, 0
resident_end:
