;----------------------------------------------------------------------------
;                  Mini-shell for Warp/Aptiva "trapdoor".
;
; This "shell" needs to do a few of things.
;
;  0) Turn back on the screen that was blanked by the HIDE2.SYS driver.
;  1) Exec an app specified on it's command line.
;  2) Prevent "shelling out" to a command line by apps.
;  3) When the app exits and returns, flush known disk caches, and Int 19
;----------------------------------------------------------------------------
;  V1.2 - if /C seen while nested try to exec COMMAND.COM to run command
; ************************************************************************
; * Change                                                             Chg
; * Activity  Date     By    Reason                                    Num
; * --------  -------- ---   ------                                    ---
; * 150698    04/04/96 MT    Remove unnecessary second int 19h         001
; * 156227    06/05/96 MT    Remove V12 code                           002
;----------------------------------------------------------------------------
V12 = 0

;        include PDB.INC

MAXNAME   = (64 + 14)
STACKSIZE = 128

MiniShell segment para public 'code'
        assume  cs:MiniShell
        org     100h
start   label   near
        jmp     init
        org     104h
        dd      12345678h       ; A "signature" for detecting ourself


DevName db      "HIDE$",0


LoadExecStruct label byte
leEnv     dw    0               ; App inherits our environment
leCmdTail dw    0080h           ; Command line for app
          dw    ?               ; Our PSP seg (filled in at runtime)
leFCB1    dw    005Ch           ; His default FCB1 (same as ours)
          dw    ?               ; Our PSP seg (filled in at runtime)
leFCB2    dw    006Ch           ; His default FCB2 (same as ours)
          dw    ?               ; Our PSP seg (filled in at runtime)

Old21     dd    ?               ; For fake ints


;----------------------------------------------------------------------------
;                 Int 21 hooker (for shell detect API)
;----------------------------------------------------------------------------
Int21hook  proc far
        cmp     ax, 0BEEFh
        je      MaybeForMe
ChainInt21:
        jmp     dword ptr [Old21]
MaybeForMe:
        cmp     bx, 0DEADh      ; BX==DEAD if it's the detection API
        jne     ChainInt21      ;
        .186
        rol     bx,4            ; Make BX==EADD on return
        iret
Int21hook  endp



;----------------------------------------------------------------------------
;                           An internal stack
;----------------------------------------------------------------------------
          align 4
InternalStack label byte
           db   STACKSIZE dup (0)
StackStart dw   ?


;----------------------------------------------------------------------------
; This is where we chop down our memory allocation and EXEC the application.
;----------------------------------------------------------------------------
ExecTheApplication label near
        ;
        ; Chop down our arena so the app has memory to run.
        ;
        mov     ax, cs                  ;
        mov     es, ax                  ; ES == our Segment
        mov     bx, offset AppName      ; Start of AppName buffer
        add     bx, MAXNAME             ;  allow for it's size
        .186
        shr     bx, 4                   ; Adjust to PARA's
        .8086
        mov     ah, 4Ah                 ; Chop it down.
        int     21h                     ;
        ;
        ; Do the EXEC call
        ;
        mov     ax, cs                  ;
        mov     ds, ax                  ;
        mov     dx, offset AppName      ; DS:DX-->ASCIIZ app name
        mov     es, ax                  ;
        mov     bx, offset LoadExecStruct ; ES:BX-->filled in LOADEXEC struc
        mov     ax, 4B00h               ; Load and execute
        int     21h                     ;

        mov     ax, cs                  ;
        mov     ss, ax                  ;
        mov     sp, offset StackStart   ; Reset our stack
        ;
        ; Flush any caches we know the API for.
        ;
        mov     ah, 21h                 ; Qcache flush
        int     13h                     ;
        mov     ax, 2200h               ; Qcache disable
        int     13h                     ;

        mov     ah, 0A1h                ; Super PC Kwik & PC-Cache flush
        mov     si, 4358h               ;
        int     13h                     ;

        mov     ax, 0FFA5h              ; PC-Cache 6.0+ flush + disable
        mov     cx, 0DDDDh              ;
        int     16h                     ;

        mov     ax, 4A10h               ; Smartdrv 4.X 5.X
        mov     bx, 0001h               ; Commit all dirty sectors
        int     2Fh                     ;

        int     19h                     ; Reboot

