You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
667 lines
14 KiB
667 lines
14 KiB
page ,132
|
|
; SCCSID = @(#)tenv.asm 4.2 85/08/16
|
|
; SCCSID = @(#)tenv.asm 4.2 85/08/16
|
|
TITLE Part6 COMMAND Transient routines.
|
|
;/*
|
|
; * Microsoft Confidential
|
|
; * Copyright (C) Microsoft Corporation 1991
|
|
; * All Rights Reserved.
|
|
; */
|
|
|
|
; Environment utilities and misc. routines
|
|
;
|
|
; Revision History
|
|
; ================
|
|
;
|
|
; M024 SR 9/5/90 Zero out comspec_flag to fix bug
|
|
; #710 about comspec getting trashed.
|
|
;
|
|
|
|
|
|
|
|
INCLUDE comsw.asm
|
|
|
|
.xlist
|
|
.xcref
|
|
include dossym.inc
|
|
include syscall.inc
|
|
include arena.inc
|
|
include comseg.asm
|
|
include comequ.asm
|
|
include doscntry.inc ;an000;
|
|
include resmsg.equ
|
|
.list
|
|
.cref
|
|
|
|
|
|
DATARES SEGMENT PUBLIC BYTE ;AC000;
|
|
EXTRN comdrv:byte
|
|
EXTRN comspec_end:word
|
|
EXTRN dbcs_vector_addr:dword ;AN000;
|
|
EXTRN ENVIRSEG:WORD
|
|
EXTRN fucase_addr:word ;AC000;
|
|
EXTRN PutBackDrv:byte
|
|
EXTRN PutBackComSpec:byte
|
|
EXTRN RESTDIR:BYTE
|
|
DATARES ENDS
|
|
|
|
TRANDATA SEGMENT PUBLIC BYTE ;AC000;
|
|
EXTRN arg_buf_ptr:word
|
|
EXTRN comspec:byte
|
|
EXTRN comspec_flag:byte
|
|
EXTRN comspecstr:byte
|
|
EXTRN ENVERR_PTR:WORD
|
|
EXTRN PATH_TEXT:byte
|
|
EXTRN PROMPT_TEXT:byte
|
|
EXTRN SYNTMES_PTR:WORD
|
|
TRANDATA ENDS
|
|
|
|
TRANSPACE SEGMENT PUBLIC BYTE ;AC000;
|
|
EXTRN Arg_Buf:BYTE
|
|
EXTRN RESSEG:WORD
|
|
EXTRN USERDIR1:BYTE
|
|
TRANSPACE ENDS
|
|
|
|
TRANCODE SEGMENT PUBLIC byte
|
|
|
|
ASSUME CS:TRANGROUP,DS:NOTHING,ES:NOTHING,SS:NOTHING
|
|
|
|
EXTRN cerror:near
|
|
|
|
PUBLIC add_name_to_environment
|
|
PUBLIC add_prompt
|
|
PUBLIC delete_path
|
|
PUBLIC find_name_in_environment
|
|
PUBLIC find_path
|
|
PUBLIC find_prompt
|
|
PUBLIC move_name
|
|
PUBLIC restudir
|
|
PUBLIC restudir1
|
|
PUBLIC scan_double_null
|
|
PUBLIC scasb2
|
|
PUBLIC store_char
|
|
PUBLIC Testkanj ;AN000; 3/3/KK
|
|
PUBLIC upconv
|
|
PUBLIC GETENVSIZ
|
|
|
|
BREAK <Environment utilities>
|
|
ASSUME DS:TRANGROUP
|
|
|
|
break Prompt command
|
|
assume ds:trangroup,es:trangroup
|
|
|
|
ADD_PROMPT:
|
|
CALL DELETE_PROMPT ; DELETE ANY EXISTING PROMPT
|
|
CALL SCAN_DOUBLE_NULL
|
|
|
|
ADD_PROMPT2:
|
|
PUSH SI
|
|
CALL GETARG
|
|
POP SI
|
|
retz ; PRE SCAN FOR ARGUMENTS
|
|
CALL MOVE_NAME ; MOVE IN NAME
|
|
CALL GETARG
|
|
PUSH SI
|
|
JMP SHORT ADD_NAME
|
|
|
|
|
|
break The SET command
|
|
assume ds:trangroup,es:trangroup
|
|
|
|
;
|
|
; Input: DS:SI points to a CR terminated string
|
|
; Output: carry flag is set if no room
|
|
; otherwise name is added to environment
|
|
;
|
|
|
|
DISP_ENVj:
|
|
jmp DISP_ENV
|
|
|
|
ADD_NAME_TO_ENVIRONMENT:
|
|
CALL GETARG
|
|
JZ DISP_ENVj
|
|
;
|
|
; check if line contains exactly one equals sign
|
|
;
|
|
XOR BX,BX ;= COUNT IS 0
|
|
PUSH SI ;SAVE POINTER TO BEGINNING OF LINE
|
|
|
|
EQLP:
|
|
LODSB ;GET A CHAR
|
|
CMP AL,13 ;IF CR WE'RE ALL DONE
|
|
JZ QUEQ
|
|
CMP AL,'=' ;LOOK FOR = SIGN
|
|
JNZ EQLP ;NOT THERE, GET NEXT CHAR
|
|
INC BL ;OTHERWISE INCREMENT EQ COUNT
|
|
CMP BYTE PTR [SI],13 ;LOOK FOR CR FOLLOWING = SIGN
|
|
JNZ EQLP
|
|
INC BH ;SET BH=1 MEANS NO PARAMETERS
|
|
JMP EQLP ;AND LOOK FOR MORE
|
|
|
|
QUEQ:
|
|
POP SI ;RESTORE BEGINNING OF LINE
|
|
DEC BL ;ZERO FLAG MEANS ONLY ONE EQ
|
|
JZ ONEQ ;GOOD LINE
|
|
MOV DX,OFFSET TRANGROUP:SYNTMES_ptr
|
|
JMP CERROR
|
|
|
|
ONEQ:
|
|
PUSH BX
|
|
CALL DELETE_NAME_IN_ENVIRONMENT
|
|
POP BX
|
|
DEC BH
|
|
retz
|
|
|
|
CALL SCAN_DOUBLE_NULL
|
|
mov bx,di ; Save ptr to beginning of env var name
|
|
CALL MOVE_NAME
|
|
push si
|
|
xchg bx,di ; Switch ptrs to beginning and end of
|
|
; env var name
|
|
;
|
|
; We want to special-case COMSPEC. This is to reduce the amount of code
|
|
; necessary in the resident for re-reading the transient. Let's look for
|
|
; COMSPEC=
|
|
;
|
|
mov comspec_flag,0 ; clear flag ; M024
|
|
mov si,offset trangroup:comspecstr ; Load ptr to string "COMSPEC"
|
|
mov cx,4 ; If the new env var is comspec, set
|
|
repz cmpsw ; the comspec_flag
|
|
;
|
|
; Zero set => exact match
|
|
;
|
|
jnz not_comspec
|
|
inc comspec_flag ; comspec is changing ; M024
|
|
|
|
not_comspec:
|
|
mov di,bx ; Load ptr to end of env var name
|
|
|
|
ADD_NAME: ; Add the value of the new env var
|
|
pop si ; to the environment.
|
|
push si
|
|
|
|
add_name1:
|
|
LODSB
|
|
CMP AL,13
|
|
jz add_name_ret
|
|
CALL STORE_CHAR
|
|
JMP ADD_NAME1
|
|
|
|
add_name_ret:
|
|
pop si
|
|
cmp comspec_flag,0 ; If the new env var is comspec,
|
|
retz ; copy the value into the
|
|
;
|
|
; We have changed the COMSPEC variable. We need to update the resident
|
|
; pieces necessary to reread in the info. First, skip all delimiters
|
|
;
|
|
invoke ScanOff
|
|
mov es,[resseg] ; comspec var in the resident
|
|
assume es:resgroup
|
|
;
|
|
; Make sure that the printer knows where the beginning of the string is
|
|
;
|
|
mov di,offset resgroup:comspec
|
|
mov bx,di
|
|
;
|
|
; Generate drive letter for display
|
|
;
|
|
xor ax,ax ;g assume no drive first
|
|
mov comdrv,al ;g
|
|
push ax ;AN000; 3/3/KK
|
|
mov al,[si] ;AN000; 3/3/KK
|
|
call testkanj ;AN000; 3/3/KK
|
|
pop ax ;AN000; 3/3/KK
|
|
jnz GotDrive
|
|
cmp byte ptr [si+1],':' ; drive specified?
|
|
jnz GotDrive
|
|
mov al,[si] ; get his specified drive
|
|
call UpConv ; convert to uppercase
|
|
sub al,'A' ; convert to 0-based
|
|
add di,2
|
|
inc al ; convert to 1-based number
|
|
mov comdrv,al
|
|
;
|
|
; Stick the drive letter in the prompt message. Nothing special needs to be
|
|
; done here..
|
|
;
|
|
|
|
add al,'A'-1
|
|
|
|
GotDrive: ;g
|
|
mov PutBackComSpec.SubstPtr,di ;g point to beginning of name after drive
|
|
mov es:PutBackDrv,al
|
|
;
|
|
; Copy chars until delim
|
|
;
|
|
|
|
mov di,bx
|
|
|
|
copy_comspec:
|
|
lodsb
|
|
invoke Delim
|
|
jz CopyDone
|
|
cmp al,13
|
|
jz CopyDone
|
|
stosb
|
|
jmp short copy_comspec
|
|
|
|
CopyDone:
|
|
xor al,al ; Null terminate the string and quit
|
|
stosb
|
|
mov comspec_flag,0
|
|
dec di
|
|
mov comspec_end,di
|
|
|
|
ret
|
|
|
|
DISP_ENV:
|
|
MOV DS,[RESSEG]
|
|
ASSUME DS:RESGROUP
|
|
MOV DS,[ENVIRSEG]
|
|
ASSUME DS:NOTHING
|
|
XOR SI,SI
|
|
|
|
PENVLP:
|
|
CMP BYTE PTR [SI],0
|
|
retz
|
|
mov di,offset trangroup:arg_buf
|
|
|
|
PENVLP2:
|
|
LODSB
|
|
stosb
|
|
OR AL,AL
|
|
JNZ PENVLP2
|
|
mov dx,offset trangroup:arg_buf_ptr
|
|
push ds
|
|
push es
|
|
pop ds
|
|
invoke printf_crlf
|
|
pop ds
|
|
JMP PENVLP
|
|
|
|
ASSUME DS:TRANGROUP
|
|
|
|
DELETE_PATH:
|
|
MOV SI,OFFSET TRANGROUP:PATH_TEXT
|
|
JMP SHORT DELETE_NAME_IN_environment
|
|
|
|
DELETE_PROMPT:
|
|
MOV SI,OFFSET TRANGROUP:PROMPT_TEXT
|
|
|
|
DELETE_NAME_IN_environment:
|
|
;
|
|
; Input: DS:SI points to a "=" terminated string
|
|
; Output: carry flag is set if name not found
|
|
; otherwise name is deleted
|
|
;
|
|
PUSH SI
|
|
PUSH DS
|
|
CALL FIND ; ES:DI POINTS TO NAME
|
|
JC DEL1
|
|
MOV SI,DI ; SAVE IT
|
|
CALL SCASB2 ; SCAN FOR THE NUL
|
|
XCHG SI,DI
|
|
;SR;
|
|
; If we have only one env string, then the double null is lost when the last
|
|
;string is deleted and we have an invalid empty environment with only a
|
|
;single null. To avoid this, we will look for the double null case and then
|
|
;move an extra null char.
|
|
; Bugbug: The only possible problem is that the last pathstring
|
|
;will be followed by a triple null. Is this really a problem?
|
|
;
|
|
cmp byte ptr es:[si],0 ;null char?
|
|
jnz not_dnull ;no, we are at a double null
|
|
dec si ;point at the double null
|
|
not_dnull:
|
|
|
|
CALL GETENVSIZ
|
|
SUB CX,SI
|
|
PUSH ES
|
|
POP DS ; ES:DI POINTS TO NAME, DS:SI POINTS TO NEXT NAME
|
|
REP MOVSB ; DELETE THE NAME
|
|
|
|
DEL1:
|
|
POP DS
|
|
POP SI
|
|
return
|
|
|
|
FIND_PATH:
|
|
MOV SI,OFFSET TRANGROUP:PATH_TEXT
|
|
JMP SHORT FIND_NAME_IN_environment
|
|
|
|
FIND_PROMPT:
|
|
MOV SI,OFFSET TRANGROUP:PROMPT_TEXT
|
|
|
|
FIND_NAME_IN_environment:
|
|
;
|
|
; Input: DS:SI points to a "=" terminated string
|
|
; Output: ES:DI points to the arguments in the environment
|
|
; zero is set if name not found
|
|
; carry flag is set if name not valid format
|
|
;
|
|
CALL FIND ; FIND THE NAME
|
|
retc ; CARRY MEANS NOT FOUND
|
|
JMP SCASB1 ; SCAN FOR = SIGN
|
|
;
|
|
; On return of FIND1, ES:DI points to beginning of name
|
|
;
|
|
FIND:
|
|
CLD
|
|
CALL COUNT0 ; CX = LENGTH OF NAME
|
|
MOV ES,[RESSEG]
|
|
ASSUME ES:RESGROUP
|
|
MOV ES,[ENVIRSEG]
|
|
ASSUME ES:NOTHING
|
|
XOR DI,DI
|
|
|
|
FIND1:
|
|
PUSH CX
|
|
PUSH SI
|
|
PUSH DI
|
|
|
|
FIND11:
|
|
LODSB
|
|
CALL TESTKANJ
|
|
JZ NOTKANJ3
|
|
DEC SI
|
|
LODSW
|
|
INC DI
|
|
INC DI
|
|
CMP AX,ES:[DI-2]
|
|
JNZ FIND12
|
|
DEC CX
|
|
LOOP FIND11
|
|
JMP SHORT FIND12
|
|
|
|
NOTKANJ3:
|
|
CALL UPCONV
|
|
INC DI
|
|
CMP AL,ES:[DI-1]
|
|
JNZ FIND12
|
|
LOOP FIND11
|
|
|
|
FIND12:
|
|
POP DI
|
|
POP SI
|
|
POP CX
|
|
retz
|
|
PUSH CX
|
|
CALL SCASB2 ; SCAN FOR A NUL
|
|
POP CX
|
|
CMP BYTE PTR ES:[DI],0
|
|
JNZ FIND1
|
|
STC ; INDICATE NOT FOUND
|
|
return
|
|
|
|
COUNT0:
|
|
PUSH DS
|
|
POP ES
|
|
MOV DI,SI
|
|
|
|
COUNT1:
|
|
PUSH DI ; COUNT NUMBER OF CHARS UNTIL "="
|
|
CALL SCASB1
|
|
JMP SHORT COUNTX
|
|
|
|
COUNT2:
|
|
PUSH DI ; COUNT NUMBER OF CHARS UNTIL NUL
|
|
CALL SCASB2
|
|
|
|
COUNTX:
|
|
POP CX
|
|
SUB DI,CX
|
|
XCHG DI,CX
|
|
return
|
|
|
|
MOVE_NAME:
|
|
CMP BYTE PTR DS:[SI],13
|
|
retz
|
|
LODSB
|
|
|
|
;;;; IFDEF DBCS 3/3/KK
|
|
CALL TESTKANJ
|
|
JZ NOTKANJ1
|
|
CALL STORE_CHAR
|
|
LODSB
|
|
CALL STORE_CHAR
|
|
JMP SHORT MOVE_NAME
|
|
|
|
NOTKANJ1:
|
|
;;;; ENDIF 3/3/KK
|
|
|
|
CALL UPCONV
|
|
CALL STORE_CHAR
|
|
CMP AL,'='
|
|
JNZ MOVE_NAME
|
|
return
|
|
|
|
GETARG:
|
|
MOV SI,80H
|
|
LODSB
|
|
OR AL,AL
|
|
retz
|
|
invoke SCANOFF
|
|
CMP AL,13
|
|
return
|
|
|
|
;
|
|
; Point ES:DI to the final NULL string. Note that in an empty environment,
|
|
; there is NO double NULL, merely a string that is empty.
|
|
;
|
|
SCAN_DOUBLE_NULL:
|
|
MOV ES,[RESSEG]
|
|
ASSUME ES:RESGROUP
|
|
MOV ES,[ENVIRSEG]
|
|
ASSUME ES:NOTHING
|
|
XOR DI,DI
|
|
;
|
|
; Top cycle-point. If the string here is empty, then we are done
|
|
;
|
|
SDN1:
|
|
cmp byte ptr es:[di],0 ; nul string?
|
|
retz ; yep, all done
|
|
CALL SCASB2
|
|
JMP SDN1
|
|
|
|
SCASB1:
|
|
MOV AL,'=' ; SCAN FOR AN =
|
|
JMP SHORT SCASBX
|
|
SCASB2:
|
|
XOR AL,AL ; SCAN FOR A NUL
|
|
SCASBX:
|
|
MOV CX,1000H
|
|
REPNZ SCASB
|
|
return
|
|
;Bugbug: This is Kanji stuff - put it in conditionals
|
|
|
|
TESTKANJ:
|
|
push ds ;AN000; 3/3/KK
|
|
push si ;AN000; 3/3/KK
|
|
push ax ;AN000; 3/3/KK
|
|
mov ds,cs:[resseg] ;AN000; Get resident segment
|
|
assume ds:resgroup ;AN000;
|
|
lds si,dbcs_vector_addr ;AN000; get DBCS vector
|
|
ktlop: ;AN000; 3/3/KK
|
|
cmp word ptr ds:[si],0 ;AN000; end of Table 3/3/KK
|
|
je notlead ;AN000; 3/3/KK
|
|
pop ax ;AN000; 3/3/KK
|
|
push ax ;AN000; 3/3/KK
|
|
cmp al, byte ptr ds:[si] ;AN000; 3/3/KK
|
|
jb notlead ;AN000; 3/3/KK
|
|
inc si ;AN000; 3/3/KK
|
|
cmp al, byte ptr ds:[si] ;AN000; 3/3/KK
|
|
jbe islead ;AN000; 3/3/KK
|
|
inc si ;AN000; 3/3/KK
|
|
jmp short ktlop ;AN000; try another range ; 3/3/KK
|
|
Notlead: ;AN000; 3/3/KK
|
|
xor ax,ax ;AN000; set zero 3/3/KK
|
|
jmp short ktret ;AN000; 3/3/KK
|
|
Islead: ;AN000; 3/3/KK
|
|
xor ax,ax ;AN000; reset zero 3/3/KK
|
|
inc ax ;AN000; 3/3/KK
|
|
ktret: ;AN000; 3/3/KK
|
|
pop ax ;AN000; 3/3/KK
|
|
pop si ;AN000; 3/3/KK
|
|
pop ds ;AN000; 3/3/KK
|
|
return ;AN000; 3/3/KK
|
|
;------------------------------------- ;3/3/KK
|
|
|
|
|
|
; ****************************************************************
|
|
; *
|
|
; * ROUTINE: UPCONV (ADDED BY EMG 4.00)
|
|
; *
|
|
; * FUNCTION: This routine returns the upper case equivalent of
|
|
; * the character in AL from the file upper case table
|
|
; * in DOS if character if above ascii 128, else
|
|
; * subtracts 20H if between "a" and "z".
|
|
; *
|
|
; * INPUT: AL char to be upper cased
|
|
; * FUCASE_ADDR set to the file upper case table
|
|
; *
|
|
; * OUTPUT: AL upper cased character
|
|
; *
|
|
; ****************************************************************
|
|
|
|
assume ds:trangroup ;AN000;
|
|
|
|
upconv proc near ;AN000;
|
|
|
|
cmp al,80h ;AN000; see if char is > ascii 128
|
|
jb oth_fucase ;AN000; no - upper case math
|
|
sub al,80h ;AN000; only upper 128 chars in table
|
|
push ds ;AN000;
|
|
push bx ;AN000;
|
|
mov ds,[resseg] ;AN000; get resident data segment
|
|
assume ds:resgroup ;AN000;
|
|
lds bx,dword ptr fucase_addr+1 ;AN000; get table address
|
|
add bx,2 ;AN000; skip over first word
|
|
xlat ds:byte ptr [bx] ;AN000; convert to upper case
|
|
pop bx ;AN000;
|
|
pop ds ;AN000;
|
|
assume ds:trangroup ;AN000;
|
|
jmp short upconv_end ;AN000; we finished - exit
|
|
|
|
oth_fucase: ;AN000;
|
|
cmp al,small_a ;AC000; if between "a" and "z",
|
|
jb upconv_end ;AC000; subtract 20h to get
|
|
cmp al,small_z ;AC000; upper case equivalent.
|
|
ja upconv_end ;AC000;
|
|
sub al,20h ;AC000; Change lower-case to upper
|
|
|
|
upconv_end: ;AN000;
|
|
ret
|
|
|
|
upconv endp ;AN000;
|
|
|
|
|
|
;
|
|
; STORE A CHAR IN environment, GROWING IT IF NECESSARY
|
|
;
|
|
STORE_CHAR:
|
|
PUSH CX
|
|
PUSH BX
|
|
PUSH ES ;AN056;
|
|
PUSH DS ;AN056; Save local DS
|
|
MOV DS,[RESSEG] ;AN056; Get resident segment
|
|
ASSUME DS:RESGROUP ;AN056;
|
|
MOV ES,[ENVIRSEG] ;AN056; Get environment segment
|
|
ASSUME ES:NOTHING ;AN056;
|
|
POP DS ;AN056; Get local segment back
|
|
ASSUME DS:TRANGROUP ;AN056;
|
|
CALL GETENVSIZ
|
|
MOV BX,CX
|
|
SUB BX,2 ; SAVE ROOM FOR DOUBLE NULL
|
|
CMP DI,BX
|
|
JB STORE1
|
|
|
|
PUSH AX
|
|
PUSH CX
|
|
PUSH BX ; Save Size of environment
|
|
invoke FREE_TPA
|
|
POP BX
|
|
ADD BX,2 ; Recover true environment size
|
|
|
|
CMP BX, 8000H ; Don't let environment grow > 32K
|
|
JB ENVSIZ_OK
|
|
BAD_ENV_SIZE: ;AN056;
|
|
STC
|
|
JMP SHORT ENVNOSET
|
|
ENVSIZ_OK:
|
|
|
|
MOV CL,4
|
|
SHR BX,CL ; Convert back to paragraphs
|
|
INC BX ; Try to grow environment by one para
|
|
MOV CX,ES ;AN056; Get environment segment
|
|
ADD CX,BX ;AN056; Add in size of environment
|
|
ADD CX,020H ;AN056; Add in some TPA
|
|
MOV AX,CS ;AN056; Get the transient segment
|
|
CMP CX,AX ;AN056; Are we hitting the transient?
|
|
JNB BAD_ENV_SIZE ;AN056; Yes - don't do it!!!
|
|
MOV AH,SETBLOCK
|
|
INT 21h
|
|
ENVNOSET:
|
|
PUSHF
|
|
PUSH ES
|
|
MOV ES,[RESSEG]
|
|
invoke ALLOC_TPA
|
|
POP ES
|
|
POPF
|
|
POP CX
|
|
POP AX
|
|
JNC STORE1
|
|
POP ES ;AN056;
|
|
MOV DX,OFFSET TRANGROUP:ENVERR_ptr
|
|
JMP CERROR
|
|
STORE1:
|
|
STOSB
|
|
MOV WORD PTR ES:[DI],0 ; NULL IS AT END
|
|
POP ES ;AN056;
|
|
POP BX
|
|
POP CX
|
|
return
|
|
|
|
GETENVSIZ:
|
|
;Get size of environment in bytes, rounded up to paragraph boundry
|
|
;ES has environment segment
|
|
;Size returned in CX, all other registers preserved
|
|
|
|
PUSH ES
|
|
PUSH AX
|
|
MOV AX,ES
|
|
DEC AX ;Point at arena
|
|
MOV ES,AX
|
|
MOV AX,ES:[arena_size]
|
|
MOV CL,4
|
|
SHL AX,CL ;Convert to bytes
|
|
MOV CX,AX
|
|
POP AX
|
|
POP ES
|
|
return
|
|
|
|
|
|
ASSUME DS:TRANGROUP
|
|
|
|
|
|
RESTUDIR1:
|
|
PUSH DS
|
|
MOV DS,[RESSEG]
|
|
ASSUME DS:RESGROUP
|
|
CMP [RESTDIR],0
|
|
POP DS
|
|
ASSUME DS:TRANGROUP
|
|
retz
|
|
|
|
RESTUDIR:
|
|
MOV DX,OFFSET TRANGROUP:USERDIR1
|
|
MOV AH,CHDIR
|
|
INT 21h ; Restore users DIR
|
|
XOR AL,AL
|
|
invoke SETREST
|
|
RET56:
|
|
return
|
|
|
|
trancode ends
|
|
end
|
|
|