diff --git a/CMakeLists.txt b/CMakeLists.txt index ce128c4..18f965d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ if(MARS_NWE_BUILD_DOSUTILS) find_package(OpenWatcom REQUIRED) set(DOSUTILS_C_SOURCES + kern.c net.c tools.c netcall.c diff --git a/kern.c b/kern.c new file mode 100644 index 0000000..31ab335 --- /dev/null +++ b/kern.c @@ -0,0 +1,271 @@ +/* + * kern.c - C-side experimental NetWare/DOS request wrappers + * + * kern_wasm.asm remains the production/reference implementation. + * These C functions are test wrappers so we can inspect register setup and + * slowly port proven pieces from ASM to C. + */ + +#include +#include +#include + +#include "net.h" + +#if defined(__WATCOMC__) +#define KERN_C_CALL _Cdecl +#else +#define KERN_C_CALL +#endif + +typedef struct { + unsigned int in_ax; + unsigned int in_bx; + unsigned int in_cx; + unsigned int in_dx; + unsigned int in_si; + unsigned int in_di; + unsigned int in_ds; + unsigned int in_es; + + unsigned int out_ax; + unsigned int out_bx; + unsigned int out_cx; + unsigned int out_dx; + unsigned int out_si; + unsigned int out_di; + unsigned int out_flags; + + void far *req_ptr; + void far *repl_ptr; + + int rc; +} NET_CALL_C_DEBUG; + +NET_CALL_C_DEBUG Net_Call_C_Last; + +static void net_call_c_clear(void) +{ + memset(&Net_Call_C_Last, 0, sizeof(Net_Call_C_Last)); +} + +static void net_call_c_save_in(union REGS *r, struct SREGS *s, + void *req, void *repl) +{ + Net_Call_C_Last.in_ax = r->x.ax; + Net_Call_C_Last.in_bx = r->x.bx; + Net_Call_C_Last.in_cx = r->x.cx; + Net_Call_C_Last.in_dx = r->x.dx; + Net_Call_C_Last.in_si = r->x.si; + Net_Call_C_Last.in_di = r->x.di; + Net_Call_C_Last.in_ds = s->ds; + Net_Call_C_Last.in_es = s->es; + Net_Call_C_Last.req_ptr = req; + Net_Call_C_Last.repl_ptr = repl; +} + +static void net_call_c_save_out(union REGS *r, unsigned int returned_ax) +{ + Net_Call_C_Last.out_ax = returned_ax; + Net_Call_C_Last.out_bx = r->x.bx; + Net_Call_C_Last.out_cx = r->x.cx; + Net_Call_C_Last.out_dx = r->x.dx; + Net_Call_C_Last.out_si = r->x.si; + Net_Call_C_Last.out_di = r->x.di; + Net_Call_C_Last.out_flags = r->x.cflag; + Net_Call_C_Last.rc = returned_ax & 0x00ff; +} + +/* + * C equivalent of kern_wasm.asm Net_Call: + * AX = func + * DS:SI = request + * ES:DI = reply + * int 21h + * return AL only, because kern_wasm.asm clears AH before return. + */ +int KERN_C_CALL Net_Call_C(unsigned int ax, void *req, void *repl) +{ + return Net_Call_CX(ax, 0, 0, 0, req, repl); +} + +int KERN_C_CALL Net_Call_CX(unsigned int ax, unsigned int bx, + unsigned int cx, unsigned int dx, + void *req, void *repl) +{ + union REGS inregs; + union REGS outregs; + struct SREGS segregs; + unsigned int ret_ax; + + memset(&inregs, 0, sizeof(inregs)); + memset(&outregs, 0, sizeof(outregs)); + memset(&segregs, 0, sizeof(segregs)); + net_call_c_clear(); + + inregs.x.ax = ax; + inregs.x.bx = bx; + inregs.x.cx = cx; + inregs.x.dx = dx; + + inregs.x.si = FP_OFF(req); + inregs.x.di = FP_OFF(repl); + segregs.ds = FP_SEG(req); + segregs.es = FP_SEG(repl); + + net_call_c_save_in(&inregs, &segregs, req, repl); + + int86x(0x21, &inregs, &outregs, &segregs); + + ret_ax = outregs.x.ax & 0x00ff; /* match ASM Net_Call return */ + + net_call_c_save_out(&outregs, ret_ax); + + return (int)ret_ax; +} + +/* + * Experimental Novell Client/VLM AH=F2 wrapper derived from official FLAG.EXE + * disassembly: + * AH = F2h + * AL = NCP function, e.g. 57h for NCP 87 + * CX = request length + * DX = reply buffer length + * DS:SI = request buffer + * ES:DI = reply buffer + */ +int KERN_C_CALL Net_Call_F2_C(unsigned int function, + unsigned int req_len, + unsigned int repl_len, + void *req, + void *repl) +{ + union REGS inregs; + union REGS outregs; + struct SREGS segregs; + unsigned int ret_ax; + + memset(&inregs, 0, sizeof(inregs)); + memset(&outregs, 0, sizeof(outregs)); + memset(&segregs, 0, sizeof(segregs)); + net_call_c_clear(); + + inregs.h.ah = 0xF2; + inregs.h.al = (unsigned char)(function & 0xff); + inregs.x.cx = req_len; + inregs.x.dx = repl_len; + inregs.x.si = FP_OFF(req); + inregs.x.di = FP_OFF(repl); + segregs.ds = FP_SEG(req); + segregs.es = FP_SEG(repl); + + net_call_c_save_in(&inregs, &segregs, req, repl); + + int86x(0x21, &inregs, &outregs, &segregs); + + /* + * Novell wrapper behavior: + * xor ah,ah + * or al,al + * if al != 0: ah=89h + * Return AL only for C caller, but keep 89xx in debug out_ax. + */ + ret_ax = outregs.x.ax & 0x00ff; + if (ret_ax) + ret_ax |= 0x8900; + + net_call_c_save_out(&outregs, ret_ax); + + return (int)(ret_ax & 0x00ff); +} + +/* + * Fully generic INT 21h register test wrapper. + * Used for reproducing the NWCREQUEST/NWCSHELLREQ register calls seen in + * DeveloperNet 1997 clndos16.lib. + */ +int KERN_C_CALL Net_Call_F2X_C(unsigned int ax, + unsigned int bx, + unsigned int cx, + unsigned int dx, + void *req, + void *repl) +{ + union REGS inregs; + union REGS outregs; + struct SREGS segregs; + unsigned int ret_ax; + + memset(&inregs, 0, sizeof(inregs)); + memset(&outregs, 0, sizeof(outregs)); + memset(&segregs, 0, sizeof(segregs)); + net_call_c_clear(); + + inregs.x.ax = ax; + inregs.x.bx = bx; + inregs.x.cx = cx; + inregs.x.dx = dx; + inregs.x.si = FP_OFF(req); + inregs.x.di = FP_OFF(repl); + segregs.ds = FP_SEG(req); + segregs.es = FP_SEG(repl); + + net_call_c_save_in(&inregs, &segregs, req, repl); + + int86x(0x21, &inregs, &outregs, &segregs); + + ret_ax = outregs.x.ax & 0x00ff; + if (ret_ax) + ret_ax |= 0x8900; + + net_call_c_save_out(&outregs, ret_ax); + + return (int)(ret_ax & 0x00ff); +} + + +void KERN_C_CALL Net_Call_C_Dump(void) +{ + fprintf(stdout, "NETCALLC in : AX=%04X BX=%04X CX=%04X DX=%04X DS:SI=%04X:%04X ES:DI=%04X:%04X\n", + Net_Call_C_Last.in_ax, + Net_Call_C_Last.in_bx, + Net_Call_C_Last.in_cx, + Net_Call_C_Last.in_dx, + Net_Call_C_Last.in_ds, + Net_Call_C_Last.in_si, + Net_Call_C_Last.in_es, + Net_Call_C_Last.in_di); + + fprintf(stdout, "NETCALLC out: AX=%04X BX=%04X CX=%04X DX=%04X SI=%04X DI=%04X CF=%04X RC=%d\n", + Net_Call_C_Last.out_ax, + Net_Call_C_Last.out_bx, + Net_Call_C_Last.out_cx, + Net_Call_C_Last.out_dx, + Net_Call_C_Last.out_si, + Net_Call_C_Last.out_di, + Net_Call_C_Last.out_flags, + Net_Call_C_Last.rc); +} + +unsigned int KERN_C_CALL Net_Call_C_GetDebug(unsigned int idx) +{ + switch (idx) { + case 0: return Net_Call_C_Last.in_ax; + case 1: return Net_Call_C_Last.in_bx; + case 2: return Net_Call_C_Last.in_cx; + case 3: return Net_Call_C_Last.in_dx; + case 4: return Net_Call_C_Last.in_ds; + case 5: return Net_Call_C_Last.in_si; + case 6: return Net_Call_C_Last.in_es; + case 7: return Net_Call_C_Last.in_di; + case 8: return Net_Call_C_Last.out_ax; + case 9: return Net_Call_C_Last.out_bx; + case 10: return Net_Call_C_Last.out_cx; + case 11: return Net_Call_C_Last.out_dx; + case 12: return Net_Call_C_Last.out_si; + case 13: return Net_Call_C_Last.out_di; + case 14: return Net_Call_C_Last.out_flags; + case 15: return (unsigned int)Net_Call_C_Last.rc; + } + return 0xffff; +} diff --git a/kern.h b/kern.h index 780557a..8e2c5a4 100644 --- a/kern.h +++ b/kern.h @@ -3,6 +3,8 @@ #define KERN_CALL _Cdecl #else #define KERN_CALL + + #endif extern int KERN_CALL IPXinit(void); @@ -13,8 +15,34 @@ extern void asm_esr_routine(void); extern void esr_routine(ECB *ecb); extern void KERN_CALL xmemmove(void *ziel, void *quelle, UI anz); extern int KERN_CALL Net_Call(UI func, void *req, void *repl); +extern int KERN_CALL C32_LoadNios_Probe(UI axfunc, void *outbuf); +extern int KERN_CALL C32_GetFunc_Probe(char *name, void *outbuf); +extern int KERN_CALL C32_CallVersion_Nios_Probe(void *outbuf); +extern int KERN_CALL C32_MapLock_Probe(void *ptr, UI len, void *outbuf); +extern int KERN_CALL C32_MapVar_Probe(UI specLen, UI flag, void *outbuf); +extern int KERN_CALL C32_OpenRef_Probe(UI refLo, UI refHi, void *outbuf); +extern int KERN_CALL C32_NCP87_Raw5_Probe(UI connLo, UI connHi, + void *hdr, UI hdrLen, + void *path, UI pathLen, + void *rep0, UI rep0Len, + void *rep1, UI rep1Len, + void *outbuf); +extern int KERN_CALL Net_Call_VLM_Raw(UI ax, UI bx, UI cx, UI dx, + void *req, void *repl, + UI p1, UI p2, UI p3); +extern int KERN_CALL Net_Call_C(UI func, void *req, void *repl); +extern int KERN_CALL Net_Call_CX(UI func, UI bx, UI cx, UI dx, + void *req, void *repl); +extern int KERN_CALL Net_Call_F2_C(UI function, UI req_len, UI repl_len, + void *req, void *repl); +extern int KERN_CALL Net_Call_F2X_C(UI ax, UI bx, UI cx, UI dx, + void *req, void *repl); +extern void KERN_CALL Net_Call_C_Dump(void); +extern UI KERN_CALL Net_Call_C_GetDebug(UI idx); + + + #undef KERN_CALL - diff --git a/kern_wasm.asm b/kern_wasm.asm index 290ccba..2b7be2b 100644 --- a/kern_wasm.asm +++ b/kern_wasm.asm @@ -20,7 +20,14 @@ public _IPXclose_socket public _IPXlisten public _xmemmove public _Net_Call - +public _C32_LoadNios_Probe +public _C32_GetFunc_Probe +public _C32_CallVersion_Nios_Probe +public _C32_MapLock_Probe +public _C32_NCP87_Raw5_Probe +public _C32_OpenRef_Probe +public _C32_MapVar_Probe +public _Net_Call_VLM_Raw _IPXinit proc far push bp mov bp, sp @@ -213,4 +220,1239 @@ _Net_Call proc far ret _Net_Call endp +; int Net_Call_VLM_Raw(UI ax, UI bx, UI cx, UI dx, +; void *req, void *repl, +; UI p1, UI p2, UI p3) +; +; Experimental VLM entry caller. +; INT 2F AX=7A20 returns ES:BX = VLM entry if AX == 0000. +; +; Register setup for VLM entry: +; AX, BX, CX, DX from function args +; DS:SI = req +; ES:DI = repl +; +; Extra stack args pushed before calling VLM entry: +; p3, p2, p1 +; +; For NWCREQUEST/VLM test: +; AX=function +; BX=numReqFrags +; CX=connid +; DX=numReplyFrags +; DS:SI=reqFragList +; ES:DI=replyFragList +; p1=6, p2=20h, p3=0 +_Net_Call_VLM_Raw proc far + push bp + mov bp, sp + sub sp, 4 + + push ds + push si + push di + push es + + mov ax, 7A20h + xor bx, bx + int 2Fh + or ax, ax + jz vlm_raw_found + + mov ax, 88FFh + jmp short vlm_raw_done + +vlm_raw_found: + ; save VLM entry ES:BX in stack locals + mov [bp-4], bx + mov ax, es + mov [bp-2], ax + + ; load caller-requested registers + mov ax, [bp+6] + mov bx, [bp+8] + mov cx, [bp+10] + mov dx, [bp+12] + lds si, dword ptr [bp+14] + les di, dword ptr [bp+18] + + push word ptr [bp+26] + push word ptr [bp+24] + push word ptr [bp+22] + call dword ptr [bp-4] + +vlm_raw_done: + pop es + pop di + pop si + pop ds + + mov sp, bp + pop bp + ret +_Net_Call_VLM_Raw endp + + +; int C32_LoadNios_Probe(UI axfunc, void *outbuf) +; +; 16-bit object, but captures 32-bit ESI and ECX using manual 386 opcodes. +; This keeps the OMF/linker in 16-bit mode while still reading Client32/NIOS +; values returned by: +; INT 2F AX=D8C1 +; +; outbuf: +; +00 word AX after INT 2F +; +02 dword ESI after INT 2F +; +06 dword ECX after INT 2F +; +0A word BX +; +0C word CX low +; +0E word DX +; +10 word SI low +; +12 word DS +; +14 word ES +_C32_LoadNios_Probe proc far + push bp + mov bp, sp + + push ds + push es + push si + push di + + ; xor ecx, ecx + db 66h, 33h, 0C9h + ; xor esi, esi + db 66h, 33h, 0F6h + + mov ax, [bp+6] + int 2Fh + + push ds + push es + push si + push cx + push bx + push dx + push ax + + les di, dword ptr [bp+8] + + ; restore AX result into AX and store word at out+0 + pop ax + mov es:[di+0], ax + + ; Store ESI dword at es:[di+2]. + ; Encoding: ES override + operand-size prefix + MOV r/m32,r32 + ; mov es:[di+02], esi = 26 66 89 75 02 + db 26h, 66h, 89h, 75h, 02h + + ; Store ECX dword at es:[di+6]. + ; mov es:[di+06], ecx = 26 66 89 4Dh 06 + db 26h, 66h, 89h, 4Dh, 06h + + pop dx + pop bx + pop cx + + mov es:[di+10], bx + mov es:[di+12], cx + mov es:[di+14], dx + + pop ax ; saved SI + mov es:[di+16], ax + + pop ax ; saved ES + pop bx ; saved DS + mov es:[di+18], bx + mov es:[di+20], ax + + pop di + pop si + pop es + pop ds + pop bp + xor ah, ah + ret +_C32_LoadNios_Probe endp + + +; int C32_GetFunc_Probe(char *name, void *outbuf) +; +; 16-bit OMF Client32 function resolver probe. +; This mimics C32BEGINUSE for one function name: +; INT 2F AX=D8C1 +; save ESI as far pointer to __Nios+8 function resolver +; push name segment +; push name offset +; push 0 +; push 0 +; call far [ESI] +; add sp,8 +; +; outbuf: +; +00 word load AX from INT2F +; +02 dword ESI from INT2F +; +06 dword ECX from INT2F +; +0A word resolver return AX +; +0C word resolver return DX +; +0E word BX +; +10 word CX +_C32_GetFunc_Probe proc far + push bp + mov bp, sp + sub sp, 4 + + push ds + push es + push si + push di + + ; clear ECX/ESI + db 66h, 33h, 0C9h + db 66h, 33h, 0F6h + + mov ax, 0D8C1h + int 2Fh + + ; save ESI dword to [bp-4] + db 66h, 89h, 76h, 0FCh + + push ax ; save load AX + + ; call resolver only if AX == 0 + or ax, ax + jne c32get_store_fail + + push word ptr [bp+8] ; name segment + push word ptr [bp+6] ; name offset + push 0 + push 0 + call dword ptr [bp-4] + add sp, 8 + + jmp short c32get_store + +c32get_store_fail: + xor dx, dx + xor ax, ax + +c32get_store: + push dx + push ax + + les di, dword ptr [bp+10] + + pop ax ; resolver AX + pop dx ; resolver DX + pop bx ; load AX saved in BX temporarily + + mov es:[di+0], bx + + ; Store ESI dword at out+2: ES override + operand prefix + db 26h, 66h, 89h, 75h, 02h + + ; Store ECX dword at out+6 + db 26h, 66h, 89h, 4Dh, 06h + + mov es:[di+10], ax + mov es:[di+12], dx + mov es:[di+14], bx + mov es:[di+16], cx + + pop di + pop si + pop es + pop ds + mov sp, bp + pop bp + xor ah, ah + ret +_C32_GetFunc_Probe endp + + +; int C32_CallVersion_Nios_Probe(void *outbuf) +; +; Safe Client32 version call using the exact d32wrap convention: +; 1) INT 2F AX=D8C1 +; 2) use ESI resolver to resolve "CLIENT32GetVersion" +; 3) call ECX trampoline, not the function pointer directly: +; push 0 +; push 0 +; push fn_seg +; push fn_off +; call ECX +; add sp,8 +; +; outbuf: +; +00 load AX +; +02 resolver off +; +04 resolver seg +; +06 trampoline off +; +08 trampoline seg +; +0A fn off +; +0C fn seg +; +0E call AX +; +10 call DX +; +12 flags +_C32_CallVersion_Nios_Probe proc far + push bp + mov bp, sp + sub sp, 12 + + push ds + push es + push si + push di + pushf + + ; clear ECX/ESI + db 66h, 33h, 0C9h + db 66h, 33h, 0F6h + + mov ax, 0D8C1h + int 2Fh + + ; save ESI resolver at [bp-4] + db 66h, 89h, 76h, 0FCh + ; save ECX trampoline at [bp-8] + db 66h, 89h, 4Eh, 0F8h + + push ax ; load AX for output + or ax, ax + jne c32ver_fail + + push cs + push offset c32ver_name + push 0 + push 0 + call dword ptr [bp-4] + add sp, 8 + + ; resolver returns DX:AX function pointer + mov [bp-12], ax + mov [bp-10], dx + + or ax, dx + je c32ver_fail + + ; d32wrap _CLIENT32GetVersion convention: + push 0 + push 0 + push word ptr [bp-10] + push word ptr [bp-12] + call dword ptr [bp-8] + add sp, 8 + + pushf + push dx + push ax + jmp short c32ver_store + +c32ver_fail: + xor dx, dx + xor ax, ax + pushf + push dx + push ax + +c32ver_store: + les di, dword ptr [bp+6] + + pop ax ; call AX + pop dx ; call DX + pop bx ; flags + pop cx ; load AX + + mov es:[di+0], cx + mov cx, word ptr [bp-4] + mov es:[di+2], cx + mov cx, word ptr [bp-2] + mov es:[di+4], cx + mov cx, word ptr [bp-8] + mov es:[di+6], cx + mov cx, word ptr [bp-6] + mov es:[di+8], cx + mov cx, word ptr [bp-12] + mov es:[di+10], cx + mov cx, word ptr [bp-10] + mov es:[di+12], cx + mov es:[di+14], ax + mov es:[di+16], dx + mov es:[di+18], bx + + popf + pop di + pop si + pop es + pop ds + mov sp, bp + pop bp + xor ah, ah + ret + +c32ver_name db 'CLIENT32GetVersion',0 + +_C32_CallVersion_Nios_Probe endp + + +; int C32_MapLock_Probe(void *ptr, UI len, void *outbuf) +; +; Probe d32wrap.o __MapLockFlat / __UnlockFlat: +; INT 2F AX=D8C1 +; ESI resolver returned by D8C1 is used with command 2 and 3: +; push len_hi +; push len_lo +; push seg +; push off +; push 0 +; push 2 +; call ESI ; returns flat DX:AX +; +; push len_hi +; push len_lo +; push flat_hi +; push flat_lo +; push 0 +; push 3 +; call ESI ; unlock +; +; outbuf: +; +00 load AX +; +02 ESI off +; +04 ESI seg +; +06 map AX low +; +08 map DX high +; +0A unlock AX +; +0C unlock DX +_C32_MapLock_Probe proc far + push bp + mov bp, sp + sub sp, 8 + + push ds + push es + push si + push di + + ; clear ECX/ESI + db 66h, 33h, 0C9h + db 66h, 33h, 0F6h + + mov ax, 0D8C1h + int 2Fh + + ; save ESI resolver at [bp-4] + db 66h, 89h, 76h, 0FCh + push ax ; load AX + + or ax, ax + jne mapflat_fail + + ; MapLockFlat(ptr, len) + push 0 + push word ptr [bp+10] ; len + push word ptr [bp+8] ; ptr seg + push word ptr [bp+6] ; ptr off + push 0 + push 2 + call dword ptr [bp-4] + add sp, 0cH + + ; save mapped flat DX:AX at [bp-8] + mov word ptr [bp-8], ax + mov word ptr [bp-6], dx + + ; UnlockFlat(flat, len) + push 0 + push word ptr [bp+10] ; len + push dx + push ax + push 0 + push 3 + call dword ptr [bp-4] + add sp, 0cH + + jmp short mapflat_store + +mapflat_fail: + xor dx, dx + xor ax, ax + mov word ptr [bp-8], ax + mov word ptr [bp-6], dx + +mapflat_store: + les di, dword ptr [bp+12] + + mov bx, ax ; unlock AX + mov cx, dx ; unlock DX + pop ax ; load AX + + mov es:[di+0], ax + mov ax, word ptr [bp-4] + mov es:[di+2], ax + mov ax, word ptr [bp-2] + mov es:[di+4], ax + mov ax, word ptr [bp-8] + mov es:[di+6], ax + mov ax, word ptr [bp-6] + mov es:[di+8], ax + mov es:[di+10], bx + mov es:[di+12], cx + + pop di + pop si + pop es + pop ds + mov sp, bp + pop bp + xor ah, ah + ret +_C32_MapLock_Probe endp + + + +; int C32_OpenRef_Probe(UI refLo, UI refHi, void *outbuf) +; +; Opens a Client32 connection by connection reference using the same d32wrap +; convention as _CONNOpenByReference / w95ocref.o. +; +; Input: +; refLo/refHi = connection reference, e.g. C32PRIMREF returned 0028:0000. +; +; outbuf: +; +00 load AX from D8C1 +; +02 resolver off +; +04 resolver seg +; +06 trampoline off +; +08 trampoline seg +; +0A function off +; +0C function seg +; +0E return AX +; +10 return DX +; +12 handle low +; +14 handle high +_C32_OpenRef_Probe proc far + push bp + mov bp, sp + sub sp, 80 + + push ds + push es + push si + push di + + ; clear ESI/ECX + db 66h, 33h, 0F6h + db 66h, 33h, 0C9h + + mov ax, 0D8C1h + int 2Fh + + mov [bp-4], ax ; load AX + ; resolver ESI -> [bp-8] + db 66h, 89h, 76h, 0F8h + ; trampoline ECX -> [bp-12] + db 66h, 89h, 4Eh, 0F4h + + or ax, ax + jne c32openref_fail + + ; resolve "CONNOpenByReference" + push cs + push offset c32openref_name + push 0 + push 0 + call dword ptr [bp-8] + add sp, 8 + mov [bp-16], ax + mov [bp-14], dx + or ax, dx + jne c32openref_have_func + jmp c32openref_fail + +c32openref_have_func: + ; local output handle dword at [bp-20] + mov word ptr [bp-20], 0 + mov word ptr [bp-18], 0 + + ; MapLockFlat(&handle, 4) -> flat [bp-24] + push 0 + push 4 + push ss + lea ax, -20[bp] + push ax + push 0 + push 2 + call dword ptr [bp-8] + add sp, 0cH + mov [bp-24], ax + mov [bp-22], dx + + ; Call NIOS trampoline command 5 / CONNOpenByReference. + ; This matches d32wrap _CONNOpenByReference after w95ocref: + ; flat handle ptr, + ; refHi/refLo, + ; 0,0, + ; FEFE FEFE FEFE FEFE, + ; command 5, + ; function ptr. + push word ptr [bp-22] ; flat handle high + push word ptr [bp-24] ; flat handle low + push word ptr [bp+8] ; ref high + push word ptr [bp+6] ; ref low + push 0 + push 0 + push 0fefeH + push 0fefeH + push 0fefeH + push 0fefeH + push 0 + push 5 + push word ptr [bp-14] ; function seg + push word ptr [bp-16] ; function off + call dword ptr [bp-12] + add sp, 1cH + + mov [bp-28], ax + mov [bp-26], dx + + ; UnlockFlat(handle flat, 4) + push 0 + push 4 + push word ptr [bp-22] + push word ptr [bp-24] + push 0 + push 3 + call dword ptr [bp-8] + add sp, 0cH + + jmp short c32openref_store + +c32openref_fail: + mov word ptr [bp-16], 0 + mov word ptr [bp-14], 0 + mov word ptr [bp-28], 0ffffH + mov word ptr [bp-26], 0ffffH + mov word ptr [bp-20], 0 + mov word ptr [bp-18], 0 + +c32openref_store: + les di, dword ptr [bp+10] + + mov ax, [bp-4] + mov es:[di+0], ax + mov ax, [bp-8] + mov es:[di+2], ax + mov ax, [bp-6] + mov es:[di+4], ax + mov ax, [bp-12] + mov es:[di+6], ax + mov ax, [bp-10] + mov es:[di+8], ax + mov ax, [bp-16] + mov es:[di+10], ax + mov ax, [bp-14] + mov es:[di+12], ax + mov ax, [bp-28] + mov es:[di+14], ax + mov ax, [bp-26] + mov es:[di+16], ax + mov ax, [bp-20] + mov es:[di+18], ax + mov ax, [bp-18] + mov es:[di+20], ax + + pop di + pop si + pop es + pop ds + mov sp, bp + pop bp + xor ah, ah + ret + +c32openref_name db 'CONNOpenByReference',0 + +_C32_OpenRef_Probe endp + + + +; int C32_MapVar_Probe(UI specLen, UI flag, void *outbuf) +; +; Exact-ish raw version of w95mconn.o::__C32MapConn16To32 second step. +; +; It emulates: +; C32MAPCONNONE 40 -> server name MARS +; NWCSCANCONNINFO(scanIterator/result ptr, +; scanInfoLevel=0A, scanConnInfo=NWCString/SPECTDATA "MARS", +; scanFlags=1, connInfoVersion=0, +; returnInfoLevel=0, returnConnInfo=NULL, +; connReference local) +; +; But calls Client32 CONNScanInfo directly through ECX/NIOS command 0A. +; +; outbuf: +; +00 load AX +; +02 resolver off +; +04 resolver seg +; +06 trampoline off +; +08 trampoline seg +; +0A function off +; +0C function seg +; +0E ret AX +; +10 ret DX +; +12 resultRef low ; corresponds to caller output ptr in w95mconn +; +14 resultRef high +; +16 connRefLocal low ; corresponds to d32conni local -1c +; +18 connRefLocal high +_C32_MapVar_Probe proc far + push bp + mov bp, sp + sub sp, 140 + + push ds + push es + push si + push di + + ; clear ESI/ECX + db 66h, 33h, 0F6h + db 66h, 33h, 0C9h + + mov ax, 0D8C1h + int 2Fh + + mov [bp-4], ax ; load AX + ; resolver ESI -> [bp-8] + db 66h, 89h, 76h, 0F8h + ; trampoline ECX -> [bp-12] + db 66h, 89h, 4Eh, 0F4h + + or ax, ax + jne c32mapvar_fail + + ; resolve "CONNScanInfo" + push cs + push offset c32mapvar_name + push 0 + push 0 + call dword ptr [bp-8] + add sp, 8 + mov [bp-16], ax + mov [bp-14], dx + or ax, dx + jne c32mapvar_have_func + jmp c32mapvar_fail + +c32mapvar_have_func: + ; resultRef/output dword at [bp-20], init 0 + mov word ptr [bp-20], 0 + mov word ptr [bp-18], 0 + + ; connReference local dword at [bp-24], init 0 + mov word ptr [bp-24], 0 + mov word ptr [bp-22], 0 + + ; string buffer at [bp-80], copy "MARS", zero padded enough + lea di, -80[bp] + push ss + pop es + mov byte ptr es:[di+0], 'M' + mov byte ptr es:[di+1], 'A' + mov byte ptr es:[di+2], 'R' + mov byte ptr es:[di+3], 'S' + mov byte ptr es:[di+4], 0 + + ; Map string buffer len 31h -> [bp-28] + push 0 + push 31H + push ss + lea ax, -80[bp] + push ax + push 0 + push 2 + call dword ptr [bp-8] + add sp, 0cH + mov [bp-28], ax + mov [bp-26], dx + + ; Build SPECTDATA/NWCString transfer block at [bp-48], len 10h. + ; Mirrors d32conni initialization for server-name scan. + mov ax, [bp+6] + mov word ptr [bp-48], ax + mov word ptr [bp-46], 0 + mov ax, [bp-28] + mov word ptr [bp-44], ax + mov ax, [bp-26] + mov word ptr [bp-42], ax + mov word ptr [bp-40], 1 + mov word ptr [bp-38], 0 + mov word ptr [bp-36], 0 + mov word ptr [bp-34], 0 + + ; Map scanInfo spectdata block len 10h -> [bp-32] + push 0 + push 10H + push ss + lea ax, -48[bp] + push ax + push 0 + push 2 + call dword ptr [bp-8] + add sp, 0cH + mov [bp-32], ax + mov [bp-30], dx + + ; Map resultRef/output len4 -> [bp-56] (this is w95mconn caller ptr) + push 0 + push 4 + push ss + lea ax, -20[bp] + push ax + push 0 + push 2 + call dword ptr [bp-8] + add sp, 0cH + mov [bp-56], ax + mov [bp-54], dx + + ; Map connReference local len4 -> [bp-60] + push 0 + push 4 + push ss + lea ax, -24[bp] + push ax + push 0 + push 2 + call dword ptr [bp-8] + add sp, 0cH + mov [bp-60], ax + mov [bp-58], dx + + ; Raw CONNScanInfo via NIOS, following d32wrap _CONNScanInfo + ; argument order from d32conni L$115. + push word ptr [bp-58] ; connReference flat high + push word ptr [bp-60] ; connReference flat low + + push 0 ; returnConnInfo flat high = NULL + push 0 ; returnConnInfo flat low = NULL + + push 0 ; returnInfoLen high + push 0 ; returnInfoLen low + + push 0 ; returnInfoLevel high + push 4 ; returnInfoLevel low, as d32conni L$115 + + push 0 ; scan flag high + push word ptr [bp+8] ; scan flag low + + push word ptr [bp-30] ; scanInfo flat high + push word ptr [bp-32] ; scanInfo flat low + + push 0 ; scanInfoLevel high + push 0aH ; scanInfoLevel low = SERVER_NAME + + push word ptr [bp-54] ; scanIterator/result flat high + push word ptr [bp-56] ; scanIterator/result flat low + + push 0fefeH + push 0fefeH + push 0fefeH + push 0fefeH + + push 0 + push 0aH + push word ptr [bp-14] + push word ptr [bp-16] + call dword ptr [bp-12] + add sp, 30H + + mov [bp-64], ax + mov [bp-62], dx + + ; Unlock maps. + push 0 + push 4 + push word ptr [bp-58] + push word ptr [bp-60] + push 0 + push 3 + call dword ptr [bp-8] + add sp, 0cH + + push 0 + push 4 + push word ptr [bp-54] + push word ptr [bp-56] + push 0 + push 3 + call dword ptr [bp-8] + add sp, 0cH + + push 0 + push 10H + push word ptr [bp-30] + push word ptr [bp-32] + push 0 + push 3 + call dword ptr [bp-8] + add sp, 0cH + + push 0 + push 31H + push word ptr [bp-26] + push word ptr [bp-28] + push 0 + push 3 + call dword ptr [bp-8] + add sp, 0cH + + jmp short c32mapvar_store + +c32mapvar_fail: + mov word ptr [bp-16], 0 + mov word ptr [bp-14], 0 + mov word ptr [bp-64], 0ffffH + mov word ptr [bp-62], 0ffffH + mov word ptr [bp-20], 0 + mov word ptr [bp-18], 0 + mov word ptr [bp-24], 0 + mov word ptr [bp-22], 0 + +c32mapvar_store: + les di, dword ptr [bp+10] + + mov ax, [bp-4] + mov es:[di+0], ax + mov ax, [bp-8] + mov es:[di+2], ax + mov ax, [bp-6] + mov es:[di+4], ax + mov ax, [bp-12] + mov es:[di+6], ax + mov ax, [bp-10] + mov es:[di+8], ax + mov ax, [bp-16] + mov es:[di+10], ax + mov ax, [bp-14] + mov es:[di+12], ax + mov ax, [bp-64] + mov es:[di+14], ax + mov ax, [bp-62] + mov es:[di+16], ax + mov ax, [bp-20] + mov es:[di+18], ax + mov ax, [bp-18] + mov es:[di+20], ax + mov ax, [bp-24] + mov es:[di+22], ax + mov ax, [bp-22] + mov es:[di+24], ax + + pop di + pop si + pop es + pop ds + mov sp, bp + pop bp + xor ah, ah + ret + +c32mapvar_name db 'CONNScanInfo',0 + +_C32_MapVar_Probe endp + + + + +; int C32_NCP87_Raw5_Probe(UI connLo, UI connHi, +; void *hdr, UI hdrLen, +; void *path, UI pathLen, +; void *rep0, UI rep0Len, +; void *rep1, UI rep1Len, +; void *outbuf) +; +; Same as C32_NCP87_Raw_Probe but uses d32wrap-compatible 5-slot +; fragment tables: 5 * 8 = 0x28 bytes for request and reply. +_C32_NCP87_Raw5_Probe proc far + push bp + mov bp, sp + sub sp, 180 + + push ds + push es + push si + push di + + ; clear ESI/ECX + db 66h, 33h, 0F6h + db 66h, 33h, 0C9h + + mov ax, 0D8C1h + int 2Fh + + mov [bp-2], ax + db 66h, 89h, 76h, 0FAh ; resolver at [bp-6] + db 66h, 89h, 4Eh, 0F6h ; trampoline at [bp-10] + + or ax, ax + jne raw5_fail + + ; resolve COMPATNcpRequestReply + push cs + push offset raw5_name + push 0 + push 0 + call dword ptr [bp-6] + add sp, 8 + mov [bp-14], ax + mov [bp-12], dx + or ax, dx + jne raw5_have_func + jmp raw5_fail + +raw5_have_func: + ; actual reply len dword at [bp-36] + mov word ptr [bp-36], 0 + mov word ptr [bp-34], 0 + + ; map hdr -> [bp-20] + push 0 + push word ptr [bp+0eH] + push word ptr [bp+0cH] + push word ptr [bp+0aH] + push 0 + push 2 + call dword ptr [bp-6] + add sp, 0cH + mov [bp-20], ax + mov [bp-18], dx + + ; map path -> [bp-24] + push 0 + push word ptr [bp+14H] + push word ptr [bp+12H] + push word ptr [bp+10H] + push 0 + push 2 + call dword ptr [bp-6] + add sp, 0cH + mov [bp-24], ax + mov [bp-22], dx + + ; map rep0 -> [bp-28] + push 0 + push word ptr [bp+1aH] + push word ptr [bp+18H] + push word ptr [bp+16H] + push 0 + push 2 + call dword ptr [bp-6] + add sp, 0cH + mov [bp-28], ax + mov [bp-26], dx + + ; map rep1 -> [bp-32] + push 0 + push word ptr [bp+20H] + push word ptr [bp+1eH] + push word ptr [bp+1cH] + push 0 + push 2 + call dword ptr [bp-6] + add sp, 0cH + mov [bp-32], ax + mov [bp-30], dx + + ; zero req table [bp-160] len 40 and reply table [bp-120] len 40 + push ss + pop es + cld + xor ax, ax + lea di, -160[bp] + mov cx, 20 + rep stosw + lea di, -120[bp] + mov cx, 20 + rep stosw + + ; req entry0 = hdr + mov ax, [bp-20] + mov [bp-160], ax + mov ax, [bp-18] + mov [bp-158], ax + mov ax, [bp+0eH] + mov [bp-156], ax + mov word ptr [bp-154], 0 + + ; req entry1 = path + mov ax, [bp-24] + mov [bp-152], ax + mov ax, [bp-22] + mov [bp-150], ax + mov ax, [bp+14H] + mov [bp-148], ax + mov word ptr [bp-146], 0 + + ; reply entry0 = rep0 + mov ax, [bp-28] + mov [bp-120], ax + mov ax, [bp-26] + mov [bp-118], ax + mov ax, [bp+1aH] + mov [bp-116], ax + mov word ptr [bp-114], 0 + + ; reply entry1 = rep1 + mov ax, [bp-32] + mov [bp-112], ax + mov ax, [bp-30] + mov [bp-110], ax + mov ax, [bp+20H] + mov [bp-108], ax + mov word ptr [bp-106], 0 + + ; map req table 0x28 -> [bp-40] + push 0 + push 28H + push ss + lea ax, -160[bp] + push ax + push 0 + push 2 + call dword ptr [bp-6] + add sp, 0cH + mov [bp-40], ax + mov [bp-38], dx + + ; map reply table 0x28 -> [bp-44] + push 0 + push 28H + push ss + lea ax, -120[bp] + push ax + push 0 + push 2 + call dword ptr [bp-6] + add sp, 0cH + mov [bp-44], ax + mov [bp-42], dx + + ; map actual reply len -> [bp-48] + push 0 + push 4 + push ss + lea ax, -36[bp] + push ax + push 0 + push 2 + call dword ptr [bp-6] + add sp, 0cH + mov [bp-48], ax + mov [bp-46], dx + + ; call COMPAT via NIOS command 8 + push word ptr [bp-46] + push word ptr [bp-48] + push word ptr [bp-42] + push word ptr [bp-44] + push 0 + push 2 + push word ptr [bp-38] + push word ptr [bp-40] + push 0 + push 2 + push 0 + push 57H + push 0 + push 0 + push word ptr [bp+8] + push word ptr [bp+6] + push 0 + push 8 + push word ptr [bp-12] + push word ptr [bp-14] + call dword ptr [bp-10] + add sp, 28H + + mov [bp-52], ax + mov [bp-50], dx + + ; unlock important mappings only; ignore return + push 0 + push 4 + push word ptr [bp-46] + push word ptr [bp-48] + push 0 + push 3 + call dword ptr [bp-6] + add sp, 0cH + + push 0 + push 28H + push word ptr [bp-42] + push word ptr [bp-44] + push 0 + push 3 + call dword ptr [bp-6] + add sp, 0cH + + push 0 + push 28H + push word ptr [bp-38] + push word ptr [bp-40] + push 0 + push 3 + call dword ptr [bp-6] + add sp, 0cH + + jmp short raw5_store + +raw5_fail: + mov word ptr [bp-14], 0 + mov word ptr [bp-12], 0 + mov word ptr [bp-52], 0ffffH + mov word ptr [bp-50], 0ffffH + mov word ptr [bp-36], 0 + mov word ptr [bp-34], 0 + +raw5_store: + les di, dword ptr [bp+22H] + mov ax, [bp-2] + mov es:[di+0], ax + mov ax, [bp-6] + mov es:[di+2], ax + mov ax, [bp-4] + mov es:[di+4], ax + mov ax, [bp-10] + mov es:[di+6], ax + mov ax, [bp-8] + mov es:[di+8], ax + mov ax, [bp-14] + mov es:[di+10], ax + mov ax, [bp-12] + mov es:[di+12], ax + mov ax, [bp-52] + mov es:[di+14], ax + mov ax, [bp-50] + mov es:[di+16], ax + mov ax, [bp-36] + mov es:[di+18], ax + mov ax, [bp-34] + mov es:[di+20], ax + + pop di + pop si + pop es + pop ds + mov sp, bp + pop bp + xor ah, ah + ret + +raw5_name db 'COMPATNcpRequestReply',0 + +_C32_NCP87_Raw5_Probe endp + + end diff --git a/nwtests.c b/nwtests.c index a2e7abf..1895418 100644 --- a/nwtests.c +++ b/nwtests.c @@ -6,13 +6,59 @@ #include "net.h" -static int usage(void) + +static int tests_same_arg(char *a, char *b) { - return(-1); + while (*a || *b) { + int ca = *a++; + int cb = *b++; + if (ca >= 'a' && ca <= 'z') ca -= 32; + if (cb >= 'a' && cb <= 'z') cb -= 32; + if (ca != cb) return(0); + } + return(1); } -int func_tests(int argc, char *argv[], int mode) +static void tests_usage(void) { + fprintf(stdout, "Usage: TESTS [OLD|NETCALL|E300|NCPF2 [file]|NWREQ87 [file]]\n"); +} + +static int tests_netcall(void) +{ + unsigned char req[4]; + unsigned char repl[4]; + int asm_rc; + int c_rc; + + memset(req, 0, sizeof(req)); + memset(repl, 0, sizeof(repl)); + + fprintf(stdout, "TEST Net_Call ASM vs C\n"); + fprintf(stdout, "Call: INT 21h AH=19h get current drive\n"); + fprintf(stdout, "Expected: AL=0 for A:, 1 for B:, 2 for C:, ...\n\n"); + + asm_rc = Net_Call(0x1900, req, repl); + fprintf(stdout, "ASM Net_Call(1900h) rc=%04X drive=%c:\n", + asm_rc, 'A' + (asm_rc & 0xff)); + + c_rc = Net_Call_C(0x1900, req, repl); + fprintf(stdout, "C Net_Call_C(1900h) rc=%04X drive=%c:\n", + c_rc, 'A' + (c_rc & 0xff)); + + Net_Call_C_Dump(); + + if ((asm_rc & 0xff) == (c_rc & 0xff)) + fprintf(stdout, "\nNETCALL C basic register test OK\n"); + else + fprintf(stdout, "\nNETCALL C basic register test FAILED\n"); + + return(0); +} + +static int tests_old(int argc, char *argv[], int mode) +{ + int level = ncp_17_02(NWCONN, 6); int dirhandle = alloc_temp_dir_handle(0, "SYS:", 'd', NULL); int result = -1; @@ -42,3 +88,1615 @@ int func_tests(int argc, char *argv[], int mode) if (level > -1) (void) ncp_17_02(NWCONN, level); return(0); } + +static void tests_dump_bytes(char *title, unsigned char *p, int len) +{ + int i; + fprintf(stdout, "%s", title); + for (i = 0; i < len; i++) + fprintf(stdout, " %02X", p[i]); + fprintf(stdout, "\n"); +} + +static int tests_netcall_e300(void) +{ + struct { + uint16 len; + uint8 func; + } req_asm, req_c; + + struct { + uint16 len; + uint8 data[16]; + } repl_asm, repl_c; + + int asm_rc; + int c_rc; + + memset(&req_asm, 0, sizeof(req_asm)); + memset(&req_c, 0, sizeof(req_c)); + memset(&repl_asm, 0, sizeof(repl_asm)); + memset(&repl_c, 0, sizeof(repl_c)); + + /* + * NCP 17/17 Get Login Encryption Key. + * This is a known safe bindery/login helper call used by LOGIN/PASSWD code. + * Request via old DOS NetWare API AX=E300. + */ + req_asm.len = 1; + req_asm.func = 0x17; + repl_asm.len = 8; + + req_c.len = 1; + req_c.func = 0x17; + repl_c.len = 8; + + fprintf(stdout, "TEST Net_Call ASM vs C E300/NCP17/17\n"); + fprintf(stdout, "Call: Get Login Encryption Key\n\n"); + + asm_rc = Net_Call(0xE300, &req_asm, &repl_asm); + fprintf(stdout, "ASM Net_Call(E300h) rc=%04X\n", asm_rc); + tests_dump_bytes("ASM key:", repl_asm.data, 8); + + c_rc = Net_Call_C(0xE300, &req_c, &repl_c); + fprintf(stdout, "C Net_Call_C(E300h) rc=%04X\n", c_rc); + tests_dump_bytes("C key:", repl_c.data, 8); + + Net_Call_C_Dump(); + + if ((asm_rc & 0xff) == (c_rc & 0xff)) + fprintf(stdout, "\nNETCALL C E300 return test OK\n"); + else + fprintf(stdout, "\nNETCALL C E300 return test FAILED\n"); + + /* + * Keys can differ per request, so do not require byte-identical keys. + * We only compare return status and check that both calls returned 8 bytes. + */ + return(0); +} + + +static void tests_put_word_lh(uint8 *p, uint16 v) +{ + p[0] = (uint8)(v & 0xff); + p[1] = (uint8)((v >> 8) & 0xff); +} + +static void tests_put_dword_lh(uint8 *p, uint32 v) +{ + p[0] = (uint8)(v & 0xff); + p[1] = (uint8)((v >> 8) & 0xff); + p[2] = (uint8)((v >> 16) & 0xff); + p[3] = (uint8)((v >> 24) & 0xff); +} + +static uint32 tests_get_dword_lh(uint8 *p) +{ + return((uint32)p[0] | + ((uint32)p[1] << 8) | + ((uint32)p[2] << 16) | + ((uint32)p[3] << 24)); +} + +static int tests_get_current_drive(void) +{ + REGS regs; + + regs.h.ah = 0x19; + int86(0x21, ®s, ®s); + return((int)regs.h.al); +} + +static int tests_current_conn_dhandle(uint8 *connid, uint8 *dhandle) +{ + uint8 flags = 0; + int drive; + + drive = tests_get_current_drive(); + + if (get_drive_info((uint8)drive, connid, dhandle, &flags)) { + fprintf(stdout, "NCPF2DBG get_drive_info failed drive=%c:\n", 'A' + drive); + return(-1); + } + + fprintf(stdout, "NCPF2DBG drive=%c: connid=%u dhandle=%u flags=%02X\n", + 'A' + drive, *connid, *dhandle, flags); + + if (!*connid || (flags & 0x80)) { + fprintf(stdout, "NCPF2DBG current drive is not a network drive\n"); + return(-1); + } + + return(0); +} + +static int tests_add_handle_path(uint8 *p, uint8 dhandle, char *name) +{ + int nlen; + + nlen = strlen(name); + if (nlen > 255) nlen = 255; + + *p++ = dhandle; + tests_put_dword_lh(p, 0L); p += 4; + *p++ = 0; /* dirstyle = short dir handle */ + *p++ = 1; /* one component */ + *p++ = (uint8)nlen; + memcpy(p, name, nlen); + p += nlen; + + return(1 + 4 + 1 + 1 + 1 + nlen); +} + +static int tests_ncpf2_read_one(char *name, UI cx_mode) +{ + struct { + uint8 data[320]; + } req; + struct { + uint8 data[256]; + } repl; + + uint8 *p; + uint8 connid = 0; + uint8 dhandle = 0; + UI cx; + int hlen; + int rc; + uint32 attr; + + if (tests_current_conn_dhandle(&connid, &dhandle)) + return(1); + + memset(&req, 0, sizeof(req)); + memset(&repl, 0, sizeof(repl)); + + p = req.data; + *p++ = 6; /* NCP87 subfunction 6: obtain file/subdir info */ + *p++ = 0; /* source namespace DOS */ + *p++ = 0; /* target namespace DOS */ + tests_put_word_lh(p, 0x0006); p += 2; /* SA_ALL */ + tests_put_dword_lh(p, 0x00000004UL); p += 4; /* RIM_ATTRIBUTES */ + hlen = tests_add_handle_path(p, dhandle, name); + p += hlen; + + if (cx_mode == 0) + cx = (UI)(p - req.data); /* most likely: request length */ + else + cx = sizeof(repl.data); /* alternate: reply buffer size */ + + fprintf(stdout, "NCPF2DBG name=%s func=57 conn=%u cx=%u dx=%u mode=%u req.len=%u repl.max=%u\n", + name, connid, cx, (UI)sizeof(repl.data), cx_mode, (UI)(p - req.data), (UI)sizeof(repl.data)); + tests_dump_bytes("NCPF2DBG req:", req.data, (p - req.data) > 64 ? 64 : (int)(p - req.data)); + + rc = Net_Call_F2_C(0x57, cx, (UI)sizeof(repl.data), req.data, repl.data); + + fprintf(stdout, "NCPF2DBG rc=%04X\n", rc); + tests_dump_bytes("NCPF2DBG repl:", repl.data, 64); + Net_Call_C_Dump(); + + attr = tests_get_dword_lh(repl.data); + fprintf(stdout, "NCPF2DBG attr[0]=%08lX\n", attr); + + return(0); +} + +static int tests_ncpf2(int argc, char *argv[]) +{ + char *name = "LOGIN.EXE"; + UI mode = 0; + + if (argc > 2) + name = argv[2]; + if (argc > 3 && tests_same_arg(argv[3], "REPLY")) + mode = 1; + + fprintf(stdout, "TEST NCPF2 AH=F2/AL=57 NCP87 obtain RIM_ATTRIBUTES\n"); + fprintf(stdout, "Default CX is request length. Add REPLY to use reply size.\n"); + fprintf(stdout, "This is read-only, but still experimental.\n\n"); + + return tests_ncpf2_read_one(name, mode); +} + + +static void tests_build_nwreq87_path(uint8 *buf, uint8 dhandle, + char *name, UI *out_len) +{ + uint8 *p; + int nlen; + + memset(buf, 0, 300); + + p = buf; + nlen = strlen(name); + if (nlen > 255) nlen = 255; + + /* + * Same compact handle/path payload we used before: + * handle, dirbase, dirstyle, component-count, len, name + * The new part in this test is not the path itself; it is that we now + * reproduce NWCREQUEST's fragmented request and full reply size. + */ + *p++ = dhandle; + tests_put_dword_lh(p, 0L); p += 4; + *p++ = 0; /* dirstyle = short directory handle */ + *p++ = 1; /* one path component */ + *p++ = (uint8)nlen; + memcpy(p, name, nlen); + p += nlen; + + *out_len = (UI)(p - buf); +} + +static UI tests_build_nwreq87s6_flat(uint8 *req, + uint8 *path, + UI path_len) +{ + uint8 *p; + + memset(req, 0, 400); + p = req; + + /* + * DeveloperNet ncpdos16 87s6.c builds two request fragments: + * frag 1 length 9: + * subfn=6, srcNS, dstNS, searchAttrs, returnInfoMask + * frag 2: + * packed handle/path structure + * + * NWCREQUEST for NETX/Shell concatenates those request fragments before + * calling NWCSHELLREQ. + */ + *p++ = 6; /* NCP87 subfunction 6 */ + *p++ = 0; /* source namespace DOS */ + *p++ = 0; /* target namespace DOS */ + tests_put_word_lh(p, 0x0006); p += 2; /* SA_ALL */ + tests_put_dword_lh(p, 0x00000004UL); p += 4;/* RIM_ATTRIBUTES */ + + memcpy(p, path, path_len); + p += path_len; + + return (UI)(p - req); +} + +static int tests_nwreq87(int argc, char *argv[]) +{ + char *name = "LOGIN.EXE"; + uint8 connid = 0; + uint8 dhandle = 0; + uint8 path[300]; + uint8 req[400]; + uint8 repl[0x180]; + uint8 dummy[8]; + UI path_len; + UI req_len; + int rc; + uint32 a0; + uint32 a4; + uint32 a4d; + + if (argc > 2) + name = argv[2]; + + if (tests_current_conn_dhandle(&connid, &dhandle)) + return(1); + + tests_build_nwreq87_path(path, dhandle, name, &path_len); + req_len = tests_build_nwreq87s6_flat(req, path, path_len); + + memset(repl, 0, sizeof(repl)); + memset(dummy, 0, sizeof(dummy)); + + fprintf(stdout, "TEST NWREQ87 shell-style NCP87/S6 for %s\n", name); + fprintf(stdout, "connid=%u dhandle=%u path.len=%u req.len=%u repl.len=%u\n", + connid, dhandle, path_len, req_len, (UI)0x014d); + tests_dump_bytes("NWREQ87 path:", path, path_len > 48 ? 48 : path_len); + tests_dump_bytes("NWREQ87 req :", req, req_len > 64 ? 64 : req_len); + + /* + * Reproduce the NETX/Shell path in clndos16 dreq.c: + * first AH=F0/DX=conn via NWCSHELLREQ + * then AH=F2/AL=function with DS:SI=request, CX=requestLen, + * ES:DI=reply, DX=sum(replyFragLengths). + * + * For 87s6.c reply fragments are 0x4d + 0x100 = 0x014d. + */ + fprintf(stdout, "NWREQ87 init F000 DX=conn\n"); + rc = Net_Call_F2X_C(0xF000, 0, 0, (UI)connid, dummy, repl); + fprintf(stdout, "NWREQ87 init rc=%04X\n", rc); + Net_Call_C_Dump(); + + fprintf(stdout, "NWREQ87 call F257 CX=req.len DX=014D\n"); + rc = Net_Call_F2X_C(0xF257, 0, req_len, 0x014d, req, repl); + fprintf(stdout, "NWREQ87 rc=%04X\n", rc); + Net_Call_C_Dump(); + + tests_dump_bytes("NWREQ87 repl[0..63]:", repl, 64); + tests_dump_bytes("NWREQ87 repl[4d..8c]:", repl + 0x4d, 64); + + a0 = tests_get_dword_lh(repl); + a4 = tests_get_dword_lh(repl + 4); + a4d = tests_get_dword_lh(repl + 0x4d); + + fprintf(stdout, "NWREQ87 dword[0]=%08lX dword[4]=%08lX dword[4d]=%08lX\n", + a0, a4, a4d); + + return(0); +} + + +typedef struct { + uint16 off; + uint16 seg; + uint16 len; +} TEST_NWFRAG16; + +static void tests_set_frag(TEST_NWFRAG16 *f, void *ptr, UI len) +{ + f->off = FP_OFF(ptr); + f->seg = FP_SEG(ptr); + f->len = (uint16)len; +} + +static int tests_nwreq87vlm(int argc, char *argv[]) +{ + char *name = "LOGIN.EXE"; + uint8 connid = 0; + uint8 dhandle = 0; + uint8 path[300]; + uint8 hdr[16]; + uint8 repl[0x180]; + TEST_NWFRAG16 reqfrags[2]; + TEST_NWFRAG16 replfrags[2]; + UI path_len; + int rc; + uint32 a0; + uint32 a4d; + + if (argc > 2) + name = argv[2]; + + if (tests_current_conn_dhandle(&connid, &dhandle)) + return(1); + + tests_build_nwreq87_path(path, dhandle, name, &path_len); + + memset(hdr, 0, sizeof(hdr)); + memset(repl, 0, sizeof(repl)); + memset(reqfrags, 0, sizeof(reqfrags)); + memset(replfrags, 0, sizeof(replfrags)); + + /* + * ncpdos16 87s6.c request frag #1, length 9. + */ + hdr[0] = 6; /* subfunction */ + hdr[1] = 0; /* src namespace DOS */ + hdr[2] = 0; /* dst namespace DOS */ + tests_put_word_lh(hdr + 3, 0x0006); /* SA_ALL */ + tests_put_dword_lh(hdr + 5, 0x00000004UL); /* RIM_ATTRIBUTES */ + + tests_set_frag(&reqfrags[0], hdr, 9); + tests_set_frag(&reqfrags[1], path, path_len); + + /* + * ncpdos16 87s6.c reply frags: + * local entry-info block 0x4d + * caller output extension 0x100 + */ + tests_set_frag(&replfrags[0], repl, 0x4d); + tests_set_frag(&replfrags[1], repl + 0x4d, 0x100); + + fprintf(stdout, "TEST NWREQ87VLM NCP87/S6 for %s\n", name); + fprintf(stdout, "connid=%u dhandle=%u path.len=%u\n", connid, dhandle, path_len); + tests_dump_bytes("VLM hdr :", hdr, 9); + tests_dump_bytes("VLM path:", path, path_len > 48 ? 48 : path_len); + + fprintf(stdout, "VLM call: AX=0057 BX=0002 CX=conn DX=0002 p1=6 p2=20 p3=0\n"); + + rc = Net_Call_VLM_Raw(0x0057, 2, (UI)connid, 2, + reqfrags, replfrags, + 6, 0x20, 0); + + fprintf(stdout, "NWREQ87VLM rc=%04X\n", rc); + tests_dump_bytes("VLM repl[0..63]:", repl, 64); + tests_dump_bytes("VLM repl[4d..8c]:", repl + 0x4d, 64); + + a0 = tests_get_dword_lh(repl); + a4d = tests_get_dword_lh(repl + 0x4d); + fprintf(stdout, "NWREQ87VLM dword[0]=%08lX dword[4d]=%08lX\n", a0, a4d); + + return(0); +} + + +static UI tests_build_nwreq87_path_multi(uint8 *buf, uint8 dhandle, + UI dirbase, uint8 style, + int count, + char *c1, char *c2, char *c3) +{ + uint8 *p; + int l; + + memset(buf, 0, 300); + p = buf; + + *p++ = dhandle; + tests_put_dword_lh(p, (uint32)dirbase); p += 4; + *p++ = style; + *p++ = (uint8)count; + + if (count > 0 && c1) { + l = strlen(c1); if (l > 255) l = 255; + *p++ = (uint8)l; memcpy(p, c1, l); p += l; + } + if (count > 1 && c2) { + l = strlen(c2); if (l > 255) l = 255; + *p++ = (uint8)l; memcpy(p, c2, l); p += l; + } + if (count > 2 && c3) { + l = strlen(c3); if (l > 255) l = 255; + *p++ = (uint8)l; memcpy(p, c3, l); p += l; + } + + return (UI)(p - buf); +} + + +static void tests_wait_key(void) +{ + fprintf(stdout, "\nStrike any key when ready . . . "); + fflush(stdout); + getch(); + fprintf(stdout, "\n"); + fflush(stdout); +} + +static int tests_nwreq87vlm_one(char *label, uint8 connid, + uint8 *hdr, uint8 *path, UI path_len) +{ + uint8 repl[0x180]; + TEST_NWFRAG16 reqfrags[2]; + TEST_NWFRAG16 replfrags[2]; + int rc; + uint32 a0; + uint32 a4d; + + memset(repl, 0, sizeof(repl)); + memset(reqfrags, 0, sizeof(reqfrags)); + memset(replfrags, 0, sizeof(replfrags)); + + tests_set_frag(&reqfrags[0], hdr, 9); + tests_set_frag(&reqfrags[1], path, path_len); + tests_set_frag(&replfrags[0], repl, 0x4d); + tests_set_frag(&replfrags[1], repl + 0x4d, 0x100); + + fprintf(stdout, "\nCASE %s\n", label); + tests_dump_bytes("PATH:", path, path_len > 64 ? 64 : path_len); + + rc = Net_Call_VLM_Raw(0x0057, 2, (UI)connid, 2, + reqfrags, replfrags, + 6, 0x20, 0); + + fprintf(stdout, "RC=%04X\n", rc); + tests_dump_bytes("REPL0:", repl, 32); + tests_dump_bytes("REPL4D:", repl + 0x4d, 32); + a0 = tests_get_dword_lh(repl); + a4d = tests_get_dword_lh(repl + 0x4d); + fprintf(stdout, "DW0=%08lX DW4D=%08lX\n", a0, a4d); + + tests_wait_key(); + + return rc; +} + +static int tests_nwreq87vlmmatrix(int argc, char *argv[]) +{ + char *name = "LOGIN.EXE"; + uint8 connid = 0; + uint8 dhandle = 0; + uint8 hdr[16]; + uint8 path[300]; + UI len; + + (void)argc; + (void)argv; + + if (tests_current_conn_dhandle(&connid, &dhandle)) + return(1); + + memset(hdr, 0, sizeof(hdr)); + hdr[0] = 6; + hdr[1] = 0; + hdr[2] = 0; + tests_put_word_lh(hdr + 3, 0x0006); + tests_put_dword_lh(hdr + 5, 0x00000004UL); + + fprintf(stdout, "TEST NWREQ87VLMMATRIX NCP87/S6\n"); + fprintf(stdout, "connid=%u dhandle=%u file=%s\n", connid, dhandle, name); + tests_dump_bytes("HDR:", hdr, 9); + + /* + * We know the VLM transport is reached. RC=81 now likely means our + * packed handle/path is wrong for NCP87. Try the common handle/path + * variants: + * + * current mapped handle + file + * current mapped handle + PUBLIC/file + * root handle 0 + PUBLIC/file + * style 1 variants in case dirstyle differs + */ + len = tests_build_nwreq87_path_multi(path, dhandle, 0, 0, 1, + "LOGIN.EXE", 0, 0); + tests_nwreq87vlm_one("h=dhandle style=0 LOGIN.EXE", connid, hdr, path, len); + + len = tests_build_nwreq87_path_multi(path, dhandle, 0, 0, 2, + "PUBLIC", "LOGIN.EXE", 0); + tests_nwreq87vlm_one("h=dhandle style=0 PUBLIC/LOGIN.EXE", connid, hdr, path, len); + + len = tests_build_nwreq87_path_multi(path, 0, 0, 0, 2, + "PUBLIC", "LOGIN.EXE", 0); + tests_nwreq87vlm_one("h=0 style=0 PUBLIC/LOGIN.EXE", connid, hdr, path, len); + + len = tests_build_nwreq87_path_multi(path, dhandle, 0, 1, 1, + "LOGIN.EXE", 0, 0); + tests_nwreq87vlm_one("h=dhandle style=1 LOGIN.EXE", connid, hdr, path, len); + + len = tests_build_nwreq87_path_multi(path, 0, 0, 1, 2, + "PUBLIC", "LOGIN.EXE", 0); + tests_nwreq87vlm_one("h=0 style=1 PUBLIC/LOGIN.EXE", connid, hdr, path, len); + + return(0); +} + + +static int tests_nwreq87vlmcase(int argc, char *argv[]) +{ + int which = 1; + uint8 connid = 0; + uint8 dhandle = 0; + uint8 hdr[16]; + uint8 path[300]; + UI len; + + if (argc > 2) + which = atoi(argv[2]); + + if (tests_current_conn_dhandle(&connid, &dhandle)) + return(1); + + memset(hdr, 0, sizeof(hdr)); + hdr[0] = 6; + hdr[1] = 0; + hdr[2] = 0; + tests_put_word_lh(hdr + 3, 0x0006); + tests_put_dword_lh(hdr + 5, 0x00000004UL); + + fprintf(stdout, "TEST NWREQ87VLMCASE %d\n", which); + fprintf(stdout, "connid=%u dhandle=%u\n", connid, dhandle); + tests_dump_bytes("HDR:", hdr, 9); + + switch (which) { + case 1: + len = tests_build_nwreq87_path_multi(path, dhandle, 0, 0, 1, + "LOGIN.EXE", 0, 0); + return tests_nwreq87vlm_one("1 h=dhandle style=0 LOGIN.EXE", connid, hdr, path, len); + + case 2: + len = tests_build_nwreq87_path_multi(path, dhandle, 0, 0, 2, + "PUBLIC", "LOGIN.EXE", 0); + return tests_nwreq87vlm_one("2 h=dhandle style=0 PUBLIC/LOGIN.EXE", connid, hdr, path, len); + + case 3: + len = tests_build_nwreq87_path_multi(path, 0, 0, 0, 2, + "PUBLIC", "LOGIN.EXE", 0); + return tests_nwreq87vlm_one("3 h=0 style=0 PUBLIC/LOGIN.EXE", connid, hdr, path, len); + + case 4: + len = tests_build_nwreq87_path_multi(path, dhandle, 0, 1, 1, + "LOGIN.EXE", 0, 0); + return tests_nwreq87vlm_one("4 h=dhandle style=1 LOGIN.EXE", connid, hdr, path, len); + + case 5: + len = tests_build_nwreq87_path_multi(path, 0, 0, 1, 2, + "PUBLIC", "LOGIN.EXE", 0); + return tests_nwreq87vlm_one("5 h=0 style=1 PUBLIC/LOGIN.EXE", connid, hdr, path, len); + } + + fprintf(stdout, "Valid cases: 1..5\n"); + return(1); +} + + +static void tests_set_frag_alt_len_off_seg(uint8 *buf, void *ptr, UI len) +{ + tests_put_word_lh(buf + 0, (uint16)len); + tests_put_word_lh(buf + 2, (uint16)FP_OFF(ptr)); + tests_put_word_lh(buf + 4, (uint16)FP_SEG(ptr)); +} + +static void tests_nwreq87vlmreg_help(void) +{ + fprintf(stdout, "Usage: TESTS NWREQ87VLMREG n\n"); + fprintf(stdout, "n=1..20, one VLM register/fragment-layout case\n"); + fprintf(stdout, "1-10 frag layout off,seg,len\n"); + fprintf(stdout, "11-20 frag layout len,off,seg\n"); + fprintf(stdout, "odd=stack 6,20,0 even=stack 0,20,6\n"); + fprintf(stdout, "reg maps repeat every 2 cases:\n"); + fprintf(stdout, " A AX=57 BX=reqs CX=conn DX=reps\n"); + fprintf(stdout, " B AX=57 BX=conn CX=reqs DX=reps\n"); + fprintf(stdout, " C AX=57 BX=reqs CX=reps DX=conn\n"); + fprintf(stdout, " D AX=57 BX=conn CX=reps DX=reqs\n"); + fprintf(stdout, " E AX=57 BX=reps CX=conn DX=reqs\n"); +} + +static int tests_nwreq87vlmreg(int argc, char *argv[]) +{ + int which = 1; + int layout_alt; + int local_case; + int stack_rev; + int regmap; + + uint8 connid = 0; + uint8 dhandle = 0; + uint8 hdr[16]; + uint8 path[300]; + uint8 repl[0x180]; + UI path_len; + + TEST_NWFRAG16 frags_req_std[2]; + TEST_NWFRAG16 frags_rep_std[2]; + uint8 frags_req_alt[12]; + uint8 frags_rep_alt[12]; + void *reqfragp; + void *repfragp; + + UI ax = 0x0057; + UI bx = 0; + UI cx = 0; + UI dx = 0; + UI p1 = 6; + UI p2 = 0x20; + UI p3 = 0; + int rc; + uint32 dw0; + uint32 dw4d; + + if (argc < 3) { + tests_nwreq87vlmreg_help(); + return(1); + } + + which = atoi(argv[2]); + if (which < 1 || which > 20) { + tests_nwreq87vlmreg_help(); + return(1); + } + + if (tests_current_conn_dhandle(&connid, &dhandle)) + return(1); + + memset(hdr, 0, sizeof(hdr)); + hdr[0] = 6; + hdr[1] = 0; + hdr[2] = 0; + tests_put_word_lh(hdr + 3, 0x0006); + tests_put_dword_lh(hdr + 5, 0x00000004UL); + + path_len = tests_build_nwreq87_path_multi(path, dhandle, 0, 0, 1, + "LOGIN.EXE", 0, 0); + + memset(repl, 0, sizeof(repl)); + memset(frags_req_std, 0, sizeof(frags_req_std)); + memset(frags_rep_std, 0, sizeof(frags_rep_std)); + memset(frags_req_alt, 0, sizeof(frags_req_alt)); + memset(frags_rep_alt, 0, sizeof(frags_rep_alt)); + + tests_set_frag(&frags_req_std[0], hdr, 9); + tests_set_frag(&frags_req_std[1], path, path_len); + tests_set_frag(&frags_rep_std[0], repl, 0x4d); + tests_set_frag(&frags_rep_std[1], repl + 0x4d, 0x100); + + tests_set_frag_alt_len_off_seg(frags_req_alt + 0, hdr, 9); + tests_set_frag_alt_len_off_seg(frags_req_alt + 6, path, path_len); + tests_set_frag_alt_len_off_seg(frags_rep_alt + 0, repl, 0x4d); + tests_set_frag_alt_len_off_seg(frags_rep_alt + 6, repl + 0x4d, 0x100); + + layout_alt = (which > 10); + local_case = layout_alt ? which - 10 : which; + stack_rev = ((local_case & 1) == 0); + regmap = (local_case + 1) / 2; /* 1..5 */ + + if (layout_alt) { + reqfragp = frags_req_alt; + repfragp = frags_rep_alt; + } else { + reqfragp = frags_req_std; + repfragp = frags_rep_std; + } + + if (stack_rev) { + p1 = 0; + p2 = 0x20; + p3 = 6; + } + + switch (regmap) { + case 1: + bx = 2; cx = connid; dx = 2; + break; + case 2: + bx = connid; cx = 2; dx = 2; + break; + case 3: + bx = 2; cx = 2; dx = connid; + break; + case 4: + bx = connid; cx = 2; dx = 2; + break; + case 5: + bx = 2; cx = connid; dx = 2; + break; + } + + /* + * Cases 2 and 4 share registers but differ stack order. + * Cases 1 and 9 share registers but let us repeat with same stack/table + * for sanity. Keep explicit output so we know exactly what was run. + */ + + fprintf(stdout, "TEST NWREQ87VLMREG case=%d\n", which); + fprintf(stdout, "layout=%s stack=%s regmap=%d\n", + layout_alt ? "len,off,seg" : "off,seg,len", + stack_rev ? "0,20,6" : "6,20,0", + regmap); + fprintf(stdout, "connid=%u dhandle=%u path_len=%u\n", connid, dhandle, path_len); + fprintf(stdout, "AX=%04X BX=%04X CX=%04X DX=%04X p1=%04X p2=%04X p3=%04X\n", + ax, bx, cx, dx, p1, p2, p3); + tests_dump_bytes("HDR :", hdr, 9); + tests_dump_bytes("PATH:", path, path_len > 48 ? 48 : path_len); + + rc = Net_Call_VLM_Raw(ax, bx, cx, dx, reqfragp, repfragp, p1, p2, p3); + + fprintf(stdout, "RC=%04X\n", rc); + tests_dump_bytes("REPL0 :", repl, 32); + tests_dump_bytes("REPL4D:", repl + 0x4d, 32); + dw0 = tests_get_dword_lh(repl); + dw4d = tests_get_dword_lh(repl + 0x4d); + fprintf(stdout, "DW0=%08lX DW4D=%08lX\n", dw0, dw4d); + + return(0); +} + + +static int tests_nwreq87vlmconn_one(UI conn_value, UI label_value) +{ + uint8 connid = 0; + uint8 dhandle = 0; + uint8 hdr[16]; + uint8 path[300]; + uint8 repl[0x180]; + TEST_NWFRAG16 reqfrags[2]; + TEST_NWFRAG16 replfrags[2]; + UI path_len; + int rc; + uint32 dw0; + uint32 dw4d; + + if (tests_current_conn_dhandle(&connid, &dhandle)) + return(1); + + memset(hdr, 0, sizeof(hdr)); + hdr[0] = 6; + hdr[1] = 0; + hdr[2] = 0; + tests_put_word_lh(hdr + 3, 0x0006); + tests_put_dword_lh(hdr + 5, 0x00000004UL); + + path_len = tests_build_nwreq87_path_multi(path, dhandle, 0, 0, 1, + "LOGIN.EXE", 0, 0); + + memset(repl, 0, sizeof(repl)); + memset(reqfrags, 0, sizeof(reqfrags)); + memset(replfrags, 0, sizeof(replfrags)); + + tests_set_frag(&reqfrags[0], hdr, 9); + tests_set_frag(&reqfrags[1], path, path_len); + tests_set_frag(&replfrags[0], repl, 0x4d); + tests_set_frag(&replfrags[1], repl + 0x4d, 0x100); + + fprintf(stdout, "TEST NWREQ87VLMCONN value=%u label=%u\n", + conn_value, label_value); + fprintf(stdout, "drive connid=%u dhandle=%u path_len=%u\n", + connid, dhandle, path_len); + fprintf(stdout, "VLM call: AX=0057 BX=0002 CX=%04X DX=0002 p1=0006 p2=0020 p3=0000\n", + conn_value); + tests_dump_bytes("HDR :", hdr, 9); + tests_dump_bytes("PATH:", path, path_len > 48 ? 48 : path_len); + + rc = Net_Call_VLM_Raw(0x0057, 2, conn_value, 2, + reqfrags, replfrags, + 6, 0x20, 0); + + fprintf(stdout, "RC=%04X\n", rc); + tests_dump_bytes("REPL0 :", repl, 32); + tests_dump_bytes("REPL4D:", repl + 0x4d, 32); + + dw0 = tests_get_dword_lh(repl); + dw4d = tests_get_dword_lh(repl + 0x4d); + fprintf(stdout, "DW0=%08lX DW4D=%08lX\n", dw0, dw4d); + + return(rc); +} + +static int tests_nwreq87vlmconn(int argc, char *argv[]) +{ + UI value; + + if (argc < 3) { + fprintf(stdout, "Usage: TESTS NWREQ87VLMCONN value\n"); + fprintf(stdout, "Examples:\n"); + fprintf(stdout, " TESTS NWREQ87VLMCONN 0\n"); + fprintf(stdout, " TESTS NWREQ87VLMCONN 1\n"); + fprintf(stdout, " TESTS NWREQ87VLMCONN 2\n"); + fprintf(stdout, "Hex is not parsed; use decimal values.\n"); + return(1); + } + + value = (UI)atoi(argv[2]); + return tests_nwreq87vlmconn_one(value, value); +} + +static int tests_nwreq87vlmconnmatrix(void) +{ + UI values[10]; + int i; + uint8 connid = 0; + uint8 dhandle = 0; + uint8 flags = 0; + int drive; + + drive = tests_get_current_drive(); + get_drive_info((uint8)drive, &connid, &dhandle, &flags); + + /* + * Paged matrix: one screen per value. + * Start with likely connection-reference candidates. + */ + values[0] = 0; + values[1] = 1; + values[2] = 2; + values[3] = 3; + values[4] = (UI)connid; + values[5] = (UI)dhandle; + values[6] = (UI)drive; + values[7] = (UI)(drive + 1); + values[8] = 0x31; /* low byte observed in AH=DC CX=3130 */ + values[9] = 0x3130; /* AH=DC returned CX */ + + fprintf(stdout, "TEST NWREQ87VLMCONNMATRIX paged\n"); + fprintf(stdout, "current drive=%c: connid=%u dhandle=%u flags=%02X\n", + 'A' + drive, connid, dhandle, flags); + tests_wait_key(); + + for (i = 0; i < 10; i++) { + tests_nwreq87vlmconn_one(values[i], (UI)i); + if (i != 9) + tests_wait_key(); + } + + return(0); +} + + +static UI tests_build_novell_handle_path(uint8 *buf, uint8 dhandle, + uint16 dirbase, uint8 style, + int count, + char *c1, char *c2, char *c3) +{ + uint8 *p; + int l; + UI used; + + /* + * DeveloperNet fillhan.o: + * memset(pathStruct, 0, 0x133) + * if handle != 0: + * byte[5] = 0 + * word[1] = handle + * word[3] = 0 + * else: + * byte[5] = 0xff + * + * _NWGETCOMPATHSTRUCTLENGTH reads component count at offset 6 and returns + * 7 + sum(1 + componentLen) + * + * ncpdos16 87s6.c sends frag2 from struct start with length stored at +13c. + */ + memset(buf, 0, 0x140); + + if (dhandle) { + tests_put_word_lh(buf + 1, (uint16)dhandle); + tests_put_word_lh(buf + 3, dirbase); + buf[5] = style; + } else { + buf[5] = 0xff; + } + + p = buf + 6; + *p++ = (uint8)count; + + if (count > 0 && c1) { + l = strlen(c1); if (l > 255) l = 255; + *p++ = (uint8)l; memcpy(p, c1, l); p += l; + } + if (count > 1 && c2) { + l = strlen(c2); if (l > 255) l = 255; + *p++ = (uint8)l; memcpy(p, c2, l); p += l; + } + if (count > 2 && c3) { + l = strlen(c3); if (l > 255) l = 255; + *p++ = (uint8)l; memcpy(p, c3, l); p += l; + } + + used = (UI)(p - buf); + tests_put_word_lh(buf + 0x13c, used); + return used; +} + +static int tests_nwreq87vlmfull_one(char *label, uint8 connid, + uint8 *hdr, uint8 *path_struct) +{ + uint8 repl[0x180]; + TEST_NWFRAG16 reqfrags[2]; + TEST_NWFRAG16 replfrags[2]; + UI path_len; + int rc; + uint32 dw0; + uint32 dw4d; + + path_len = (UI)(path_struct[0x13c] | ((UI)path_struct[0x13d] << 8)); + + memset(repl, 0, sizeof(repl)); + memset(reqfrags, 0, sizeof(reqfrags)); + memset(replfrags, 0, sizeof(replfrags)); + + tests_set_frag(&reqfrags[0], hdr, 9); + tests_set_frag(&reqfrags[1], path_struct, path_len); + tests_set_frag(&replfrags[0], repl, 0x4d); + tests_set_frag(&replfrags[1], repl + 0x4d, 0x100); + + fprintf(stdout, "\nCASE %s\n", label); + fprintf(stdout, "path_len=%u\n", path_len); + tests_dump_bytes("PATH:", path_struct, path_len > 64 ? 64 : path_len); + + rc = Net_Call_VLM_Raw(0x0057, 2, (UI)connid, 2, + reqfrags, replfrags, + 6, 0x20, 0); + + fprintf(stdout, "RC=%04X\n", rc); + tests_dump_bytes("REPL0 :", repl, 32); + tests_dump_bytes("REPL4D:", repl + 0x4d, 32); + dw0 = tests_get_dword_lh(repl); + dw4d = tests_get_dword_lh(repl + 0x4d); + fprintf(stdout, "DW0=%08lX DW4D=%08lX\n", dw0, dw4d); + tests_wait_key(); + + return rc; +} + +static int tests_nwreq87vlmfull(void) +{ + uint8 connid = 0; + uint8 dhandle = 0; + uint8 flags = 0; + int drive; + uint8 hdr[16]; + uint8 path[0x140]; + + drive = tests_get_current_drive(); + if (get_drive_info((uint8)drive, &connid, &dhandle, &flags)) { + fprintf(stdout, "get_drive_info failed\n"); + return(1); + } + + memset(hdr, 0, sizeof(hdr)); + hdr[0] = 6; + hdr[1] = 0; + hdr[2] = 0; + tests_put_word_lh(hdr + 3, 0x0006); + tests_put_dword_lh(hdr + 5, 0x00000004UL); + + fprintf(stdout, "TEST NWREQ87VLMFULL Novell handle/path struct\n"); + fprintf(stdout, "drive=%c: connid=%u dhandle=%u flags=%02X\n", + 'A' + drive, connid, dhandle, flags); + tests_dump_bytes("HDR:", hdr, 9); + + tests_build_novell_handle_path(path, dhandle, 0, 0, 1, + "LOGIN.EXE", 0, 0); + tests_nwreq87vlmfull_one("1 dhandle LOGIN.EXE", connid, hdr, path); + + tests_build_novell_handle_path(path, dhandle, 0, 0, 2, + "PUBLIC", "LOGIN.EXE", 0); + tests_nwreq87vlmfull_one("2 dhandle PUBLIC/LOGIN.EXE", connid, hdr, path); + + tests_build_novell_handle_path(path, 0, 0, 0, 2, + "PUBLIC", "LOGIN.EXE", 0); + tests_nwreq87vlmfull_one("3 handle0 PUBLIC/LOGIN.EXE", connid, hdr, path); + + return(0); +} + + +static uint16 tests_get_word_lh(uint8 *p) +{ + return (uint16)(p[0] | ((uint16)p[1] << 8)); +} + +static uint32 tests_get_dword_lh2(uint8 *p) +{ + return ((uint32)p[0] | + ((uint32)p[1] << 8) | + ((uint32)p[2] << 16) | + ((uint32)p[3] << 24)); +} + +static int tests_c32type(void) +{ + uint8 out[32]; + int rc; + uint16 ax, bx, cx, dx, si, dsreg, esreg; + uint32 esi, ecx; + + memset(out, 0, sizeof(out)); + + fprintf(stdout, "TEST Client32/NIOS probe ESI/ECX\n"); + fprintf(stdout, "16-bit OMF, manual 386 stores after INT 2F AX=D8C1\n\n"); + + rc = C32_LoadNios_Probe(0xD8C1, out); + + ax = tests_get_word_lh(out + 0); + esi = tests_get_dword_lh2(out + 2); + ecx = tests_get_dword_lh2(out + 6); + bx = tests_get_word_lh(out + 10); + cx = tests_get_word_lh(out + 12); + dx = tests_get_word_lh(out + 14); + si = tests_get_word_lh(out + 16); + dsreg = tests_get_word_lh(out + 18); + esreg = tests_get_word_lh(out + 20); + + fprintf(stdout, "C32 probe rc=%04X\n", rc); + fprintf(stdout, "INT2F D8C1 result AX=%04X\n", ax); + fprintf(stdout, "ESI=%08lX ECX=%08lX\n", esi, ecx); + fprintf(stdout, "BX=%04X CX=%04X DX=%04X SI=%04X\n", bx, cx, dx, si); + fprintf(stdout, "DS=%04X ES=%04X\n", dsreg, esreg); + tests_dump_bytes("RAW:", out, 22); + + if (ax == 0) + fprintf(stdout, "\nClient32/NIOS D8C1 reports success.\n"); + else + fprintf(stdout, "\nClient32/NIOS D8C1 did not report success.\n"); + + return(0); +} + + +static void tests_c32get_one(char *name) +{ + uint8 out[32]; + uint16 load_ax; + uint32 esi, ecx; + uint16 ret_ax, ret_dx; + + memset(out, 0, sizeof(out)); + C32_GetFunc_Probe(name, out); + + load_ax = tests_get_word_lh(out + 0); + esi = tests_get_dword_lh2(out + 2); + ecx = tests_get_dword_lh2(out + 6); + ret_ax = tests_get_word_lh(out + 10); + ret_dx = tests_get_word_lh(out + 12); + + fprintf(stdout, "\nFUNC %s\n", name); + fprintf(stdout, "Load AX=%04X ESI=%08lX ECX=%08lX\n", load_ax, esi, ecx); + fprintf(stdout, "Resolver returned DX:AX=%04X:%04X\n", ret_dx, ret_ax); + tests_dump_bytes("RAW:", out, 18); +} + +static int tests_c32getfunc(void) +{ + fprintf(stdout, "TEST C32GETFUNC\n"); + fprintf(stdout, "Resolve Client32 function addresses via NIOS resolver\n"); + + tests_c32get_one("CLIENT32GetVersion"); + tests_c32get_one("COMPATNcpRequestReply"); + tests_c32get_one("CONNOpenByReference"); + tests_c32get_one("CONNClose"); + + return(0); +} + + +static int tests_c32callver2(void) +{ + uint8 out[32]; + uint16 load_ax; + uint16 res_off, res_seg; + uint16 tramp_off, tramp_seg; + uint16 fn_off, fn_seg; + uint16 call_ax, call_dx, flags; + + memset(out, 0, sizeof(out)); + + fprintf(stdout, "TEST C32CALLVER2\n"); + fprintf(stdout, "Call CLIENT32GetVersion through ECX NIOS trampoline\n\n"); + + C32_CallVersion_Nios_Probe(out); + + load_ax = tests_get_word_lh(out + 0); + res_off = tests_get_word_lh(out + 2); + res_seg = tests_get_word_lh(out + 4); + tramp_off = tests_get_word_lh(out + 6); + tramp_seg = tests_get_word_lh(out + 8); + fn_off = tests_get_word_lh(out + 10); + fn_seg = tests_get_word_lh(out + 12); + call_ax = tests_get_word_lh(out + 14); + call_dx = tests_get_word_lh(out + 16); + flags = tests_get_word_lh(out + 18); + + fprintf(stdout, "Load AX=%04X\n", load_ax); + fprintf(stdout, "Resolver=%04X:%04X Trampoline=%04X:%04X\n", + res_seg, res_off, tramp_seg, tramp_off); + fprintf(stdout, "Function=%04X:%04X\n", fn_seg, fn_off); + fprintf(stdout, "Call returned DX:AX=%04X:%04X FLAGS=%04X\n", + call_dx, call_ax, flags); + tests_dump_bytes("RAW:", out, 20); + + if (load_ax == 0 && (fn_off || fn_seg)) + fprintf(stdout, "\nNIOS trampoline path resolved successfully.\n"); + return(0); +} + + +static int tests_c32mapflat(void) +{ + uint8 out[32]; + char sample[32]; + uint16 load_ax, esi_off, esi_seg; + uint16 map_lo, map_hi; + uint16 unl_ax, unl_dx; + + strcpy(sample, "C32MAPFLAT"); + memset(out, 0, sizeof(out)); + + fprintf(stdout, "TEST C32MAPFLAT\n"); + fprintf(stdout, "Probe d32wrap __MapLockFlat/__UnlockFlat via NIOS resolver\n"); + fprintf(stdout, "sample ptr=%04X:%04X len=%u text=%s\n", + FP_SEG(sample), FP_OFF(sample), (UI)strlen(sample) + 1, sample); + + C32_MapLock_Probe(sample, (UI)strlen(sample) + 1, out); + + load_ax = tests_get_word_lh(out + 0); + esi_off = tests_get_word_lh(out + 2); + esi_seg = tests_get_word_lh(out + 4); + map_lo = tests_get_word_lh(out + 6); + map_hi = tests_get_word_lh(out + 8); + unl_ax = tests_get_word_lh(out + 10); + unl_dx = tests_get_word_lh(out + 12); + + fprintf(stdout, "Load AX=%04X Resolver=%04X:%04X\n", + load_ax, esi_seg, esi_off); + fprintf(stdout, "MapLock returned flat=%04X:%04X\n", map_hi, map_lo); + fprintf(stdout, "Unlock returned DX:AX=%04X:%04X\n", unl_dx, unl_ax); + tests_dump_bytes("RAW:", out, 14); + + if (load_ax == 0 && (map_lo || map_hi)) + fprintf(stdout, "\nMapLockFlat appears to work.\n"); + else + fprintf(stdout, "\nMapLockFlat did not return a flat pointer.\n"); + + return(0); +} + + + + + +static void tests_set_reg_word(uint8 *r, int off, UI val) +{ + tests_put_word_lh(r + off, (uint16)val); +} + +static UI tests_get_reg_word(uint8 *r, int off) +{ + return (UI)(r[off] | ((UI)r[off + 1] << 8)); +} + +static void tests_dump_vlm_regs(char *title, uint8 *r) +{ + fprintf(stdout, "%s SI=%04X DS=%04X DI=%04X ES=%04X AX=%04X BX=%04X CX=%04X DX=%04X\n", + title, + tests_get_reg_word(r, 0), + tests_get_reg_word(r, 2), + tests_get_reg_word(r, 4), + tests_get_reg_word(r, 6), + tests_get_reg_word(r, 8), + tests_get_reg_word(r, 10), + tests_get_reg_word(r, 12), + tests_get_reg_word(r, 14)); +} + + + + + + + + + + + + + +static unsigned long tests_get_dword_be_local(uint8 *p) +{ + return (((unsigned long)p[0]) << 24) | + (((unsigned long)p[1]) << 16) | + (((unsigned long)p[2]) << 8) | + ((unsigned long)p[3]); +} + +static unsigned long tests_get_dword_le_local(uint8 *p) +{ + return ((unsigned long)p[0]) | + (((unsigned long)p[1]) << 8) | + (((unsigned long)p[2]) << 16) | + (((unsigned long)p[3]) << 24); +} + +static int tests_ncp87c32auto(void) +{ + uint8 connid = 0; + uint8 dhandle = 0; + uint8 flags = 0; + int drive; + + uint8 mapout[32]; + uint8 openout[32]; + uint8 rawout[32]; + + uint16 map_ret_ax, map_ret_dx; + uint16 cref_lo, cref_hi; + uint16 open_ret_ax, open_ret_dx; + uint16 handle_lo, handle_hi; + uint16 raw_ret_ax, raw_ret_dx; + uint16 actual_lo, actual_hi; + + uint8 hdr[16]; + uint8 path[0x140]; + uint8 rep0[0x60]; + uint8 rep1[0x110]; + UI path_len; + + drive = tests_get_current_drive(); + get_drive_info((uint8)drive, &connid, &dhandle, &flags); + + fprintf(stdout, "TEST NCP87C32AUTO\n"); + fprintf(stdout, "Auto Client32 path: MAPVAR len=4 flag=0 -> OPENREF -> NCP87/S6\n"); + fprintf(stdout, "drive=%c: connid=%u dhandle=%u flags=%02X\n", + 'A' + drive, connid, dhandle, flags); + + memset(mapout, 0, sizeof(mapout)); + C32_MapVar_Probe(4, 0, mapout); + + map_ret_ax = tests_get_word_lh(mapout + 14); + map_ret_dx = tests_get_word_lh(mapout + 16); + cref_lo = tests_get_word_lh(mapout + 22); /* connRefLocal */ + cref_hi = tests_get_word_lh(mapout + 24); + + fprintf(stdout, "\nSTEP1 C32MAPVAR 4 0\n"); + fprintf(stdout, "Return DX:AX=%04X:%04X connRefLocal=%04X:%04X\n", + map_ret_dx, map_ret_ax, cref_hi, cref_lo); + tests_dump_bytes("MAP OUT:", mapout, 26); + + if (map_ret_ax != 0 || map_ret_dx != 0 || (cref_lo == 0 && cref_hi == 0)) { + fprintf(stdout, "MAP step failed or returned empty connRef.\n"); + return(1); + } + + memset(openout, 0, sizeof(openout)); + C32_OpenRef_Probe(cref_lo, cref_hi, openout); + + open_ret_ax = tests_get_word_lh(openout + 14); + open_ret_dx = tests_get_word_lh(openout + 16); + handle_lo = tests_get_word_lh(openout + 18); + handle_hi = tests_get_word_lh(openout + 20); + + fprintf(stdout, "\nSTEP2 C32OPENREF %u %u\n", cref_lo, cref_hi); + fprintf(stdout, "Return DX:AX=%04X:%04X handle=%04X:%04X\n", + open_ret_dx, open_ret_ax, handle_hi, handle_lo); + tests_dump_bytes("OPEN OUT:", openout, 22); + + if (open_ret_ax != 0 || open_ret_dx != 0 || (handle_lo == 0 && handle_hi == 0)) { + fprintf(stdout, "OPENREF failed or returned empty handle.\n"); + return(1); + } + + memset(hdr, 0, sizeof(hdr)); + hdr[0] = 6; + hdr[1] = 0; + hdr[2] = 0; + tests_put_word_lh(hdr + 3, 0x0006); + tests_put_dword_lh(hdr + 5, 0x00000004UL); /* RIM_ATTRIBUTES */ + + path_len = tests_build_novell_handle_path(path, dhandle, 0, 0, 1, + "LOGIN.EXE", 0, 0); + + memset(rep0, 0, sizeof(rep0)); + memset(rep1, 0, sizeof(rep1)); + memset(rawout, 0, sizeof(rawout)); + + C32_NCP87_Raw5_Probe(handle_lo, handle_hi, + hdr, 9, + path, path_len, + rep0, 0x4d, + rep1, 0x100, + rawout); + + raw_ret_ax = tests_get_word_lh(rawout + 14); + raw_ret_dx = tests_get_word_lh(rawout + 16); + actual_lo = tests_get_word_lh(rawout + 18); + actual_hi = tests_get_word_lh(rawout + 20); + + fprintf(stdout, "\nSTEP3 NWREQ87C32RAW5 handle=%04X:%04X\n", + handle_hi, handle_lo); + fprintf(stdout, "Return DX:AX=%04X:%04X actual=%04X:%04X\n", + raw_ret_dx, raw_ret_ax, actual_hi, actual_lo); + tests_dump_bytes("RAW OUT:", rawout, 22); + tests_dump_bytes("REP0:", rep0, 80); + tests_dump_bytes("REP1:", rep1, 64); + + if (raw_ret_ax == 0 && raw_ret_dx == 0) { + fprintf(stdout, "\nNCP87C32AUTO OK\n"); + fprintf(stdout, "attr guess BE dword[0]=%08lX LE dword[0]=%08lX\n", + tests_get_dword_be_local(rep0 + 0), + tests_get_dword_le_local(rep0 + 0)); + fprintf(stdout, "dword[4] BE=%08lX LE=%08lX\n", + tests_get_dword_be_local(rep0 + 4), + tests_get_dword_le_local(rep0 + 4)); + } + + return(0); +} + + + +/* + * Reusable Client32 helpers for FLAG and future DOS tools. + * + * These are intentionally not test/probe commands. They wrap the verified + * Client32 sequence: + * + * C32_MapVar_Probe(4,0) -> connRefLocal FFFF:FFFE + * C32_OpenRef_Probe(connRefLocal) -> Client32 handle, e.g. 0101:0001 + * C32_NCP87_Raw5_Probe(handle) -> NCP87/S6 + */ +static int c32_get_ncp_handle(uint16 *handle_lo, uint16 *handle_hi) +{ + uint8 mapout[32]; + uint8 openout[32]; + uint16 map_ret_ax, map_ret_dx; + uint16 cref_lo, cref_hi; + uint16 open_ret_ax, open_ret_dx; + + if (!handle_lo || !handle_hi) + return(1); + + *handle_lo = 0; + *handle_hi = 0; + + memset(mapout, 0, sizeof(mapout)); + C32_MapVar_Probe(4, 0, mapout); + + map_ret_ax = tests_get_word_lh(mapout + 14); + map_ret_dx = tests_get_word_lh(mapout + 16); + cref_lo = tests_get_word_lh(mapout + 22); + cref_hi = tests_get_word_lh(mapout + 24); + + if (map_ret_ax != 0 || map_ret_dx != 0 || (cref_lo == 0 && cref_hi == 0)) + return(2); + + memset(openout, 0, sizeof(openout)); + C32_OpenRef_Probe(cref_lo, cref_hi, openout); + + open_ret_ax = tests_get_word_lh(openout + 14); + open_ret_dx = tests_get_word_lh(openout + 16); + *handle_lo = tests_get_word_lh(openout + 18); + *handle_hi = tests_get_word_lh(openout + 20); + + if (open_ret_ax != 0 || open_ret_dx != 0 || (*handle_lo == 0 && *handle_hi == 0)) + return(3); + + return(0); +} + +static int c32_ncp87_obtain_rim_attributes(const char *name, + uint16 dir_handle, + unsigned long *attr_out, + uint16 *actual_out, + uint16 *handle_lo_out, + uint16 *handle_hi_out) +{ + uint16 handle_lo, handle_hi; + uint8 hdr[16]; + uint8 path[0x140]; + uint8 rep0[0x60]; + uint8 rep1[0x110]; + uint8 rawout[32]; + UI path_len; + uint16 raw_ret_ax, raw_ret_dx; + uint16 actual_lo; + int rc; + + if (!name || !attr_out) + return(1); + + *attr_out = 0; + if (actual_out) + *actual_out = 0; + if (handle_lo_out) + *handle_lo_out = 0; + if (handle_hi_out) + *handle_hi_out = 0; + + rc = c32_get_ncp_handle(&handle_lo, &handle_hi); + if (rc) + return(10 + rc); + + memset(hdr, 0, sizeof(hdr)); + hdr[0] = 6; + hdr[1] = 0; + hdr[2] = 0; + tests_put_word_lh(hdr + 3, 0x0006); /* NCP87 subfunction 6 */ + tests_put_dword_lh(hdr + 5, 0x00000004UL); /* RIM_ATTRIBUTES */ + + path_len = tests_build_novell_handle_path(path, dir_handle, 0, 0, 1, + (char *)name, 0, 0); + + memset(rep0, 0, sizeof(rep0)); + memset(rep1, 0, sizeof(rep1)); + memset(rawout, 0, sizeof(rawout)); + + C32_NCP87_Raw5_Probe(handle_lo, handle_hi, + hdr, 9, + path, path_len, + rep0, 0x4d, + rep1, 0x100, + rawout); + + raw_ret_ax = tests_get_word_lh(rawout + 14); + raw_ret_dx = tests_get_word_lh(rawout + 16); + actual_lo = tests_get_word_lh(rawout + 18); + + if (raw_ret_ax != 0 || raw_ret_dx != 0) + return(20); + + /* + * Verified reply layout for RIM_ATTRIBUTES: + * REP0+4 little-endian dword = DOS attributes + * Example LOGIN.EXE: 20h archive. + */ + *attr_out = tests_get_dword_le_local(rep0 + 4); + + if (actual_out) + *actual_out = actual_lo; + if (handle_lo_out) + *handle_lo_out = handle_lo; + if (handle_hi_out) + *handle_hi_out = handle_hi; + + return(0); +} + +static int tests_ncp87c32attr(void) +{ + uint8 connid = 0; + uint8 dhandle = 0; + uint8 flags = 0; + int drive; + unsigned long attr; + uint16 actual; + uint16 handle_lo; + uint16 handle_hi; + int rc; + + drive = tests_get_current_drive(); + get_drive_info((uint8)drive, &connid, &dhandle, &flags); + + rc = c32_ncp87_obtain_rim_attributes("LOGIN.EXE", + (uint16)dhandle, + &attr, + &actual, + &handle_lo, + &handle_hi); + if (rc) { + fprintf(stdout, "NCP87C32ATTR failed rc=%d\n", rc); + return(rc); + } + + fprintf(stdout, "NCP87C32ATTR LOGIN.EXE attr=%02lX handle=%04X:%04X actual=%04X\n", + attr & 0xffUL, handle_hi, handle_lo, actual); + + return(0); +} + +int func_tests(int argc, char *argv[], int mode) +{ + if (argc >= 2) { + if (tests_same_arg(argv[1], "NETCALL")) + return tests_netcall(); + + if (tests_same_arg(argv[1], "E300") || tests_same_arg(argv[1], "NETCALLE300")) + return tests_netcall_e300(); + + if (tests_same_arg(argv[1], "C32TYPE")) + return tests_c32type(); + + if (tests_same_arg(argv[1], "C32GETFUNC")) + return tests_c32getfunc(); + + if (tests_same_arg(argv[1], "C32CALLVER2")) + return tests_c32callver2(); + + if (tests_same_arg(argv[1], "C32MAPFLAT")) + return tests_c32mapflat(); + + if (tests_same_arg(argv[1], "NCP87C32ATTR")) + return tests_ncp87c32attr(); + + if (tests_same_arg(argv[1], "NCP87C32AUTO")) + return tests_ncp87c32auto(); + if (tests_same_arg(argv[1], "NWREQ87")) + return tests_nwreq87(argc, argv); + + if (tests_same_arg(argv[1], "NWREQ87VLM")) + return tests_nwreq87vlm(argc, argv); + + if (tests_same_arg(argv[1], "NWREQ87VLMMATRIX")) + return tests_nwreq87vlmmatrix(argc, argv); + + if (tests_same_arg(argv[1], "NWREQ87VLMCASE")) + return tests_nwreq87vlmcase(argc, argv); + + if (tests_same_arg(argv[1], "NWREQ87VLMCONN")) + return tests_nwreq87vlmconn(argc, argv); + + if (tests_same_arg(argv[1], "NWREQ87VLMCONNMATRIX")) + return tests_nwreq87vlmconnmatrix(); + + if (tests_same_arg(argv[1], "NWREQ87VLMFULL")) + return tests_nwreq87vlmfull(); + + if (tests_same_arg(argv[1], "NWREQ87VLMREG")) + return tests_nwreq87vlmreg(argc, argv); + + if (tests_same_arg(argv[1], "NCPF2")) + return tests_ncpf2(argc, argv); + + if (tests_same_arg(argv[1], "OLD")) + return tests_old(argc - 1, argv + 1, mode); + + if (tests_same_arg(argv[1], "/?") || tests_same_arg(argv[1], "-?") || + tests_same_arg(argv[1], "?")) { + tests_usage(); + return(0); + } + } + + /* + * Default and unknown arguments keep the historical nwtests.c behavior. + */ + return tests_old(argc, argv, mode); +} +