Terminate:
        mov     ax, 4C00h
        int     21h

;----------------------------------------------------------------------------
; Note: As a copy of the app name is being made, it WILL be overlaying
;       the code below.  This is OK, since that code will have done it's
;       job by that point!
;----------------------------------------------------------------------------
AppName   label byte            ; Copy of the app name from our command tail

;----------------------------------------------------------------------------
;                 This is the real start of the shell.
;----------------------------------------------------------------------------

init    label   near
        mov     sp, offset StackStart   ; Init stack ptr
        ;
        ; Call the detection API to see if we're loaded.
        ;
        mov     ax, 0BEEFh              ; Detection API
        mov     bx, 0DEADh              ;
        int     21h                     ;
        cmp     bx, 0DEADh              ; Means we're not loaded yet
        je      LoadingShellOK          ;
        cmp     bx, 0EADDh              ; We're loaded for sure if equal
        jne     LoadingShellOK          ; What the hell, let it go...

if V12
;-----------------------------------------------------------------------
; Check for /C on command line.  If present, then allow exec to happen
; anyway - but substitute C:\DOS\COMMAND.COM instead of us.
;-----------------------------------------------------------------------
        xor     cx, cx
        mov     cl,cs:[80h]
        push    cs
        pop     ds
        mov     si, 0081h

LoopAgain:
        lodsb
        cmp     al, '/'
        je      GotSlash
        loop    LoopAgain

NestedTerminate:
        mov     ax, 4CFFh               ; Terminate, we're nested
        int     21h                     ;

GotSlash:
        lodsb
        cmp     al, 'c'
        je      GotC
        cmp     al, 'C'
        jne     NestedTerminate

GotC:   lodsb
        cmp     al, ' '
        je      GotOneForSure
        cmp     al, 9
        je      GotOneForSure
        cmp     al, 13
        je      GotOneForSure
        cmp     al, 10
        jne     NestedTerminate
GotOneForSure:
        jmp     Exec2TheAppCont
else
        mov     ax, 4CFFh               ; Terminate, we're nested
        int     21h                     ;
endif

LoadingShellOK:
        ;
        ; Save Int 21 for chaining...
        ;
        xor     ax, ax                  ;
        mov     es, ax                  ; ES-->IVT
        .386
        mov     eax, es:[21h*4]         ; Save Int 21 vector
        mov     Old21, eax              ;

        ;
        ; Take Int 21 for detection API installation
        ;
        mov     ax, cs
        shl     eax, 16
        mov     ax, offset Int21Hook
        mov     es:[21h*4], eax
        .8086

        ;
        ; OK, we *ARE* the root shell.  One of the thing's we're responsible
        ; for is turning back on the display that's being thwacked 18.2 time
        ; a second right now by the HIDE2.SYS driver that loaded before us.
        ;
        ; HIDE2.SYS implements a device called "HIDE$" that we can open and
        ; write a few bytes to.  This will cause "HIDE$" to reenable the
        ; screen automatically.
        ;
        mov     ax, cs                  ;
        mov     ds, ax                  ;
        mov     dx, offset DevName      ; DS:DX-->"HIDE$"
        mov     ax, 3D01h               ; Open w/"write only" access
        int     21h                     ;
        jc      NoHideDevicePresent

        push    ax                      ; Save handle
        mov     dx, offset DevName      ; DS:DX-->"buffer"
        mov     cx, 5                   ; Write 5 bytes
        xchg    bx, ax                  ; BX==handle
        mov     ah, 40h                 ; "write" to handle
        int     21h                     ;

        pop     bx                      ; Close handle
        mov     ah, 3Eh                 ;
        int     21h                     ;

        mov     ax, 0B800h              ;
        mov     es, ax                  ; ES:DI-->CGA vram buffer
        xor     di,di                   ;
        mov     cx, ((80 * 25) / 2)     ;
        .386
        mov     eax, 07200720h          ; " " and attrib 7
        rep     stosd                   ;

        .8086
