forked from KolibriOS/kolibrios
4a1560020b
1) Low level procedures for СD device: read sector, load tray, out tray; 2) Function 70/0 for ISO9660 - read file 2) Function 70/0 for ISO9660 - read directory in format of standard 1. git-svn-id: svn://kolibrios.org@87 a494cfbc-eb01-0410-851d-a64ba20cac60
632 lines
15 KiB
PHP
632 lines
15 KiB
PHP
uglobal
|
||
cd_current_pointer_of_input dd 0
|
||
cd_current_pointer_of_input_2 dd 0
|
||
cd_mem_location dd 0
|
||
cd_counter_block dd 0
|
||
endg
|
||
|
||
CDDataBuf equ 0x7000
|
||
|
||
reserve_cd:
|
||
|
||
cli
|
||
cmp [cd_status],0
|
||
je reserve_ok2
|
||
|
||
sti
|
||
call change_task
|
||
jmp reserve_hd1
|
||
|
||
reserve_ok2:
|
||
|
||
push eax
|
||
mov eax,[0x3000]
|
||
shl eax,5
|
||
mov eax,[eax+0x3000+4]
|
||
mov [cd_status],eax
|
||
pop eax
|
||
sti
|
||
ret
|
||
|
||
cd_status dd 0
|
||
|
||
;----------------------------------------------------------------
|
||
;
|
||
; fs_CdRead - LFN variant for reading CD disk
|
||
;
|
||
; esi points to filename /dir1/dir2/.../dirn/file,0
|
||
; ebx pointer to 64-bit number = first wanted byte, 0+
|
||
; may be ebx=0 - start from first byte
|
||
; ecx number of bytes to read, 0+
|
||
; edx mem location to return data
|
||
;
|
||
; ret ebx = bytes read or 0xffffffff file not found
|
||
; eax = 0 ok read or other = errormsg
|
||
;
|
||
;--------------------------------------------------------------
|
||
fs_CdRead:
|
||
push edi
|
||
cmp byte [esi], 0
|
||
jnz @f
|
||
.noaccess:
|
||
pop edi
|
||
.noaccess_2:
|
||
or ebx, -1
|
||
mov eax, ERROR_ACCESS_DENIED
|
||
ret
|
||
|
||
.noaccess_3:
|
||
pop eax edx ecx edi
|
||
jmp .noaccess_2
|
||
|
||
@@:
|
||
call cd_find_lfn
|
||
jnc .found
|
||
pop edi
|
||
cmp [DevErrorCode],0
|
||
jne .noaccess_2
|
||
or ebx, -1
|
||
mov eax, ERROR_FILE_NOT_FOUND
|
||
ret
|
||
|
||
.found:
|
||
mov edi,[cd_current_pointer_of_input]
|
||
test byte [edi+25],10b ; do not allow read directories
|
||
jnz .noaccess
|
||
test ebx, ebx
|
||
jz .l1
|
||
cmp dword [ebx+4], 0
|
||
jz @f
|
||
xor ebx, ebx
|
||
.reteof:
|
||
mov eax, 6 ; end of file
|
||
pop edi
|
||
ret
|
||
@@:
|
||
mov ebx, [ebx]
|
||
.l1:
|
||
push ecx edx
|
||
push 0
|
||
mov eax, [edi+10] ; ðåàëüíûé ðàçìåð ôàéëîâîé ñåêöèè
|
||
sub eax, ebx
|
||
jb .eof
|
||
cmp eax, ecx
|
||
jae @f
|
||
mov ecx, eax
|
||
mov byte [esp], 6
|
||
@@:
|
||
mov eax,[edi+2]
|
||
mov [CDSectorAddress],eax
|
||
; now eax=cluster, ebx=position, ecx=count, edx=buffer for data
|
||
.new_sector:
|
||
test ecx, ecx
|
||
jz .done
|
||
sub ebx, 2048
|
||
jae .new_sector
|
||
add ebx, 2048
|
||
jnz .incomplete_sector
|
||
cmp ecx, 2048
|
||
jb .incomplete_sector
|
||
; we may read and memmove complete sector
|
||
mov [CDDataBuf_pointer],edx
|
||
call ReadCDWRetr ; ÷èòàåì ñåêòîð ôàéëà
|
||
cmp [DevErrorCode],0
|
||
jne .noaccess_3
|
||
inc dword [CDSectorAddress]
|
||
add edx, 2048
|
||
sub ecx, 2048
|
||
jmp .new_sector
|
||
.incomplete_sector:
|
||
; we must read and memmove incomplete sector
|
||
mov [CDDataBuf_pointer],CDDataBuf
|
||
call ReadCDWRetr ; ÷èòàåì ñåêòîð ôàéëà
|
||
cmp [DevErrorCode],0
|
||
jne .noaccess_3
|
||
inc dword [CDSectorAddress]
|
||
mov eax,CDDataBuf
|
||
add eax, ebx
|
||
push ecx
|
||
add ecx, ebx
|
||
cmp ecx, 2048
|
||
jbe @f
|
||
mov ecx, 2048
|
||
@@:
|
||
sub ecx, ebx
|
||
push edi esi ecx
|
||
mov edi,edx
|
||
mov esi,eax ;0x7000 ; CD data buffer
|
||
cld
|
||
rep movsb
|
||
pop ecx esi edi
|
||
add edx, ecx
|
||
sub [esp], ecx
|
||
pop ecx
|
||
xor ebx, ebx
|
||
jmp .new_sector
|
||
|
||
.done:
|
||
mov ebx, edx
|
||
pop eax edx ecx edi
|
||
sub ebx, edx
|
||
ret
|
||
.eof:
|
||
mov ebx, edx
|
||
pop eax edx ecx
|
||
sub ebx, edx
|
||
jmp .reteof
|
||
|
||
;----------------------------------------------------------------
|
||
;
|
||
; fs_CdReadFolder - LFN variant for reading CD disk folder
|
||
;
|
||
; esi points to filename /dir1/dir2/.../dirn/file,0
|
||
; ebx pointer to structure 32-bit number = first wanted block, 0+
|
||
; & flags (bitfields)
|
||
; flags: bit 0: 0=ANSI names, 1=UNICODE names
|
||
; ecx number of blocks to read, 0+
|
||
; edx mem location to return data
|
||
;
|
||
; ret ebx = blocks read or 0xffffffff folder not found
|
||
; eax = 0 ok read or other = errormsg
|
||
;
|
||
;--------------------------------------------------------------
|
||
fs_CdReadFolder:
|
||
push edi
|
||
call cd_find_lfn
|
||
jnc .found
|
||
pop edi
|
||
cmp [DevErrorCode],0
|
||
jne .noaccess_1
|
||
or ebx, -1
|
||
mov eax, ERROR_FILE_NOT_FOUND
|
||
ret
|
||
.found:
|
||
mov edi,[cd_current_pointer_of_input]
|
||
test byte [edi+25],10b ; do not allow read directories
|
||
jnz .found_dir
|
||
pop edi
|
||
.noaccess_1:
|
||
or ebx, -1
|
||
mov eax, ERROR_ACCESS_DENIED
|
||
ret
|
||
.found_dir:
|
||
mov eax,[edi+2] ; eax=cluster
|
||
mov [CDSectorAddress],eax
|
||
mov eax,[edi+10] ; ðàçìåð äèðåêòðîðèè
|
||
.doit:
|
||
; init header
|
||
push eax ecx
|
||
mov edi, edx
|
||
mov ecx, 32/4
|
||
xor eax, eax
|
||
rep stosd
|
||
pop ecx eax
|
||
mov byte [edx], 1 ; version
|
||
mov [cd_mem_location],edx
|
||
add [cd_mem_location],32
|
||
; íà÷èíàåì ïåðåáðîñêó ÁÄÂÊ â ÓÑÂÊ
|
||
;.mainloop:
|
||
mov [cd_counter_block],dword 0
|
||
dec dword [CDSectorAddress]
|
||
push ecx
|
||
.read_to_buffer:
|
||
inc dword [CDSectorAddress]
|
||
mov [CDDataBuf_pointer],CDDataBuf
|
||
call ReadCDWRetr ; ÷èòàåì ñåêòîð äèðåêòîðèè
|
||
cmp [DevErrorCode],0
|
||
jne .noaccess_1
|
||
call .get_names_from_buffer
|
||
sub eax,2048
|
||
; äèðåêòîðèÿ çàêîí÷èëàñü?
|
||
cmp eax,0
|
||
jg .read_to_buffer
|
||
mov edi,[cd_counter_block]
|
||
mov [edx+8],edi
|
||
mov edi,[ebx]
|
||
sub [edx+4],edi
|
||
pop ecx edi
|
||
xor ebx,ebx
|
||
mov eax,ERROR_SUCCESS
|
||
ret
|
||
|
||
.get_names_from_buffer:
|
||
mov [cd_current_pointer_of_input_2],CDDataBuf
|
||
push eax esi edi edx
|
||
.get_names_from_buffer_1:
|
||
call cd_get_name
|
||
jc .end_buffer
|
||
inc dword [cd_counter_block]
|
||
mov eax,[cd_counter_block]
|
||
cmp [ebx],eax
|
||
jae .get_names_from_buffer_1
|
||
test ecx, ecx
|
||
jz .get_names_from_buffer_1
|
||
mov edi,[cd_counter_block]
|
||
mov [edx+4],edi
|
||
dec ecx
|
||
mov esi,ebp
|
||
mov edi,[cd_mem_location]
|
||
add edi,40
|
||
test dword [ebx+4], 1 ; 0=ANSI, 1=UNICODE
|
||
jnz .unicode
|
||
; jmp .unicode
|
||
.ansi:
|
||
cmp [cd_counter_block],2
|
||
jbe .ansi_parent_directory
|
||
cld
|
||
lodsw
|
||
xchg ah,al
|
||
call uni2ansi_char
|
||
cld
|
||
stosb
|
||
; ïðîâåðêà êîíöà ôàéëà
|
||
mov al,[esi+1]
|
||
cmp al,byte 3Bh ; ñåïàðàòîð êîíöà ôàéëà ';'
|
||
je .cd_get_parameters_of_file_1
|
||
; ïðîâåðêà äëÿ ôàéëîâ íå çàêàí÷èâàþùèõñÿ ñåïàðàòîðîì
|
||
movzx eax,byte [ebp-33]
|
||
add eax,ebp
|
||
sub eax,34
|
||
cmp esi,eax
|
||
je .cd_get_parameters_of_file_1
|
||
; ïðîâåðêà êîíöà ïàïêè
|
||
movzx eax,byte [ebp-1]
|
||
add eax,ebp
|
||
cmp esi,eax
|
||
jb .ansi
|
||
.cd_get_parameters_of_file_1:
|
||
mov [edi],byte 0
|
||
call .cd_get_parameters_of_file
|
||
add [cd_mem_location],304
|
||
jmp .get_names_from_buffer_1
|
||
|
||
.ansi_parent_directory:
|
||
cmp [cd_counter_block],2
|
||
je @f
|
||
mov [edi],byte '.'
|
||
inc edi
|
||
jmp .cd_get_parameters_of_file_1
|
||
@@:
|
||
mov [edi],word '..'
|
||
add edi,2
|
||
jmp .cd_get_parameters_of_file_1
|
||
|
||
.unicode:
|
||
cmp [cd_counter_block],2
|
||
jbe .unicode_parent_directory
|
||
cld
|
||
movsw
|
||
; ïðîâåðêà êîíöà ôàéëà
|
||
mov ax,[esi]
|
||
cmp ax,word 3B00h ; ñåïàðàòîð êîíöà ôàéëà ';'
|
||
je .cd_get_parameters_of_file_2
|
||
; ïðîâåðêà äëÿ ôàéëîâ íå çàêàí÷èâàþùèõñÿ ñåïàðàòîðîì
|
||
movzx eax,byte [ebp-33]
|
||
add eax,ebp
|
||
sub eax,34
|
||
cmp esi,eax
|
||
je .cd_get_parameters_of_file_2
|
||
; ïðîâåðêà êîíöà ïàïêè
|
||
movzx eax,byte [ebp-1]
|
||
add eax,ebp
|
||
cmp esi,eax
|
||
jb .unicode
|
||
.cd_get_parameters_of_file_2:
|
||
mov [edi],word 0
|
||
call .cd_get_parameters_of_file
|
||
add [cd_mem_location],560
|
||
jmp .get_names_from_buffer_1
|
||
|
||
.unicode_parent_directory:
|
||
cmp [cd_counter_block],2
|
||
je @f
|
||
mov [edi],word 2E00h ; '.'
|
||
add edi,2
|
||
jmp .cd_get_parameters_of_file_2
|
||
@@:
|
||
mov [edi],dword 2E002E00h ; '..'
|
||
add edi,4
|
||
jmp .cd_get_parameters_of_file_2
|
||
|
||
.cd_get_parameters_of_file:
|
||
mov edi,[cd_mem_location]
|
||
; ïîëó÷àåì àòðèáóòû ôàéëà
|
||
xor eax,eax
|
||
; ôàéë íå àðõèâèðîâàëñÿ
|
||
inc al
|
||
shl eax,1
|
||
; ýòî êàòàëîã?
|
||
test [ebp-8],byte 2
|
||
jz .file
|
||
inc al
|
||
.file:
|
||
; ìåòêà òîìà íå êàê â FAT, â ýòîì âèäå îòñóòñâóåò
|
||
; ôàéë íå ÿâëÿåòñÿ ñèñòåìíûì
|
||
shl eax,3
|
||
; ôàéë ÿâëÿåòñÿ ñêðûòûì? (àòðèáóò ñóùåñòâîâàíèå)
|
||
test [ebp-8],byte 1
|
||
jz .hidden
|
||
inc al
|
||
.hidden:
|
||
shl eax,1
|
||
; ôàéë âñåãäà òîëüêî äëÿ ÷òåíèÿ, òàê êàê ýòî CD
|
||
inc al
|
||
mov [edi],eax
|
||
; ïîëó÷àåì âðåìÿ äëÿ ôàéëà
|
||
;÷àñ
|
||
movzx eax,byte [ebp-12]
|
||
shl eax,8
|
||
;ìèíóòà
|
||
mov al,[ebp-11]
|
||
shl eax,8
|
||
;ñåêóíäà
|
||
mov al,[ebp-10]
|
||
;âðåìÿ ñîçäàíèÿ ôàéëà
|
||
mov [edi+8],eax
|
||
;âðåìÿ ïîñëåäíåãî äîñòóïà
|
||
mov [edi+16],eax
|
||
;âðåìÿ ïîñëåäíåé çàïèñè
|
||
mov [edi+24],eax
|
||
; ïîëó÷àåì äàòó äëÿ ôàéëà
|
||
;ãîä
|
||
movzx eax,byte [ebp-15]
|
||
add eax,1900
|
||
shl eax,8
|
||
;ìåñÿö
|
||
mov al,[ebp-14]
|
||
shl eax,8
|
||
;äåíü
|
||
mov al,[ebp-13]
|
||
;äàòà ñîçäàíèÿ ôàéëà
|
||
mov [edi+12],eax
|
||
;âðåìÿ ïîñëåäíåãî äîñòóïà
|
||
mov [edi+20],eax
|
||
;âðåìÿ ïîñëåäíåé çàïèñè
|
||
mov [edi+28],eax
|
||
; ïîëó÷àåì òèï äàííûõ èìåíè
|
||
xor eax,eax
|
||
test dword [ebx+4], 1 ; 0=ANSI, 1=UNICODE
|
||
jnz .unicode_1
|
||
mov [edi+4],eax
|
||
jmp @f
|
||
.unicode_1:
|
||
inc eax
|
||
mov [edi+4],eax
|
||
@@:
|
||
; ïîëó÷àåì ðàçìåð ôàéëà â áàéòàõ
|
||
xor eax,eax
|
||
mov [edi+32+4],eax
|
||
mov eax,[ebp-23]
|
||
mov [edi+32],eax
|
||
ret
|
||
|
||
.end_buffer:
|
||
pop edx edi esi eax
|
||
ret
|
||
|
||
cd_find_lfn:
|
||
; in: esi->name
|
||
; out: CF=1 - file not found
|
||
; else CF=0 and [cd_current_pointer_of_input] direntry
|
||
push eax esi
|
||
; 16 ñåêòîð íà÷àëî íàáîðà äåñêðèïòîðîâ òîìîâ
|
||
mov [CDSectorAddress],dword 15
|
||
.start:
|
||
inc dword [CDSectorAddress]
|
||
mov [CDDataBuf_pointer],CDDataBuf
|
||
call ReadCDWRetr
|
||
cmp [DevErrorCode],0
|
||
jne .access_denied
|
||
; ïðîâåðêà íà âøèâîñòü
|
||
cmp [CDDataBuf+1],dword 'CD00'
|
||
jne .access_denied
|
||
cmp [CDDataBuf+5],byte '1'
|
||
jne .access_denied
|
||
; ñåêòîð ÿâëÿåòñÿ òåðìèíàòîðîì íàáîð äåñêðèïòîðîâ òîìîâ?
|
||
cmp [CDDataBuf],byte 0xff
|
||
je .access_denied
|
||
; ñåêòîð ÿâëÿåòñÿ äîïîëíèòåëüíûì è óëó÷øåííûì äåñêðèïòîðîì òîìà?
|
||
cmp [CDDataBuf],byte 0x2
|
||
jne .start
|
||
; ñåêòîð ÿâëÿåòñÿ äîïîëíèòåëüíûì äåñêðèïòîðîì òîìà?
|
||
cmp [CDDataBuf+6],byte 0x1
|
||
jne .start
|
||
; ïàðàìåòðû root äèðåêòðîðèè
|
||
mov eax,[CDDataBuf+0x9c+2] ; íà÷àëî root äèðåêòðîðèè
|
||
mov [CDSectorAddress],eax
|
||
mov eax,[CDDataBuf+0x9c+10] ; ðàçìåð root äèðåêòðîðèè
|
||
cmp byte [esi], 0
|
||
jnz @f
|
||
mov [cd_current_pointer_of_input],CDDataBuf+0x9c
|
||
jmp .done
|
||
@@:
|
||
; íà÷èíàåì ïîèñê
|
||
.mainloop:
|
||
dec dword [CDSectorAddress]
|
||
.read_to_buffer:
|
||
inc dword [CDSectorAddress]
|
||
mov [CDDataBuf_pointer],CDDataBuf
|
||
call ReadCDWRetr ; ÷èòàåì ñåêòîð äèðåêòîðèè
|
||
cmp [DevErrorCode],0
|
||
jne .access_denied
|
||
call cd_find_name_in_buffer
|
||
jnc .found
|
||
sub eax,2048
|
||
; äèðåêòîðèÿ çàêîí÷èëàñü?
|
||
cmp eax,0
|
||
jg .read_to_buffer
|
||
; íåò èñêîìîãî ýëåìåíòà öåïî÷êè
|
||
.access_denied:
|
||
pop esi eax
|
||
stc
|
||
ret
|
||
; èñêîìûé ýëåìåíò öåïî÷êè íàéäåí
|
||
.found:
|
||
; êîíåö ïóòè ôàéëà
|
||
cmp byte [esi], 0
|
||
jz .done
|
||
mov eax,[cd_current_pointer_of_input]
|
||
add eax,2
|
||
mov eax,[eax]
|
||
mov [CDSectorAddress],eax ; íà÷àëî äèðåêòîðèè
|
||
add eax,8
|
||
mov eax,[eax] ; ðàçìåð äèðåêòîðèè
|
||
jmp .mainloop
|
||
; óêàçàòåëü ôàéëà íàéäåí
|
||
.done:
|
||
pop esi eax
|
||
clc
|
||
ret
|
||
|
||
cd_find_name_in_buffer:
|
||
mov [cd_current_pointer_of_input_2],CDDataBuf
|
||
.start:
|
||
call cd_get_name
|
||
jc .not_found
|
||
call cd_compare_name
|
||
jc .start
|
||
.found:
|
||
clc
|
||
ret
|
||
.not_found:
|
||
stc
|
||
ret
|
||
|
||
cd_get_name:
|
||
push eax
|
||
mov ebp,[cd_current_pointer_of_input_2]
|
||
mov [cd_current_pointer_of_input],ebp
|
||
mov eax,[ebp]
|
||
cmp eax,0 ; âõîäû çàêîí÷èëèñü?
|
||
je .next_sector
|
||
cmp ebp,CDDataBuf+2048 ; áóôåð çàêîí÷èëñÿ?
|
||
jae .next_sector
|
||
movzx eax, byte [ebp]
|
||
add [cd_current_pointer_of_input_2],eax ; ñëåäóþùèé âõîä êàòàëîãà
|
||
add ebp,33 ; óêàçàòåëü óñòàíîâëåí íà íà÷àëî èìåíè
|
||
pop eax
|
||
clc
|
||
ret
|
||
.next_sector:
|
||
pop eax
|
||
stc
|
||
ret
|
||
|
||
cd_compare_name:
|
||
; compares ASCIIZ-names, case-insensitive (cp866 encoding)
|
||
; in: esi->name, ebp->name
|
||
; out: if names match: ZF=1 and esi->next component of name
|
||
; else: ZF=0, esi is not changed
|
||
; destroys eax
|
||
push esi eax edi
|
||
mov edi,ebp
|
||
.loop:
|
||
cld
|
||
lodsb
|
||
push ax
|
||
call char_toupper
|
||
call ansi2uni_char
|
||
xchg ah,al
|
||
cld
|
||
scasw
|
||
pop ax
|
||
je .coincides
|
||
call char_todown
|
||
call ansi2uni_char
|
||
xchg ah,al
|
||
cld
|
||
sub edi,2
|
||
scasw
|
||
jne .name_not_coincide
|
||
.coincides:
|
||
cmp [esi],byte '/' ; ðàçäåëèòåëü ïóòè, êîíåö èìåíè òåêóùåãî ýëåìåíòà
|
||
je .done
|
||
cmp [esi],byte 0 ; ðàçäåëèòåëü ïóòè, êîíåö èìåíè òåêóùåãî ýëåìåíòà
|
||
je .done
|
||
jmp .loop
|
||
.name_not_coincide:
|
||
pop edi eax esi
|
||
stc
|
||
ret
|
||
.done:
|
||
; ïðîâåðêà êîíöà ôàéëà
|
||
cmp [edi],word 3B00h ; ñåïàðàòîð êîíöà ôàéëà ';'
|
||
je .done_1
|
||
; ïðîâåðêà äëÿ ôàéëîâ íå çàêàí÷èâàþùèõñÿ ñåïàðàòîðîì
|
||
movzx eax,byte [ebp-33]
|
||
add eax,ebp
|
||
sub eax,34
|
||
cmp edi,eax
|
||
je .done_1
|
||
; ïðîâåðêà êîíöà ïàïêè
|
||
movzx eax,byte [ebp-1]
|
||
add eax,ebp
|
||
cmp edi,eax
|
||
jne .name_not_coincide
|
||
.done_1:
|
||
pop edi eax
|
||
add esp,4
|
||
inc esi
|
||
clc
|
||
ret
|
||
|
||
char_todown:
|
||
; convert character to uppercase, using cp866 encoding
|
||
; in: al=symbol
|
||
; out: al=converted symbol
|
||
cmp al, 'A'
|
||
jb .ret
|
||
cmp al, 'Z'
|
||
jbe .az
|
||
cmp al, '€'
|
||
jb .ret
|
||
cmp al, '<27>'
|
||
jb .rus1
|
||
cmp al, 'Ÿ'
|
||
ja .ret
|
||
; 0x90-0x9F -> 0xE0-0xEF
|
||
add al, 'à'-'<27>'
|
||
.ret:
|
||
ret
|
||
.rus1:
|
||
; 0x80-0x8F -> 0xA0-0xAF
|
||
.az:
|
||
add al, 0x20
|
||
ret
|
||
|
||
uni2ansi_char:
|
||
; convert UNICODE character in al to ANSI character in ax, using cp866 encoding
|
||
; in: ax=UNICODE character
|
||
; out: al=converted ANSI character
|
||
cmp ax, 0x80
|
||
jb .ascii
|
||
cmp ax, 0x401
|
||
jz .yo1
|
||
cmp ax, 0x451
|
||
jz .yo2
|
||
cmp ax, 0x410
|
||
jb .unk
|
||
cmp ax, 0x440
|
||
jb .rus1
|
||
cmp ax, 0x450
|
||
jb .rus2
|
||
.unk:
|
||
mov al, '_'
|
||
jmp .doit
|
||
.yo1:
|
||
mov al, 'ð'
|
||
jmp .doit
|
||
.yo2:
|
||
mov al, 'ñ'
|
||
jmp .doit
|
||
.rus1:
|
||
; 0x410-0x43F -> 0x80-0xAF
|
||
add al, 0x70
|
||
jmp .doit
|
||
.rus2:
|
||
; 0x440-0x44F -> 0xE0-0xEF
|
||
add al, 0xA0
|
||
.ascii:
|
||
.doit:
|
||
ret
|