NoHideDevicePresent:
        ;------------------------------------------------------------------
        ; Now we're ready to setup to EXEC the specified app.
        ;   1) He inherits our environment
        ;   2) He inherits our default FCB's
        ;   3) His command tail is a modified version of ours.
        ;------------------------------------------------------------------
        ;
        ; Fill in the LOADEXEC structure with default FCB's
        ;
        mov     ax, cs                  ; AX==our PSP
        mov     ds, ax                  ; Make sure regs are aimed at PSP
        mov     ds, ax                  ;  Make sure regs are aimed

        assume  ds:MiniShell
        mov     word ptr leFCB1+2, ax   ; Inherits our FCB's (we don't use'em)
        mov     word ptr leFCB2+2, ax   ; Inherits our FCB's (we don't use'em)
        ;
        ; Set the segment:offset part of the command tail.
        ;
        mov     word ptr leCmdTail+2, ax ; We'll build it from ours
        assume  ds:nothing
        ;
        ; Scan for the app in our command tail.
        ;
        mov     es, ax                  ; Aim seg regs at our PSP

        jmp     short OverBridge        ; Ignore this stuff
ToScanAppAgain:                         ;  it's just because
        jmp     ScanAppAgain            ;   some jumps are
OverBridge:                             ;    out of range

        ;
        ; Skip over any white space.
        ;
        mov     di, 81h                 ; ES:DI-->our command tail
ScanAppLoop:
        mov     al, [di]
        cmp     al, ' '                 ; White space (blank)?
        je      ToScanAppAgain          ;
        cmp     al, 9                   ; White space (TAB)?
        je      ToScanAppAgain          ;

        ;
        ; This adjusts in case there's not enough code above to safely
        ; account for a MAXNAME byte copied app name buffer.  Some additional
        ; NOP's will be injected to pad out as needed...
        ;
CriticalPoint label near

        if ((CriticalPoint - AppName) LT MAXNAME)
           ;-----------------------------------------------------------------
           ; We need to pad out with some NOP's so we don't whack the code
           ; below as it's running...
           ;-----------------------------------------------------------------
           PadCount = (MAXNAME - (CriticalPoint - AppName))
           rept  PadCount
                nop
           endm
        endif

        ;
        ; Now find the end of his name (copying the name as we go along)
        ;
        mov     bx, offset AppName      ; The buffer we copy the name to
        mov     cx, MAXNAME             ;
        mov     ah, 0                   ; Update NULL terminator at same time!
ScanAppNameEnd:
        mov     al, [di]                ; A (potential) character of app name
        cmp     al, ' '                 ; White space?
        je      GotEndOfName            ;
        cmp     al, 9                   ; White space?
        je      GotEndOfName            ;
        cmp     al, 0Dh                 ; <CR>? (i.e. end of command tail)
        je      GotEndOfName            ;
        mov     [bx], ax                ; Copy one char + NULL
        mov     byte ptr [di], ' '      ; Blank out name in our command tail
        inc     bx                      ;
        inc     di                      ; Keep going...
        dec     cx                      ;
        jnz     ScanAppNameEnd          ;
        jmp     short GotEndOfName      ; (Should NEVER GET here!!!)

ScanAppAgain:
        inc     di                      ; Move past the white space
        jmp     ScanAppLoop             ; Try again....


GotEndOfName:
        ;
        ; Since we pulled that sleazy trick of blanking out the app's name
        ; in *our* command tail, this leaves the contents of *our* command
        ; tail to be exactly the parameters to be passed to the app as *his*
        ; command tail.  This is convenient eh?  There's no more parsing
        ; needed.  We're almost about ready to exec the app.  All that needs
        ; to be done now is chop our memory allocation down to give the app
        ; room to run.  This chopping will be done by some code up above
        ; though, so this code here can be thrown away to give the app more
        ; room to run in.
        ;
        jmp     ExecTheApplication              ; We're on our way...


if V12

COMMAND_COM db  "C:\DOS\COMMAND.COM",0

Load2ExecStruct label byte
leEnv2     dw    0               ; App inherits our environment
leCmdTail2 dw    0080h           ; Command line for app
x          dw    ?               ; Our PSP seg (filled in at runtime)
leFCB12    dw    005Ch           ; His default FCB1 (same as ours)
y          dw    ?               ; Our PSP seg (filled in at runtime)
leFCB22    dw    006Ch           ; His default FCB2 (same as ours)
z          dw    ?               ; Our PSP seg (filled in at runtime)

;----------------------------------------------------------------
; Copies env and returns length(+slush factor) in SI in paras
;----------------------------------------------------------------
SizeCopyEnv proc near
        push    ds
        push    ax

        mov     ds, cs:[002Ch]          ; DS=our environment seg
        xor     si, si                  ;
TrySizeAgain:
        mov     bx, [si]                ; Copy a word
        mov     word ptr cs:CopyEnvTo[si], bx

        cmp     word ptr [si], 0        ; End of environment yet?
        je      FoundEnvEnd             ; Yes,...

        inc     si                      ; Move to next byte
        jmp     TrySizeAgain            ;

FoundEnvEnd:
        add     si, 96                  ; Lots of slush factor...
        .186
        shr     si, 4                   ; Adjust to para's

        pop     ax
        pop     ds
        ret
SizeCopyEnv endp


Exec2TheAppCont:
        mov     ax, cs
        mov     es, ax          ; ES=DS=CS
        mov     ds, ax
        assume  ds:MiniShell

        mov     x,  ax
        mov     y,  ax
        mov     z,  ax
        assume  ds:nothing

        ;
        ; Compute segment address of new environment block
        ;
        mov     ax, cs                  ;
        mov     bx, offset NestedEnd    ;
        shr     bx, 4                   ;
        add     ax, bx                  ;
        mov     leEnv2, ax

        ;
        ; Chop down our arena so the app has memory to run.
        ;
        call    SizeCopyEnv             ; How big is env? (returned in SI)
        mov     bx, offset NestedEnd    ; Start of end of shell
        add     bx, MAXNAME             ;  allow for slush
        shr     bx, 4                   ; Adjust to PARA's
        add     bx, si                  ; Add in new env size
        mov     ah, 4Ah                 ; Chop it down.
        int     21h                     ;

        ;
        ; Search for and eliminate any COMSPEC= in the appended copy
        ; of the environment.
        ;
        push    cs
        pop     ds
        mov     si, offset CopyEnvTo
        dec     si

SearchAgain:
        cmp     word ptr [si], 0                ; Done?
        je      GoRunTheCOMMAND                 ; Yes,...

        .386
        cmp     dword ptr [si], 4D4F4300h       ; 0,"COM"
        jne     KeepSearching
        cmp     dword ptr [si+4], 43455053h     ; "SPEC"
        jne     KeepSearching
        cmp     byte  ptr [si+8], '='
        jne     KeepSearching
        ror     dword ptr [si+1], 8             ; "COMSPEC"-->"SCOMPEC"
        jmp     GoRunTheCOMMAND

KeepSearching:
        inc     si
        jmp     SearchAgain
GoRunTheCOMMAND:


        ;
        ; Exec COMMAND.COM to run the command
        ;
        mov     dx, offset COMMAND_COM     ; DS:DX-->ASCIIZ app name
        mov     bx, offset Load2ExecStruct ; ES:BX-->filled in LOADEXEC struc
        mov     ax, 4B00h               ; Load and execute
        int     21h                     ;
        jc      ExecError               ;

        mov     ax, cs                  ;
        mov     ss, ax                  ;
        mov     sp, offset InternalStack;

        ;
        ; Get COMMAND.COM's return code in AL.
        ;
        mov     ah, 4Dh                 ; Get child RC in AL
        int     21h                     ;

        ;
        ; Return to the app who called us.
        ;
ExecError:
        mov     ah, 4Ch                 ; Terminate returning child's RC
        int     21h                     ;

endif

        db      "OS/2 'Trapdoor' Mini-Shell V1.2, Copyright (C) 1995 IBM Corp"

        align   16
NestedEnd  label byte
        db      "COMSPEC=C:\DOS\COMMAND.COM",0
CopyEnvTo  label byte
;-------------------------------------------------------------------
; Existing Environment will be appended *HERE* on /C invocations.
;-------------------------------------------------------------------

MiniShell ends
        end     start
