Source file: /~heha/hsn/dos/doslfn/doslfn_n.zip/DOSLFN.ASM

;Lange Dateinamen unter nacktem DOS
;Noch zu tun:
;"verlängerte" Sektoren und Unterstützung von Sektorlänge 128..4096 Bytes
;Umbenennen gleiche Datei
;korrektes Löschen mit Wildcards
;HeapWalker

;N: Most Protected Mode DOS extenders doesn't translate the LFN API to
;   Real Mode. (When running Windows9x, Windows makes that task, effectively
;   disabling the DOS extender.)
;   For DPMI programs, it is up to the programmer to translate these DOS
;   calls to Real Mode, using DPMI services at INT31. Best solution would
;   be a DLL that makes this task at load time, hooking the protected mode
;   INT21 chain.
;   When running Windows3 in Enhanced Mode,
;   DOSLFN auto-loads an LFNXLAT.386 VxD that translates these APIs.
;   With this VxD, there is no need for translation for Windows programs.
;   Unfortunately, Win32s programs cannot see long file names,
;   unless someone publish a great patch of one Win32s DLL.
;   The rarely used Standard Mode should be configured to load the DLL
;   (above mentioned) at startup - but DOSLFN requires a 386 itself.
	%NOLIST
	include	prolog.asm
public COMentry		;damit's mit SoftICE klappt
	P386
	JUMPS
REQfunc		equ	7146h		;in AX
REQcode		equ	9877h		;in DX
ANScode		equ	8766h		;in AX; DX=Segmentadresse
DEFHEAPSIZE	=	1000	;für einige FindInfos
DEFHEAPSIZE_CD	=	2000	;dazu eine durchschnittliche .JLT-Datei
FDChangeTime	=	3*18 ;Neue DPB einlesen nach 3 Sekunden Inaktivität
CDChangeTime	=	7*18 ;dauert naturgemäß viel länger,
	;auch das Einlesen der ersten CD-Sektoren und der Link-Tabelle
macro INT3
ifdef DEBUG
	INT 3
endif
endm

;********************
;** DOS structures **
;********************

struc tExDPB		;structure returned by INT21/AH=32, valid: DOS 4.0+
 Drive  db	?	;{0=A}
 UnitNo db	?	;number of device driver unit
 SecLen dw	?	;{Bytes pro Sektor}
 HiSec  db	?	;{Anzahl Sektoren pro Cluster -1, 2**n-1}
 Shift  db	?	;{Verschiebung n}
 ResSec dw	?	;{Reservierte Sektoren am Anfang des Laufwerks}
 FATs	db	?	;{Anzahl der FATs}
 RootEn dw	?	;{Anzahl Wurzelverzeichniseinträge}
 UsrSec dw	?	;{1. Sektor mit Userdaten}
 HiClus dw	?	;{Anzahl Cluster -1}
 SecFAT dw	?	;{Sektoren pro FAT}
 SecDir dw	?	;{Sektornummer 1.Dir}
 unused db	5 dup (?) ;Luft zum FAT32-DPB, der beginnt ab Ofs.24
			;from here starts extended trail from INT21/AH=73
 dpb_flags		db	?
 next_dpb		dd	?	;Pointer
 start_search_cluster	dw	?
 free_clusters		dd	?
 mirroring		dw	?
 file_system_info_sector dw	?
 backup_boot_sector	dw	?
 first_sector		dd	?
 max_cluster		dd	?
 sectors_per_fat	dd	?
 root_cluster		dd	?
 free_space_cluster	dd	?
 filler			db	?	;"gerade" Strukturlänge!
ends

struc tRWRec		;DOS structure for reading and writing sectors
 sect	dd	?	;sector number, allowing partitions up to 2 Terabyte
 numb	dw	?	;sector count
 addr	dd	?	;FAR address of sector data
ends

struc TSearchRec	;DOS legacy search record, returned at INT21/AH=4E&4F
;N: All undocumented fields are not used to maintain compatibility with
;   non-MS clones of DOS or some TSRs
 Drive	db	?	;bit7 set at network drives and FAT32, undocumented
 SName	db 8 dup (?)
 SExt	db 3 dup (?)
 SAttr	db	?
 DirNo	dw	?
 union
  struc
   Clus	dw	?
   Res	dd	?
  ends
  struc
   Clus32 dd	?	;on FAT32
   DirNoHi dw	?	;eventually high part of DirNo?
  ends
 ends
 Attr	db	?	;here the documented part begins
 Time	dd	?
 fsize	dd	?
 FName	db 13 dup (?)	;in DOS form, with dot if extension follows
ends
;N: Length "undocumented" = 21 bytes, "documented" = 22 bytes

struc TW32FindData	;DOS new and Win32 search record, at INT21/AX=714E&4F
;N: This is the same structure as WIN32_FIND_DATA, but file names will return
;   in OEM character set rather than ANSI
 attr	dd	?	;bits 0..6: like DOS, bit 8: temporary (not used)
 timec	dq	?	;file creation time	FAT stepping: 10 ms
 timea	dq	?	;last access time	FAT stepping:  1 day
 timem	dq	?	;last modification time	FAT stepping:  2 s
			;On CDFS, all times have 1 s stepping
 sizeh	dd	?	;(not used) high part of file length (in wrong order)
 sizel	dd	?	;file length (FAT, CDFS: up to 2GB)
 res	dq	?	;maybe compressed file length? inode number? security?
 lname	db 260 dup (?)	;long name (LFN), alone if short
 sname	db 13 dup (?)	;short name (SFN), also called "alias"
ends

struc tErrInfo		;for Set_Error_Info INT21/AX=5D0A DS:DX=pointer
 err_AX	dw	?
 err_BX	dw	?
 err_CX	dw	?
 err_DX	dw	?
 err_SI	dw	?
 err_DI	dw	?
 err_DS	dw	?
 err_ES	dw	?
 res	dw	?
 cid	dw	?	;computer ID (0 for localhost)
 pid	dw	?	;process ID (same as PSP)
ends

struc tWinStart		;Format of Windows (W3) Startup Information Structure
 ver_major db	?	;major version of info structure (3)
 ver_minor db	?	;minor version of info structure (0)
 next	   dd	?	;pointer to next tWinStart structure or 0000h:0000h
 vxd_fname dd	?	;pointer to ASCIZ name of virtual device file or zero
 vxd_rdata dd	?	;virtual device reference data
 idata	   dd	?	;pointer to instance data records
ends

;*************************
;** FAT/CDFS structures **
;*************************

struc tDirEnt	;legacy directory entry on FAT media (FAT12, FAT16, FAT32)}
		;source: Ralf Brown Interrupt List, SIZE=32
;N: First character (byte) has special meaning:
;   00: no more following DirEnts (a new cluster will be filled with zeroes)
;   05: first character is E5
;   E5: deleted entry, undeleted DirEnts may follow
 FName 	db	11 dup (?)	;FCB form without dot, upper-case letters
 Attr   db	?
 resv   db	?	;Win95: Null (maybe high part of file size)
 timeC10ms db	?	;Win95: 10ms additional value to timeC [0..199]
 timeC	dd	?	;Win95: creation time (all local time)
 timeA	dw	?	;Win95: last access date
 ClusH  dw	?	;FAT32: high part of first cluster
 timeM  dd	?	;modification time
 ClusL	dw	?	;first cluster, FAT32: low part of first cluster
 fsize	dd	?	;file size
ends

struc TLfnDirEnt	;FAT LFN directory entry structure, SIZE=32
;N: The NameX protions of this structure are not directly used;
;   they are addressed by "LODSW" and the gaps skipped by "ADD SI,space"
 count	db	?	;Bit 0..5: number (1+), Bit 6=last entry
 Name1	dw 5 dup (?)	;five Unicode name characters
 Attr	dw	?	;always 000Fh
 check	db	?	;SFN checksum link
 Name2	dw 6 dup (?)	;six Unicode name characters
 Clus1	dw	?	;always zero
 Name3	dw 2 dup (?)	;two Unicode name characters
ends

struc TCD_DirEnt	;CD (ISO and Joliet) directory entry structure
;N: A bunch of entries are ignored, SIZE>=34
 r	db	?	;the number of bytes in this record
 ea	db	?	;number of sectors in extended attribute record, zero
 sect	dd	?	;Logical Block Number of file start
 sectm	dd	?	;same for Motorola
 fsize	dd	?	;file or directory length in bytes, always divisable
			;by sector size (2048) for directory length
 fsizem	dd	?	;same for Motorola
 year	db	?	;years past 1900 (all local time)
 month	db	?	;1 = January
 day	db	?	;1 = 1.
 hour	db	?	;0..23
 minu	db	?	;0..59
 seco	db	?	;0..59, mostly an even number(?)
 tz	db	?	;time zone of local time, signed, 15min, +east, -west
 flags	db	?	;attributes, Bit0=hidden, Bit1=Dir, Bit7=more DirEnts
			;(Bit 7 is not correct on most CDs)
 isiz	db	?	;for interleaving, unused
 igap	db	?	;for interleaving, unused
 vsn	dw	?	;volume sequence number, unused
 vsnm	dw	?
 fnamelen db	?	;file name length in Bytes (always even if Unicode,
			; except for "." and ".." entries)
 fname	db	?	;file name (Motorola byte order if Unicode)
ends

;********************************
;** DOSLFN internal structures **
;********************************
;N: These structures are used inside DOSLFNs heap

;== part 1: DriveInfo data ==
IF 0
;bits for following <dtype>
DTM_mask	=7	;lower three bits are for general mode switch
DTM_FB		=0	;no bit set if FallBack
DTM_FAT		=1	;001 if FAT mode
DTM_ISO		=2	;010 if ISO mode
DTM_JOL		=3	;011 if Joliet mode, other codes are for future:
DTM_PT		=4	;100 PipeThrough (For this drive, there is an
			;    underlying LFN API, e.g. NTFSREAD)
;DTM_X		=5	;101 extfs2 (Linux) via LTOOLS
;DTM_UDF	=6	;110 UDF (Universal Disc Format, DVD)
;DTM_LFNBK	=7	;111 LFNBK file based LFN support - or some network
DT_Joliet	=1	;if not, take LFNs from ISO (these may also be long)
DT_CDFS		=2	;bit 1 acts as CDFS switch too
DT_BigDos	=8	;use extended INT25/26 interface
DT_FAT16	=16	;FAT quantity, otherwise, it's FAT12
DT_FAT32	=32	;don't use INT25/26, use INT21/AH=73 instead
;DT_SmartOS	=64	;set if OS already remove LFNs (MS-DOS7+) - no dtype!
DT_Locked	=128	;set if drive was locked (for writing, MS-DOS7+)
;N: Depending on DTM_mask'ed value if <dtype>, DOSLFN works in nearly
;   complete different modi
;   * FAT mode for diskettes and hard drives (even a ZIP drive uses FAT)
;   * CD mode (rarely used) for ISO CDs with "long" ISO file names
;   * Joliet mode, typical for Windows9x CDs
;   * fall-back mode for all other drives
;     This mode does not only map LFN_FindFirst/Next to its
;     legacy companion, it also makes the extended pattern matching.
;     Equally, LFN_Delete may delete multiple files using wildcards
;     calling "legacy delete" multiple times.
;     From the view of a programmer, it is very nice to have an LFN API
;     on _all_ drives, and there is no need to make ugly cases.
;     Note that also Win9x does wrap e.g. DOS network drives (Novell Lite)
;     with its LFN API - therefore, FallBack is a "must" for compatibility.
;     However, it conflicts with actual versions of NTFSREAD.
;   Another useful mode would be an LFNBK mode, simply using a file
;   for keeping long file names - this would instantly work on all drives.

struc TDI_FAT
 shift   db	?	;shift count for sector->cluster
 shift2  db	?	;shift count for byte->sector (always 9)
 fatsec	 dd	?	;first sector of active FAT
 dirsec  dd	?	;sector of root directory
 usrsec  dd	?	;sector of first cluster (ie cluster # 2)
 lastsec dd	?	;last valid sector of user data
ends
ENDIF

struc TDI_ISO
 voldesc dd	?	;volume descriptor
 rootdir dd	?	;root directory sector
 rootlen dw	?	;count of root directory sectors
ends

struc TDI_JOL
 isotree TDI_ISO <?>	;same as above
 joltree TDI_ISO <?>	;same as above for the Joliet tree
 linktbl dw	?	;pointer to link table (keep trees together)
ends

IF 0
struc TDI		;linked structure for all media ("Drive Info")
 next	dw	?	;NEXT pointer into LocalHeap, zero if last
 prev	dw	?
 time	dw	?	;for discarding after a while (removable media only)
 drive	db	?	;drive number (0=A:)
 dtype	db	?	;control flags (DT_xxx constants)
 secsiz	dw	?	;sector size (DOSLFN is able to process any size?)
 union
  fat TDI_FAT	<>	;additional data for FAT media
  iso TDI_ISO	<>	;additional data for ISO media
  jol TDI_JOL	<>	;additional data for Joliet media
 ends
ends
;N: drive information is kept in a cache of 2..4 entries. Otherwise,
;   copy actions (between different drives) would often retrieve the
;   drive info, and clumsy-DOS seems to always read in boot area
ENDIF

;== part 2: SectorCache data ==
IF 0
struc TSC		;linked "sector cache" entry; sector data follows!
 next	dw	?	;NEXT pointer into LocalHeap, zero if last
 prev	dw	?
 secnum	dd	?	;sector number (of first sector)
 count	db	?	;sector count (always 1)
 drive	db	?	;drive number (0=A:)
 dirty	dw	?	;16 "dirty" (="modified") bits for up to 16 sectors
ends
;N: sector data is held in cache because MS-DOS doesn't cache anything,
;   even when SmartDrv is loaded, when direct disc access is detected.
;   Especially CD access is too slow without internal cacheing of at least
;   two sectors. The LocalHeap is used up for cacheing until no more
;   memory is available. All other memory allocations free least-recently
;   used SectorCache entries to get necessary space.
ENDIF

;== part 3: FindHandle data ==

struc TFI_FAT		;size=6
 entry dw	?	;offset from start of sector to current DirEnt
 sect  dd	?	;sector number of current DirEnt
ends

struc TFI_ISO		;size=8
 entry dw	?	;offset from start of sector to current DirEnt
 sect  dd	?	;sector number of current DirEnt
 count dw	?	;number of sectors to end of directory
ends

struc TFI_JOL		;size=22
 i	TFI_ISO	<>	;same as above
 j	TFI_ISO	<>	;same for Joliet tree
 restart dd	?	;search-restart SFN sector
 restlen dw	?	;search-restart SFN directory length
ends
;N: For Joliet, many additional fields are necessary to make a round-robin
;   SFN match possible. Searching is primary done with LFN, and then
;   the next matching SFN will be taken. If no such SFN is found, searching
;   restarts at start of directory (using <restart> and <restlen> fields)
;   until found. If there is no match too, simply the following entry
;   is used. A match is when two DirEnts have the same start sectors
;   (if file) or matching start sectors (via LinkTable, if directory).
;   This complicated processing is necessary because entry order of LFN
;   and SFN entries are not always the same.
;   Limitation of <count> to a word seems to be not so bad; if some dude
;   reach it, he has more than 500 000 files in one directory

struc TFI_FB		;size=21
 dta	db 21 dup (?)	;space for undocumented DTA part
ends

struc TFI		;interal representation of FindHandle ("FindInfo")
 magic	db	?	;MagicByte (to detect invalid handles)
 drive	db	?	;drive number (0=A:)
 attr	dw	?	;Search (LowByte) and MustMatch (HighByte) attributes
 union
  fb  TFI_FB	<>	;additional for FallBack
  fat TFI_FAT	<>	;additional for FAT
  iso TFI_ISO	<>	;additional for ISO
  jol TFI_JOL	<>	;additional for Joliet
 ends
 fflags	db	?	;file flags (only DotAtEnd flag is used)
;N: fflags cannot be adressed easily. Structure fill and read-out
;   is intended using LODSx and STOSx, respective.
;N: The file match pattern follows, but if it had a dot (.) at end,
;   these dot is stripped from pattern and DotAtEnd flag is set
;   due to special meaning of such a pattern
;N: For FallBack, also the file match pattern follows, but is only
;   necessary if File_Flag_Is_LFN or _Char_High bits are in fflags
ends

IF 1
struc tFindInfo		;als interne Repräsentation von FindHandle
 usage	db	?	;MagicByte
 drive	db	?	;Laufwerk
 attr	dw	?	;Attribute
 entry	dw	?	;Eintrags-Nr. (DirEnt-Zeiger)
 sect	dd	?	;Sektor-Nummer
 cd_entry dw	?	;Joliet-DirEnt-Zeiger
 cd_sect dd	?	;Joliet-Sektor-Nummer
 fflags	db	?	;File_Flags (nur DotAtEnd wesentlich?)
			;anschließend Maske
ends

struc tFB_FindInfo
 usage	db	?	;ein anderes MagicByte
 undoc	db	21 dup (?)	;"undokumentierter" DTA-Bereich
 mmattr	db	?	;Attribut (CX beim Aufruf)
 fflags	db	?	;(nicht mehr wesentlich: File_Flag_DotAtEnd)
			;anschließend: Maske
			;(nur nötig wenn File_Flag_Is_LFN oder Char_High)
ends
MAGIC_hFind	=0ACh	;Magic-Byte für gültiges Find-Handle
MAGIC_FB_hFind	=0ADh	;dito für Rückfallmodus

ENDIF

;== part 4: FastOpen data ==
IF 0
struc TFO		;linked structure for FastOpen
 next	dw	?
 prev	dw	?
 drive	db	?	;0=A:
ends
;N: FastOpen is absolutely necessary for reasonable speed.
;   For multi-drive and copy-on-single-drive support, FastOpen
;   is now organized as a cache with multiple entries.
ENDIF
;== done with the 4 parts ==

;N: All caches are organized as linked lists with most-recently used
;   entries on head and least-recently used entries at tail of the lists.
;   A cache hit moves the matched entry onto queue's head and updates
;   the time stamp. Otherwise, a new structure is put onto head, and
;   trailing entries may discarded to maintain maximum entry count
;   (for drive info and FastOpen) or to get room (for sector cache).
;   Because moving-to-head is done at access, and DOSLFN must not be
;   reentered, static list pointers are also work area pointers.

;bits for main function control byte <ctrl0>
CTRL_Main	=bit 7	;main switch whether DOSLFN is active or turned off
CTRL_Write	=bit 6	;allow write access (except for DELETE/UNLINK/MOVE)
CTRL_Tilde	=bit 5	;tilde usage (HKLM/.../NameNumericTail)
CTRL_Tunnel	=bit 4	;tunnel effect
CTRL_CDROM	=bit 3	;CDROM support (default is ON if MSCDEX was loaded)
CTRL_InDOS	=bit 2	;InDOS flag usage
CTRL_RoBit	=bit 1	;write-protect attribute for CDROM files (like NORO)

;**********************************
;** static variables in PSP area **
;**********************************
;N: "Program Segment Prefix" area is used to reduce memory consumption
;   of DOSLFN. Area before 5Ch is apparently used by some DOS extenders.
;   Because DOS is not reentrant, LFNDOS doesn't need to be reentrant,
;   therefore, these handy variables and buffers are allowed and usable.

		org	5Ch
argv0		dw	?	;points to heap
argv0file	dw	?	;points behind last backslash, 13 bytes space
;60h
LocalHeap	dw	?	;zeigt auf Anfang des Heaps
TrailMinLen	dw	?	;TrailByte: Minimum und Anzahl
DriverChain	dd	?	;Zeiger ins DOS auf Treiber-Kette
lead_byte_table	dd	?	;Zeiger ins DOS für Führungsbyte-Bereiche
uppercase_table	dd	?	;Zeiger ins DOS für Zeichen >=80h
		;Offset ist um 7Eh vermindert für direktes XLAT
;70h
CurSector	dd	?	;current "working" sector
longpos_s	dd	?	;internal, set by Locate_DirEnt (FAT)
longpos_a	dw	?	; -"-
CurPathComp	dw	?	;Momentan "aktive" Pfad-Komponente oder Maske
DPB_Shift	db	?	;{Verschiebung für Sektoren pro Cluster}
DPB_Drive	db	?	;{Drive Parameter Block: Laufwerk, 0=A}
DriveType	db	?	;{Schalter für Festplatten-Zugriffsart}
DT_BigDos	=bit 0		;erweiterte INT25/26-Schnittstelle
DT_DrvPar	=bit 1		;Wir haben Laufwerks-Info
DT_FAT32	=bit 2		;größere Änderungen (kein INT25/26)
DT_FAT16	=bit 3		;wegen FAT-Zugriff
DT_CDFS		=bit 4		;genauer: Joliet
DT_SmartOS	=bit 5		;wenn das Betriebssystem den LFN löscht...
DT_Locked	=bit 6		;wenn geschrieben wurde (MS-DOS7+)
DT_Dirty	=bit 7		;Sektor muss geschrieben werden!
;In Abhängigkeit von DriveType arbeitet das Programm genaugenommen
;in 3 Modi (mehr sind nicht allgemein machbar):
;* FAT-Modus für Disketten und Festplatten
;* Joliet für Windows9x-typische CDs
;* Rückfallmodus für alles andere: LFN_FindFirst u.a. wird mittels "normaler"
;  Funktionen nachgebildet (wie es sonst jedes Programm zu Fuß tun müßte,
;  und das ist beim Programmieren nervenaufreibend, verflixte DTAs und
;  das erweiterte Globbing)
;Sehr nett wäre ja noch ein "Netzwerk-Modus", bspw. per NFS oder DOS-Samba,
;aber wer liefert mir den Quelltext?
;Weiterhin ein "lfnbk"-Modus, mit Abbildung der langen Dateinamen über
;eine reguläre Hilfsdatei
LFN_DirEnts	db	?	;Anzahl DirEnts beim Schreiben des LFN

;This command-line area must not contain variables that initialize at startup
;80h - following 32 bytes are used (at least) three times!
ErrInfo		TErrInfo	<>	;overloads following
		org	80h
FCB_Name	db  11 dup (?)	;used for building unique file name
				;Never contains 05h as first byte
ShortName	db  13 dup (?)	;filled by (CD_)Locate_DirEnt
;here we have 8 bytes room - temporary needed for reading ISO file names
		org	80h
DirEnt_Copy	TDirEnt	<>	;32 bytes space for moving FAT DirEnt while
				;inserting LFN, may have 05h as first byte
;A0h
DPB_FAT1Sec	dd	?	;FAT: Startsektor 1. FAT
DPB_DirSec	dd	?	;{Sektor Hauptverzeichnis (nur FAT)}
DPB_UsrSec	dd	?	;{Sektor des 1. Clusters (mit Nr. 2)}
DPB_LastSec	dd	?	;{Letzter Sektor der Partition}
;B0h
shortbuffer	db	3+64+13 dup (?)	;Puffer für kurze 8.3-Dateinamen
;100h - three bytes for JMP are free for use
LastAccessTime	dw	?
subst_drive	db	?	;Zeichen, geSUBSTetes (virtuelles) Laufwerk

;Program Control Flag Byte Assignment:
PF_Fail_Uni2Oem	=bit 0		;Nicht zu OEM konvertierbarer Unicode-Name
PF_Follow	=bit 1		;Sektor-Verfolgung nach DirScan
PF_LFN_Input	=bit 2		;LongName-API Namensinterpretation
PF_Slash	=bit 3		;Entfernter Slash bei Gen_Alias
PF_Tunnel_Save	=bit 4		;save LFN before calling OldInt21
PF_Tunnel_Restore=bit 5		;restore LFN after calling OldInt21

;*****************************
;** Stack Frame Assignments **
;*****************************
;N: Due to complex data processing inside DOSLFN, it's not worth
;   to keep register data. Registers are pushed onto stack, and with setting
;   BP, these registers are easily available via stack frame, ie
;   BP and a small offset value, as defined below.
;   This technique is known from Windows VxD programming.
;N: Some bit registers are kept onto stack too, their bit-access consumes
;   one byte less op-codes than bit-access to a static variable.

File_Flags	equ	byte bp-6	;properties of last path component
FuncNum		equ	byte bp-5	;{properties of drive used}
Client_E_DX	equ	word bp-4	;high part of EDX (unreferenced)
Client_E_AX	equ	word bp-2	;high part of EAX (unreferenced)
Client_DI	equ	word bp		;frame of "pusha" op-code
Client_SI	equ	word bp+2
Client_BP	equ	word bp+4	;(unreferenced)
;Client_SP	equ	word bp+6	;free space for us - never popped
PFlags		equ	byte bp+6	;input control flags
ctrl		equ	byte bp+7	;program control flags
Client_BX	equ	word bp+8
Client_DX	equ	word bp+0Ah
Client_CX	equ	word bp+0Ch
Client_AX	equ	word bp+0Eh
Client_ES	equ	word bp+10h
Client_DS	equ	word bp+12h
Client_FS	equ	word bp+14h	;(unreferenced)
Client_IP	equ	word bp+16h	;(unreferenced)
Client_CS	equ	word bp+18h	;(unreferenced)
Client_Flags	equ	word bp+1Ah

Client_DXBX	equ	dword bp+8
Client_CXDX	equ	dword bp+0Ah	;CX=High, DX=Low, in einem Schlag
Client_AXCX	equ	dword bp+0Ch

Client_AL	equ	byte LOW  Client_AX
Client_AH	equ	byte HIGH Client_AX
Client_BL	equ	byte LOW  Client_BX
Client_BH	equ	byte HIGH Client_BX
Client_CL	equ	byte LOW  Client_CX
Client_CH	equ	byte HIGH Client_CX
Client_DL	equ	byte LOW  Client_DX
Client_DH	equ	byte HIGH Client_DX

;*******************************
;** Start of program and ISRs **
;*******************************
;N: Both Interrupt Service Routines (ISRs) are moved to start of .COM
;   image to reduce possible offset changes during development.
;   Offset changes made resident DOSLFN indeinstallable, and I had to reboot
	org	100h
	jmp	transient
ctrl0	db	11110110b		;8 Ein/Aus-Schalter
		;Bit7: global ein/aus
		;Bit6: Schreiben e/a
		;Bit5: Schlangen e/a
		;Bit4: Tunneleffekt e/a
		;Bit3: CDROM-Code vorhanden e/a
		;Bit2: InDOS-Benutzung
		;Bit1: Schreibschutz-Attribut für CD e/a (NORO.COM Gegenstück)
		;Bit0: (ungenutzt)

proc NewInt21 far	;Neue INT21-Routine
;D: Int21 handler, always returns with IRET, chaining to OldInt21
;   by jumping rather than calling preserves stack frame for odd-behaved
;   other APIs (if any)
;   Contains Installation Check of DOSLFN (maybe moved to Int2F)
	pushf
	 cmp	ax,REQfunc      ;Installationscheck?
	 jne	I21cont
	 cmp	dx,REQcode
	 jne	I21cont
	 mov	ax,ANScode
	 mov	dx,cs
	popf
	iret
endp

proc NewInt2F far
;D: Because of dummies and for easier usage, DOSLFN (version 0.32j+)
;   traps Int2F for following actions:
; * Disables itself when Win9x starts, giving a hint message
; * Loads an API translating VxD "LFNXLAT.386" when Win3x starts
; * giving a warning message pointing to missing API translation
;   when Win3x or Win2x start in Standard Mode (=PM286)
; * Loads the appropriate OEM<->Unicode conversion table when
;   NLSFUNC changes the code page
; * Future Installation Check of DOSLFN may reside here
	pushf
	 cmp	ax,1605h	;Windows Enhanced Mode Init Broadcast
	 jz	@@handle
	 cmp	ax,1606h	;Exit Broadcast
	 jz	@@handle
	 cmp	ax,1401h	;NLSFUNC.COM CallOut BX=Codeseite
	 jz	@@handle
;	 cmp	ax,0AD01h	;DISPLAY.SYS API Aufruf BX=Codeseite
;	 jz	@@handle
@@e:	popf
	JMPF		;= db 0EAh
OldInt2F dd	?
@@handle:
	popf
;	push	ax		;(Stack-Parameter für HandleWindowsOrCP)
	pushf
	call	[cs:OldInt2F]
	call	HandleWindowsOrCP
	iret
endp

proc I21cont far
	 test	[cs:ctrl0],CTRL_Main
	 jz	@@isrend	;do nothing if main switch is OFF
	 xchg	ah,al		;AL comparings are shorter
	 cmp	al,71h		;LFN functions?
	 je	@@yes_lfn
	 cmp	al,3Ah		;SFN rmdir?
	 je	@@yes_sfn	;remove LFN entry, prepare tunnel!
	 cmp	al,41h		;SFN unlink?
	 je	@@yes_sfn	;remove LFN entry, prepare tunnel!
	 cmp	al,56h		;SFN move? (dst=ES:DI)
	 je	@@yes_sfn	;remove LFN, prepare&apply tunnel!
	 cmp	al,39h		;SFN mkdir?
	 je	@@yes_sfn	;apply tunnel!
	 cmp	al,3Ch		;SFN creat?
	 je	@@yes_sfn	;apply tunnel IF file doesn't exist
	 cmp	al,5Bh		;SFN create new?
	 je	@@yes_sfn	;apply tunnel!
	 cmp	ax,006Ch	;SFN extended open/create?
	 je	@@yes_sfn	;apply tunnel IF..., take DS:SI!
	 cmp	al,4Eh		;SFN FindFirst?
	 xchg	ah,al
	 je	@@filter0F	;filter out volume labels!
	 cmp	ah,4Fh		;SFN FindNext?
	 je	@@filter0F	;filter out volume labels too!
	 cmp	ah,11h		;FCB FindFirst?
	 je	@@FCB_11	;filter out volume labels!
	 cmp	ah,12h		;FCB FindNext?
	 je	@@FCB_12	;filter out volume labels too!
@@isrend:	popf
		JMPF		;jmp far (db 0EAh)
OldInt21	dd	?	;only this kind of jump makes no trouble

@@filter0F:	;bei FindFirst/FindNext Rückgaben mit Attribut 0Fh auswerfen
	popf
	sti
	call	sfn_find	;this routine is fully reentrant
	retf	2
@@FCB_11:
	popf
	call	sfn_find_FCB
	mov	ah,11h		;set back (may be altered to 12h)
	iret
@@FCB_12:
	popf
	call	sfn_find_FCB
	iret
@@yes_lfn:
	 INT3
@@yes_sfn:
	popf			;save two bytes of stack needed
	xchg	ah,al
	call	IncInDosFlag
	sti		;reentrancy should be detected by caller
	push	fs ds es	;bp+14h .. bp+10h
	pusha		;ax=bp+0Eh, cx..dx..bx..sp..bp..si..di=BP+0
	 mov	bp,cs
	 mov	ds,bp
	 mov	es,bp
	 mov	bp,sp
	 push	eax		;High-Teile retten für PKZIP
	 mov	al,[ctrl0]
	 mov	[ctrl],al	;access needs less bytes
	 pop	ax
	 push	edx
	  cld
	  mov	[throw_sp],sp	;prepare and beautiful error exit, less "jc"
	  mov	[throw_fi],ofs _fcb_retu	;standard finalizer
	  mov	fs,[Client_DS]	;häufig benötigt
	  shr	[by LOW Client_Flags],1	;CY ausschieben
	  cmp	ah,71h
	  je	@@yes_long
	  mov	al,PF_Tunnel_Restore
	  cmp	ah,3Ah
	  je	@@rm
	  cmp	ah,41h
	  je	@@rm
	  cmp	ah,56h
	  jne	@@cr
	  mov	al,PF_Tunnel_Save or PF_Tunnel_Restore
	  jmp	@@cr
@@rm:	  mov	al,PF_Tunnel_Save
@@cr:	  mov	[PFlags],al
	  call	sfn_process	;all writing SFN functions
	  jmp	__no_func
@@yes_long:
	  call	CheckLoadCP
	  mov	[PFlags],PF_LFN_Input
	  mov	[FuncNum],al
	  mov	di,ofs verteiler
	  call	case
	  mov	ax,7100h	;für "nicht unterstützt" (->PFlags!)
	  jc	__no_func
	  inc	[counter_i2171]
	  call	[wo di]
__no_func:
	  jnc	@@no_ax
	  mov	[Client_AX],ax
@@no_ax:  rcl	[by LOW Client_Flags],1	;CY einschieben
	 pop	edx
	 push	ax
	 pop	eax
	popa
	pop	es ds fs
	call	DecInDosFlag
	iret
endp

;THROW-Geschichten...
SetErr18:
	mov	al,18
	db	0B9h		;mov cx,nnnn
SetErr5:
	mov	al,5
	db	0B9h		;mov cx,nnnn
SetErr3:
	mov	al,3
	db	0B9h		;mov cx,nnnn
SetErr2:
	mov	al,2
SetError:
	call	SetExtError	;"eigene" Fehler setzen
	stc
proc Throw
	mov	sp,8086
throw_sp = wo $-2
	call	[throw_fi]
	jmp	__no_func
endp

;***************************
;** Legacy API twiddeling **
;***************************
;N: Except MS-DOS7+, other DOS versions generate some garbage on
;   FindFirst/Next: they list out LFNs as volume labels.
;   Well-behaved file managers ignore these entries, but some other
;   like COMMAND.COM's built-in DIR doesn't.
;   Therefore, it's up to DOSLFN to wipe out such returns, proceeding
;   with next DirEnt.
;   And while we twiddle with DirEnts, this is a good chance to
;   remove the ugly ReadOnly bits out of CD DirEnts, but is not yet done.

proc noentry_sfn_find
;D: DirEnts with attribute 0Fh (LFN designator) must be wiped out.
;   (MS-DOS 7+ and Windows NT [DOS 5] do this;
;    this is necessary for all other DOS versions)
;I: registers unchanged, _without_ BP stack frame!
;F: INT21/4E&4F alter AX even if successful! 09/01, claude.caillet@free.fr
@@rept:	popf
	mov	ah,4Fh
sfn_find:
	call	CallOld
	jc	@@e
	pushf			;don't change other flags (esp. ZF)
	 push	ax bx es
	  mov	ah,2Fh
	  call	CallOld		;get address of DTA
	  cmp	[(TSearchRec es:bx).attr],0Fh
	 pop	es bx ax
	 je	@@rept		;next iteration makes FindNext
	popf
@@e:	ret
endp

proc sfn_find_FCB
;FU: FindFirst/FindNext via FCB muss auch gefiltert werden
;    (so ein Laster, das von der COMMAND.COM aufgebürdet wird,
;     niemand sonst verwendet diese Funktionen heutzutage.)
;    (MS-DOS 7 und Windows NT [DOS 5] filtern selbst;
;     notwendig ist diese Aktion für alle anderen DOS-Versionen)
@@rept:	call	callold
	or	al,al
	jnz	@@err
	push	bx es
	 mov	ah,2Fh
	 call	CallOld			;get address of DTA
	 cmp	[by es:bx],0FFh	;Extended FCB?
	 jne	@@1			;nein, bx nicht verschieben
	 add	bx,7
@@1:	 cmp	[by es:bx+12],0Fh
	pop	es bx
	mov	ah,12h
	je	@@rept
@@err:	ret
endp

proc SFN_AL_CallOld
;FU: Aufruf des vorherigen INT21 mit DX=Zeiger auf ShortBuffer
;    Seiteneinstiege ohne Laden von AH und DX
	mov	ah,[Client_AL]
SFN_CallOld:
	mov	dx,ofs ShortBuffer
CallOld:
;	call	DecInDosFlag
	pushf
	call	[cs:OldInt21]
;	call	IncInDosFlag
_coat:	ret
endp

proc CallOldAndThrow
	call	CallOld
	jnc	_coat
	jmp	Throw
endp

proc SetExtError
;DOS-Fehler (für Int21 AH=59h) setzen (für momentanes PSP)
;Tatsächlich benötigt Win95 COMMAND.COM diese Funktion!
	mov	ah,0			;alles "kleine" Fehler
	push	ax
	 mov	di,ofs ErrInfo
	 mov	dx,di
	 stosw
	 mov	cx,(SIZE ErrInfo)/2 -2
	 xor	ax,ax
	 rep	stosw			;dazwischen lauter Nullen
	 mov	ah,62h
	 call	CallOld			;PSP ermitteln
	 xchg	bx,ax			;als letztes
	 stosw
	 mov	ax,5D0Ah
	 call	CallOld			;Fehler setzen
	pop	ax
	ret
endp

;*****************************************
;** Windows and Code Page Notifications **
;*****************************************
proc HandleWindowsOrCP
;D: Instruct Windows 3.x to load a VxD for API translating
;(PE: Auf dem Stack: AX vor dem Aufruf von OldInt2F - nein!)
;PE: DS und ES zeigen (n)irgendwohin!
;    Bei Windows (AH=16):
;     Bit0(DX)=0: Enhanced Mode
;      Bei Windows-Start (AL=5):
;	DS:SI=(uninteressant hier)
;	ES:BX=LPWinStart
;	CX=0: Windows darf starten
;	DI=Versionsnummer
;    bei Codeseitenwechsel (AH<>16):
;     AL=0: OK (von DISPLAY.SYS o.ä.)
;     BX=Codeseite
;PA: nur bei Windows-Start (AX=1605, DI<0400):
;	ES:BX=neue LPWinStart zum Laden von LFNXLAT.386
;VR: keine! Auch keine Segmentregister! Außer ES:BX im Fall s.o.
;    [ErrInfo]
	push	ds es
	pusha
	 LD	es,cs
	 LD	ds,cs
	 mov	bp,sp
	 cld
	 cmp	ah,16h		;Betrifft Windows?
	 je	@@win
	 or	al,al		;CHCP verlief OK?
	 jnz	@@e
	 mov	[NewCP],bx	;Wegen InDOS muss verzögert geladen werden
	 jmp	@@e
@@win:
	 BTST	dx,bit 0	;Enhanced Mode?
	 jnz	@@e
	 cmp	al,05h		;Init Broadcast?
	 je	@@winstart
	 BSET	[ctrl0],CTRL_Main ;dann Exit Broadcast (AL=6)
	 jmp	@@e
@@winstart:
	 or	cx,cx		;Anderes TSR sträubt sich?
	 jnz	@@e
	 cmp	di,400h		;Windows 95+?
	 jc	@@loadvxd
	 BRES	[ctrl0],CTRL_Main
	 jmp	@@e
@@loadvxd:
	 lea	di,[ErrInfo]
	 mov	[Client_BX],di	;BX modifizieren
	 mov	ax,3		;Version 3.00
	 stosw
	 xchg	ax,bx
	 stosw
	 mov	ax,es
	 xchg	ax,[Client_ES]	;ES modifizieren
	 stosw
	 mov	ax,[argv0]
	 stosw
	 mov	ax,ds
	 stosw
	 xor	ax,ax
	 mov	cl,4		;CH=0
	 rep	stosw		;2 DWörter
	 lea	si,[lfnxlatvxd$]
	 mov	di,[argv0file]
	 call	strcpy
@@e:	popa
	pop	es ds
	ret
endp

proc MakeTblFileName
;PE: DI=Ziel Dateiname
;    BX=Codeseite
;VR: AX,BX,CX,DX,DI
	lea	si,[TblFileName$]
	call	strcpy	;DI zeigt hinter die Null
	mov	ax,10
	xchg	ax,bx
@@l:	xor	dx,dx
	div	bx
	add	dl,'0'
	mov	[di-9],dl;"UNI.TBL\0" zurück auf die letzte Ascii-Null
	dec	di
	or	ax,ax
	jnz	@@l	;maximal 5 Runden
	ret
endp

proc LoadCP
;D: Loads Code Page Table (CPxxxUNI.TBL) file according to code page in BX
;PE: BX=Codeseite (bei Nummern >=1000 wird "P" oder "CP" überschrieben)
;VR: alle (außer FS)
	mov	di,[argv0file]
	call	MakeTblFileName
	mov	dx,[argv0]
;	jmp	LoadUniFile
endp

proc LoadUniFile
;D: Loads Unicode file to, removes old table from heap
;I: DX=filename
;O: CY=1 error, BL=3: cannot open file, BL=4: wrong file format
;   CY=0 OK, table loaded
;VR: alle, DS=ES, [ErrInfo], [TrailMinLen], [UniTableLen]
;Lädt beide Arten von Tabellen; die Unterscheidung, ob SBCS oder DBCS
;fällt Oem2Uni bzw. Uni2Oem anhand IsDbcsLeadByte, dem Tabellen-Wert u.ä.
	DOS	3D00h		;zum Lesen öffnen
	mov	bl,3
	jc	@@e		;Datei nicht gefunden o.ä.
ReadUniFile:		;Einstieg mit AX=Handle
	xchg	bx,ax
	mov	cx,32
	lea	dx,[ErrInfo]
	DOS	3Fh		;32 Bytes lesen, Header darf nicht länger sein
	cmp	ax,cx
	jne	@@e13		;Datei ungültig: viel zu kurz
	mov	si,dx
@@l:	lodsb
	or	al,al		;Kennbyte 00h
	jz	@@e13		;von ASCII->ASCII-Tabellen weiß DOSLFN nichts
	dec	ax		;Kennbyte 01h (in ersten 32 Byte)? (AH=0)
	jz	@@1		;"normale" Unicode-Tabelle
	dec	ax		;Kennbyte 02h (in ersten 30 Byte)? (AH=0)
	loopnz	@@l
	;DBCS
	cmp	cl,2		;Keine 2 Bytes da?
	jc	@@e13		;dann .TBL ungültig
	lodsw			;DBCS-Info
	mov	[TrailMinLen],ax
@@1:	;SBCS
	sub	si,dx
	xor	cx,cx
	push	si		;Lese-Position
	 xor	dx,dx
	 DOS	4202h		;Datei-Länge in DX:AX, DX wird ignoriert
	pop	dx
	sub	ax,dx
	or	ah,ah		;wie "cmp ax,100h"
	jz	@@e13		;Datei zu kurz
	push	ax		;Rest-Länge in AX
	 shr	ax,1
	 mov	[UniTableLen],ax ;Anzahl Unicodes, bei SBCS = 80h
	 DOS	4200h		;jetzt zum Daten-Anfang (CX=0)
	 lea	di,[UniXlat]
	 call	FreeDIPtr
	pop	ax
	push	ax
	 call	iLocalAlloc
	 mov	[UniXlat],di
	pop	cx
	jc	@@e13		;Zu lang, Heap reicht nicht
	mov	dx,di
	DOS	3Fh
	cmp	ax,cx
	jne	@@e13		;Datei ungültig: zu kurz (kann nicht sein)
	DOS	3Eh		;DOS setzt CY
@@e:	ret
@@e13:	DOS	3Eh
	mov	bl,4
	stc
	ret
endp

proc CheckLoadCP
;Prüft auf neue Unicode-Tabelle und lädt diese gegebenenfalls nach
	pusha
	 xor	bx,bx
	 xchg	[NewCP],bx
	 or	bx,bx
	 jz	@@e
	 call	LoadCP
	 jnc	@@e
	 mov	[LastError],5
@@e:	popa
	ret
endp

;****************************************
;** Initialized Constant and Data Area **
;****************************************

language	db	'E'	;'D'eutsch,'F'rancais,'N'ihongo,'R'ussky?
UniXlat		dw	0	;0 bedeutet hier: OEM = ISO-Latin-1
NewCP		dw	0	;wird bei Int2F gesetzt
rwrec		tRWRec	<?,1,ofs Sektor>
lfnxlatvxd$:	dz	"LFNXLAT.386"
TblFileName$:	dz	"CP000UNI.TBL"
	align	2

;N: The following OEM->Unicode conversion table may be overloaded from file.
;   Therefore, all 1-byte-per-character DOS versions can be supported
;   with all national characters.
;   For locales with DBCS (double-byte character set), like Chinese,
;   Japanese, or Hangul (=Korea), such a table is not applicable;
;   DOSLFN doesn't work.
;N2: THIS TABLE ISN'T INTEGRATED AND USED ANYMORE (01/03) WITH FULL-DBCS
;label oem_uni_tab word		;hier: Codeseite 437
;	dw	00C7h, 00FCh, 00E9h, 00E2h, 00E4h, 00E0h, 00E5h, 00E7h
;	dw	00EAh, 00EBh, 00E8h, 00EFh, 00EEh, 00ECh, 00C4h, 00C5h
;	dw	00C9h, 00E6h, 00C6h, 00F4h, 00F6h, 00F2h, 00FBh, 00F9h
;	dw	00FFh, 00D6h, 00DCh, 00A2h, 00A3h, 00A5h, 20A7h, 0192h
;	dw	00E1h, 00EDh, 00F3h, 00FAh, 00F1h, 00D1h, 00AAh, 00BAh
;	dw	00BFh, 2310h, 00ACh, 00BDh, 00BCh, 00A1h, 00ABh, 00BBh
;	dw	2591h, 2592h, 2593h, 2502h, 2524h, 2561h, 2562h, 2556h
;	dw	2555h, 2563h, 2551h, 2557h, 255Dh, 255Ch, 255Bh, 2510h
;	dw	2514h, 2534h, 252Ch, 251Ch, 2500h, 253Ch, 255Eh, 255Fh
;	dw	255Ah, 2554h, 2569h, 2566h, 2560h, 2550h, 256Ch, 2567h
;	dw	2568h, 2564h, 2565h, 2559h, 2558h, 2552h, 2553h, 256Bh
;	dw	256Ah, 2518h, 250Ch, 2588h, 2584h, 258Ch, 2590h, 2580h
;	dw	03B1h, 00DFh, 0393h, 03C0h, 03A3h, 03C3h, 03BCh, 03C4h
;	dw	03A6h, 0398h, 03A9h, 03B4h, 221Eh, 03C6h, 03B5h, 2229h
;	dw	2261h, 00B1h, 2265h, 2264h, 2320h, 2321h, 00F7h, 2248h
;	dw	00B0h, 2219h, 00B7h, 221Ah, 207Fh, 00B2h, 25A0h, 00A0h
;B: Because a more advanced DOSLFN version should fully support Japanese,
;   this table is intended to load dynamically and to move into heap.
;B: For Japanese support, the conversion table to load must contain code
;   for: MBCS->Unicode, Unicode->MBCS, and Upcase.
;   Difficult for DBCS is the Path Delimiter "\", because it maps to YEN
;   symbol, even worse, it is a valid trail byte.
;   This is done by Microsoft. Much easier is the use of "/" instead.
;N: Format of such a DBCS<->Unicode table is yet defined: see TBL.TXT

Invalid_Lfn_Chars db	'"<|>:\/'	;7 characters

Invalid_Chars	db	0,' .+,;=[]'	;the order here is strategic
;B: Both charsets above mean invalid characters for SFN. The dot (.)
;   has special meaning in SFN and is handled separately

;B: Normally, the space character 32 (20h) is allowed in SFN,
;   but the Win9x LFN API dircards spaces (and dots) when building SFN aliases
;   Furthermore, the space is problematic because most command-line
;   utilities do not support an escape to interpret a space literally

;B: Dots and spaces are invalid for LFNs at the end! They are stripped
;   automatically. Consequently, names containing only dots and/or spaces
;   are invalid, with the exception "." and ".." for the special directories.
;B: Although Explorer doesn't allow to create names _beginning_ with a dot
;   or space, these are allowed file names. This is an Explorer bug.


;Statistik-Zähler
counter_read	dw	0
counter_write	dw	0
counter_i2171	dw	0
LastError	db	0

proc Check_CDFS
	test	[DriveType],DT_CDFS	;bytefressender Befehl
	ret
endp

proc Check_CDFS_Throw
	call	Check_CDFS
	jnz	SetErr5
	ret
endp

;*****************************
;** Basic Sector Read/Write **
;*****************************

ReadNextSec:
	inc	[CurSector]

proc ReadSec
;{Liest einen Sektor Nummer <CurSector> nach <Sektor>, PA: CY=1:Fehler}
;Liest nur, wenn's ein neuer Sektor ist!
;PE: CurSector=Sektor-Nummer
;PA: [Sektor] gefüllt mit Sektor-Daten
;    CY=1: Fehler
;VR: alle (bedeutet hier und im folgenden EAX,BX,CX,EDX,SI und DI,
;	   nicht aber DS,ES,SS,SP und BP, werden dann extra gelistet)
   mov	eax,[CurSector]
ReadSecEAX:
   mov	[CurSector],eax
   cmp	[rwrec.sect],eax
   je	@@e		;nichts tun!
   call	FlushDirty
   cmp	eax,[CD_BackupSektor]
   je	@@restore_backup
   jmp	@@make_backup
@@nobackup:
   mov	[rwrec.sect],eax
   inc	[counter_read]

	mov	al,[DriveType]
	test	al,DT_CDFS
	jnz	@@cdfs
	xor	si,si		;lesen FAT32
	mov	di,ofs int25	;lesen FAT16
	jmp	Fat_RW
@@cdfs:
   mov	eax,[CD_BackupSektor]
   cmp	eax,[rwrec.sect]
   je	@@restore_backup
   mov	bx,ofs CD_Sektor
   mov	si,[wo HIGH rwrec.sect]
   mov	di,[wo LOW  rwrec.sect]
   movzx cx,[DPB_Drive]
   mov	dx,1		;nur 1 Sektor!
   MUX	1508h		;MSCDEX: Sektor lesen
@@e:
   ret
@@restore_backup:
   call	Check_CDFS
   jz	@@nobackup
   xchg	[rwrec.sect],eax
   mov	[CD_BackupSektor],eax
   mov	si,ofs CD_Sektor
   mov	di,ofs CD_Backup
   mov	cx,1024		;512 DWords = 2048 Bytes
@@swap:
   mov	ax,[si]
   xchg [di],ax
   mov	[si],ax
   cmpsw		;einfach si+=2 und di+=2
   loop	@@swap
   clc
   ret
@@make_backup:
   call	Check_CDFS
   jz	@@nobackup
   push	eax
    mov	eax,[rwrec.sect]
    mov	[CD_BackupSektor],eax
    mov	cx,512
    mov	si,ofs CD_Sektor
    mov	di,ofs CD_Backup
    rep	movsd
   pop	eax
   jmp	@@nobackup
endp

;******************************
;** Basic Sector Cache Flush **
;******************************

proc FlushDirty
	test	[DriveType],DT_Dirty
	jz	@@e
_WriteNow:
	push	eax
	 call	WriteSec
	pop	eax
	and	[DriveType],not DT_Dirty
@@e:	ret
endp

proc WriteNow
	call	_WriteNow
ResetDrv:
	mov	ah,0Dh
	jmp	CallOld
endp

proc WriteSec
;{Schreibt Sektor <Sektor> nach Nummer <RWRec.Sect>, PA: Fehlercode}
;PE: RWRec.sect=Sektor-Nummer
;    [Sektor] gefüllt mit Sektor-Daten
;PA: CY=1: Fehler
;VR: alle
   inc	[counter_write]
	mov	si,4001h	;schreiben: Verzeichnis-Bereich FAT32
	mov	di,ofs int26	;schreiben: FAT16
	bts	[wo DriveType],6	;DT_Locked
	jc	Fat_RW		;Laufwerk ist bereits gesperrt
	mov	bl,[DPB_Drive]
	inc	bl
	mov	bh,0		;LOCK LEVEL
	mov	cx,084Ah
	mov	dx,1		;für Schreibzugriff
	mov	ax,440Dh
	call	CallOld		;DOS7 LOCK
	;jmp	Fat_RW
endp

proc Fat_RW
;FU: Lesen und Schreiben von/auf FAT16 und FAT32
;PE: DI=Zeiger auf Prozeduren int25 oder int26 (FAT16)
;    SI=0 für Lesen, 4001h für Schreiben Verzeichnis-Daten (FAT32)
;    [DPB_Drive]=Laufwerk (0=A: usw.)
;    [DriveType]=Laufwerkstyp, FAT12/16/32,BIGDOS
;    [RWRec]=Sektor,Anzahl,Speicheradresse
;VR: alle
	mov	al,[DriveType]
	test	al,DT_FAT32
	jnz	Fat32_RW
	test	al,DT_BigDos
	jnz	@@b		;{gleich zu BIGDOS}
	mov	si,ofs RWRec	;3 Bytes
	lodsw			;je 1 Byte
	xchg	dx,ax		;DX=Sektornummer
	lodsw
	lodsw
	xchg	cx,ax		;CX=Anzahl
	lodsw
	xchg	bx,ax		;DS:BX=Sektor-Adresse
	push	di
	 call	@@pu
	pop	di
	jnc	@@e
	cmp	ax,0207h	;{zu BIGDOS wechseln? lt.RBIL}
	stc
	jnz	@@e		;{nein, sonstiger Fehler}
	or	[DriveType],DT_BigDos
@@b:
	lea	bx,[RWRec]
	mov	cx,0ffffh
@@pu:	mov	al,[DPB_Drive]
	push	bp		;zumindest DR-DOS zerstört BP
	 call	di
	pop	bp
@@e:	ret
endp

proc Fat32_RW
;FU: Lesen und Schreiben von/auf FAT32
;    [DPB_Drive]=Laufwerk
;    [RWRec]=Sektor,Anzahl,Speicheradresse
	mov	dl,[DPB_Drive]
	inc	dl
	mov	cx,0FFFFh
	lea	bx,[RWRec]
	mov	ax,7305h
	jmp	CallOld		;10/02: ohne Rekursion
endp

proc int25
	int	25h
	pop	bp		;{Stack korrigieren}
	ret
endp

proc int26
	int	26h
	pop	bp		;{Stack korrigieren}
	ret
endp

proc GetDrvParams pascal
uses es,si
;{Ermittelt Laufwerksparameter für <@drive> (1=A: usw.)
; und setzt die Variablen DPB_xxx sowie DriveType, PA: DriveType<>0 wenn OK}
; Von Laufwerken >=C: erfolgt die Bestimmung nur bei neuem Buchstaben,
; bei Disketten- und CD-Laufwerken nur nach Ablauf eines TimeOuts
;PE: AL=Laufwerksnummer (0=current, 1=A:)
	sub	al,1
	jnc	@@nodef
	mov	ah,19h		;Akt. Laufwerk beschaffen
	call	CallOld
@@nodef:
	cmp	al,[DPB_Drive]	;gleich?
	jne	@@readin	;bei Unterschied sofort lesen
	cmp	al,2
	mov	cx,FDChangeTime
	jc	@@chktick	;Laufwerke default:, A: und B:
	call	Check_CDFS
	mov	cl,CDChangeTime
	jnz	@@chktick
@@nc0:
	jmp	@@no_change	;etwas Jump-over-Jump
@@chktick:
	push	ax
	 call	GetTick
	 sub	ax,[LastAccessTime]
	 cmp	ax,cx
	pop	ax
	jc	@@nc0
@@readin:
  push	ax
   call	CD_Done		;Dynamische Datenstrukturen aufgeben
   test	[DriveType],DT_Locked
   jz	@@no_lock
   mov	bl,[DPB_Drive]
   inc	bl
   mov	cx,086Ah
   mov	ax,440Dh		;MS-DOS7 UNLOCK
   call	CallOld
@@no_lock:
  pop	ax
  mov	[DPB_Drive],al
  mov	[DriveType],0
;{Abfrage für FAT12/FAT16}
  push	ds
   mov	dl,[DPB_Drive]
   inc	dl
   mov	ah,32h		;{liefert AL=0:OK, FFh=Fehler}
   call	CallOld
   mov	dx,ds
  pop	ds
  or	al,al
  jnz	@@test32
  mov	es,dx
  cmp	[(tExDPB es:bx).SecLen],200h	;{Standardmäßige Sektorlänge?}
  jnz	@@e
  mov	al,[(tExDPB es:bx).Drive]
  cmp	al,[DPB_Drive]			;geSUBSTet?
  jne	@@e
  or	[DriveType],DT_DrvPar
  movzx	eax,[(tExDPB es:bx).UsrSec]
  mov	[DPB_UsrSec],eax
  mov	ax,[(tExDPB es:bx).ResSec]
  mov	[DPB_FAT1Sec],eax
  mov	ax,[(tExDPB es:bx).SecDir]
  mov	[DPB_DirSec],eax
  mov	cl,[(tExDPB es:bx).Shift]
  mov	ax,[(tExDPB es:bx).HiClus]	;Letzter Cluster
  cmp	ax,0FF6h
  jbe	@@ok
  or	[DriveType],DT_FAT16
@@ok:;Ansprung mit CL=Shift und EAX=LastCluster
  mov	[DPB_Shift],cl
  inc	eax
  shl	eax,cl				;1.nicht-adressierbarer Sektor
  dec	eax
  mov	[DPB_LastSec],eax
  cmp	eax,0FFFFh
  jc	@@e
  or	[DriveType],DT_BigDos
  jmp	@@e
;{Abfrage für FAT32}
@@test32:
	call	get_dpb_32		;now with own stack frame
	jnc	@@ok

@@test_cd:
	test	[ctrl],CTRL_CDROM
	jz	@@nodrv		;Will kein CD-ROM erkennen
  mov	cl,[DPB_drive]
  mov	ch,0		;CX=Laufwerk (hier 0=A:)
  call	CD_Init
  jnc	@@e
@@nodrv:
  mov	[DriveType],0	;kein unterstützter Laufwerkstyp
  jmp	@@e

@@e:
  call	InvalSector
  mov	[fastopen_buf],0
@@no_change:
  call	GetTick
  mov	[LastAccessTime],ax
  ret
endp

proc get_dpb_32 pascal
;I: [DPB_Drive]=drive to get info
;O: [DPB_Drive]=physical drive (changed if SUBSTed)
;   [DriveType], [DPB_FAT1Sec], [DPB_UsrSec], [DPB_DirSec] filled with values
;   EAX=max_cluster
;   CL=shift
;   CY=1 if error
;N: change 10/01: stack storage for tExDPB, never overwrite sector data
local @@DPB:TExDpb
	push	3Dh		;61 bytes space, marker below @@dpb
	mov	di,sp
	push	es
	 LD	es,ss

	 mov	cx,3Fh		;{63 Bytes}
	 mov	dl,[DPB_Drive]
	 inc	dl
	 mov	si,0F1A6h
	 stc
	 mov	ax,7302h	;{get extended DPB}
	 call	CallOld
	pop	es
	jc	@@e		;{Fehler: kein FAT32}
	cmp	[@@DPB.SecLen],200h
	stc
	jne	@@e		;cannot yet accept other sector sizes
	mov	al,[@@DPB.Drive]
	cmp	al,[DPB_Drive]
	stc
	jne	@@e		;cannot support SUBSTed drives yet
	or	[DriveType],(DT_FAT32 or DT_BigDos or DT_DrvPar)
				;{FAT32 ist immer BigDos}
	movzx	eax,[@@DPB.ResSec]
	mov	[DPB_FAT1Sec],eax
	mov	eax,[@@DPB.first_sector]
	mov	[DPB_UsrSec],eax
;here bugfix 10/01: root directory is not always at cluster 2!
	mov	cl,[@@DPB.Shift]
	mov	eax,[@@DPB.root_cluster]
	sub	eax,2
	shl	eax,cl
	add	eax,[DPB_UsrSec]	;should CLC
	mov	[DPB_DirSec],eax
	mov	eax,[@@DPB.max_cluster]
@@e:	ret
endp

proc InvalSector
;FU: Gelesenen Sektor-Inhalt für ungültig erklären
;    (weil Schreibzugriff auf Verzeichnis erfolgte u.ä.)
	mov	[rwrec.sect],-1		;"falscher Sektor" laden
	ret
endp

;Es muss die Zeit beim Zugriff auf wechselbare Medien
;(A:, B: und CD-Laufwerke) erfasst werden...
proc GetTick
	push	ds
	 push	40h
	 pop	ds
	 mov	ax,[6Ch]	;55-ms-Timer abfragen (Aufwärtszähler)
	pop	ds
	ret
endp

fstrcpyBS: mov	ax,ofs strcpyBS
	jmp	noentry_fstrcpy
fstrcpy: mov	ax,ofs strcpy
proc noentry_fstrcpy pascal
;fast normale strcpy-Funktion, erwartet cld, verändert keine Register
;liefert in AX Anzahl kopierter Zeichen (ohne Null), also strlen
arg @dst:dword,@src:dword
uses ds,es,si,di
	lds	si,[@src]
	les	di,[@dst]
	call	ax
	lea	ax,[si-1]
	sub	ax,[wo LOW @src]	;Anzahl Zeichen
	ret
endp

proc strcpyBS
@@l:	lodsb
	cmp	al,'/'
	jne	@@1
	mov	al,'\'
@@1:	stosb
	or	al,al
	jnz	@@l
	ret
endp

proc strcpy
	lodsb
	stosb
	or	al,al
	jnz	strcpy
_fcb_retu:
	ret
endp

File_Flag_Wildcards	=01h		;? oder * enthalten
File_Flag_Is_LFN	=02h		;s.u.
File_Flag_Has_Star	=04h		;* enthalten
File_Flag_Has_Dot	=08h		;. (nicht am Anfang) enthalten
; File_Flag_DotAtEnd	=10h		;(Abgeschnittener) Punkt am Ende
File_Flag_NDevice	=20h		;Regulärer Dateiname
File_Flag_LowerCase	=40h		;Kleinbuchstaben a-z enthalten
File_Flag_Char_High	=80h		;codeseitenabhängige Zeichen
; File_Flag_DBCS_Char	=80h

;Ist File_Flag_Is_LFN gesetzt, kann entweder ein langer Name vorliegen,
;oder der (vielleicht kurze) Name enthält die Zeichen ' .+,;=[]'
;Es bedeutet, dass der Name keinesfalls als FCB-Name zu finden ist.
;Beim Erstellen mit Schlangen-Zwang ist Schlange zu setzen.
;In Verbindung mit File_Flag_Has_Star bedeutet es, dass der Win32-
;Namensvergleich stattfinden muss (d.h. im Rückfallmodus muss nach
;der Suche nach *.* noch einmal getestet werden)
;
;Zusätzlich zu File_Flag_Lowercase ist auch File_Flag_Char_High gesetzt,
;wenn Umlaute (also Zeichen >=80h) enthalten sind.
;Auch bei nur großen Umlauten
;legt Win9x auch in diesem Fall LFN-Einträge an,
;um Probleme mit Codeseiten zu umgehen
;Suche nach LFN-Dateinamen deshalb bei Is_LFN und/oder Char_High
;
;Ist weder File_Flag_Lowercase noch File_Flag_Is_LFN gesetzt,
;ist bei CREATE/MKDIR/MOVE keinerlei LFN-Eintrag zu erzeugen!

proc _noentry_Copy_FCB_Part
;BE: Kopiert LFN in FCB, FCB muss mit SPACE vorgefüllt sein
;PE: SI=LFN-Zeiger
;    BX=LFN-Ende (zeigt hinter Punkt), 0 für Ende durch ASCII-Null
;    DI=FCB-Zeiger
;    DX=FCB-Ende-Zeiger+1
;    AH=Flags (File_Flag_Has_Dot)
;    DS,ES=CS
;PA: AH=weitere Flags (File_Flag_Wildcards u.ä.) gesetzt
;VR: AX,CX,DX,SI,DI
@@lead:	pop	dx
	dec	dx		;potentielles Ende bei 7 oder 2 Bytes
	cmp	di,dx
	je	@@a		;kein Platz für Trail-Byte!
	inc	dx
	stosb
	lodsb			;TrailByte
	or	ah,File_Flag_Char_High or File_Flag_LowerCase
	jmp	@@b

@@l2:	push	dx
	 mov	dl,al
	 call	IsDbcsLeadByte
	 jnc	@@lead
	 call	UpCase
	 or	dl,dl		;>=80h
	 js	@@is_lower
	 cmp	al,dl		;verändert?
	 jz	@@was_upper
@@is_lower:
	 and	dl,80h
	 or	ah,dl		;Bit 7 setzen (lassen, sprungfrei)
	 or	ah,File_Flag_LowerCase
@@was_upper:
	pop	dx
@@b:	stosb
Copy_FCB_Part:			;Einsprung für Erweiterung
@@l3:	lodsb
	cmp	al,'?'
	je	@@qm
	cmp	al,'*'		;zu Fragezeichen machen?
	je	@@star
	push	di
	 mov	di,ofs Invalid_Chars	;0,' .+,;=[]'
	 mov	cx,9
	 repne	scasb
	pop	di
	jnz	@@cp		;erlaubte SFN-Zeichen
	or	al,al
	je	_fcb_retu
	cmp	si,bx		;auf dem (letzten) Punkt?
	je	_fcb_retu
	or	ah,File_Flag_Is_LFN
	cmp	al,' '		;Leerzeichen ist Durchläufer (kein Ersatz)
	je	@@l3
	cmp	al,'.'		;Punkt ist Durchläufer (kein Ersatz)
	je	@@l3
	mov	al,'_'		;'+,;=[]' durch '_' ersetzen
@@cp:	cmp	di,dx		;haben noch Zeichen und kein Platz?
	jne	@@l2		;Noch ist Platz
@@a:	or	ah,File_Flag_Is_LFN
	jmp	@@l3		;weiter nach '*' oder '?' fahnden

@@qm:	;falls Fragezeichen: Bit setzen und weiter
	or	ah,File_Flag_Wildcards
	jmp	@@cp
@@star:	;falls Stern: einige Extrawürste
	or	ah,(File_Flag_Wildcards or File_Flag_Has_Star)
	test	ah,File_Flag_Has_Dot
	jnz	@@q0
	mov	dx,ofs FCB_Name+11	;alles mit Fragezeichen!
@@q0:	mov	al,'?'
	db	0B1h		;mov cl,nn
@@q1:	stosb
	cmp	di,dx
	jne	@@q1
	jmp	@@l3		;zur Auswertung von File_Flag_Is_LFN
endp

proc Gen_Alias pascal
;Alias-Generierung, "ersetzt" Int21/AX=2900h
;Eine "Erweiterung" ist definiert als der String hinter dem letzten Punkt,
;welcher nicht in einer Kette am Anfang stehender Punkte ist, also
;".ab" oder "..exe" enthalten keine "Erweiterungen"
;
;TRUENAME muss schon dafür sorgen, dass:
;- nachlaufende Leerzeichen und Punkte entfernt sind
;- ein Punkt am Ende nur verbleibt, wenn ein '*' enthalten ist
;- Pfad-Trenner zusammengefasst zu '/' sind  (für DBCS)
;- keine ungültigen LFN-Zeichen enthalten sind
;- Wildcards vor Backslash als Fehler gelten
;
;Hier werden (noch) keine Schlangen zugesetzt, das macht Poke_Number...
;Beachtet erstes Zeichen im FCB (05h für E5h) nicht!

;PE: SI=Pfad-Komponente
;    DS,ES=CS
;PA: FCB gefüllt
;    Pfad-Komponente ggf. modifiziert, Null-terminiert
;    SI=nächste Pfad-Komponente oder auf ein Null-Byte
;    AH=[File_Flags]=File_Flags
;    [CurPathComp]=momentane Pfad-Komponente (=SI beim Aufruf)
;    [PFlags]:PF_Slash zeigt gelöschten Slash an
;VR: AX,BX,CX,DX,SI
uses di
	mov	[CurPathComp],si
	;FCB löschen
	mov	di,ofs FCB_Name
	mov	cx,6		;CH bleibt 0
	mov	ax,'  '
	rep	stosw		;das Byte zuviel macht nichts
	;Pfad-Komponente isolieren
	BRES	[PFlags],PF_Slash
	push	si
@@l:	 lodsb
	 or	al,al
	 je	@@e2
	 cmp	al,'/'
	 jne	@@l
	 BSET	[PFlags],PF_Slash	;ein Fall für File_Flags!
	 mov	[by si-1],0
@@e2:	pop	si
	xor	bx,bx
	;nicht-erste Punkte übergehen suchen
@@l1:	lodsb
	cmp	al,'.'		;Punkt(e) am Anfang (".login" o.ä.)
	je	@@l1		;zählt nicht als Erweiterung
	cmp	al,' '		;auch wenn gemischt mit Leerzeichen
	je	@@l1		;(was für grausige Dateinamen!)
;	or	al,al
;	jz	@@ende		;Notbremse, sollte hier nie vorkommen!
	;letzten Punkt finden
@@l2:	lodsb
	or	al,al
	jz	@@ende
	cmp	al,'.'		;Punkt entdeckt?
	jne	@@l2		;Kein anderes Zeichen mit Sonderbehandlung
	mov	bx,si		;potenzieller Ext-Zeiger (hinter Punkt)
	or	ah,File_Flag_Has_Dot	;"normaler" Punkt
	jmp	@@l2

@@ende:	dec	si		;auf die Null!

	;Name in FCB kopieren
	push	si
	 mov	si,[CurPathComp]

	 mov	di,ofs FCB_Name
	 mov	dx,ofs FCB_Name+8
	 push	dx
	  call	Copy_FCB_Part	;Namensteil (8)
	 pop	dx
	 test	ah,File_Flag_Has_Dot
	 jz	@@keine_ext
	 mov	si,bx
	 mov	di,dx
	 add	dx,3
	 call	Copy_FCB_Part	;Erweiterung (3)
@@keine_ext:
	pop	si
	mov	[File_Flags],ah
	ret
endp

proc Poke_Number_Over_FCB pascal
;BE: in [FCB_Name] wird die Zahl SI "eingepflanzt"
;    Es erfolgt kein Test auf SI=0
;PE: [FCB_Name]-Namensteil gefüllt, ab erstem Leerzeichen oder derart,
;    dass die Zahl rechtsbündig steht. (Bei DBCS ist bisweilen 1 Leerzeichen
;    dahinter, also nur 7 Bytes insgesamt, erforderlich!)
;    Erstes Byte muss hier noch E5h sein, nicht 05h!
;    [FCB_Name] darf keine regulären Zwischen-Leerzeichen enthalten!
;    DS,ES=CS
;PA: [FCB_Name]-Namensteil modifiziert unter DBCS-Beachtung
;    BX=Zeiger auf Tilde (zum Wegpoken für den nächsten Versuch)
;VR: AX (AH=0),BX,CX=0,DX
uses DI
	mov	bx,10		;Zahlenbasis
	mov	ax,si		;Zahl
	mov	cx,1		;Ziffernzähler, 1 für '~'
@@l:	xor	dx,dx
	inc	cx		;Eine Ziffer
	div	bx
	or	ax,ax		;Null?
	push	dx		;Divisionsrest (0..9) in LIFO
	jnz	@@l		;nächste Ziffer
	mov	al,9
	sub	ax,cx		;Verbleibende Bytes +1
	mov	di,ofs FCB_Name-2
@@n2:	inc	di
@@n1:	inc	di
	mov	dl,[di]
	cmp	dl,' '		;In generierten SFN gibt es keine Leerzeichen
	je	@@ok
	dec	ax
	jz	@@ok
	call	IsDbcsLeadByte
	jc	@@n1
	dec	ax		;2 Bytes
	jnz	@@n2
@@ok:	mov	bx,di		;Rückpatch-Zeiger
	push	'~'-'0'		;...die geliebte Schlange voran...
@@more:	pop	ax		;LIFO -> Reste entstanden von hinten
	add	al,'0'		;mit der höchstwertigen Ziffer anfangen
	stosb
	loop	@@more
	cmp	di,ofs FCB_Name+7	;ein Trail-Byte zu töten?
	jne	@@e
	mov	al,' '
	stosb
@@e:	ret
endp

proc Copy_Term_Spaces
;Hilfsroutine für Copy_FCB_8P3
@@l:	lodsb
Copy_Term_Spaces_1:		;Einsprung für modifiziertes AL
	stosb
	cmp	al,' '
	je	@@f
	mov	bx,di		;Möglicher Ende-Zeiger (vorrücken)
@@f:	loop	@@l
	mov	di,bx		;bei Trailing Spaces zurückstellen
	ret
endp

proc Change_First_FCB_Byte
;FU: Hilfsprogramm für Copy_FCB_8P3 und Is_FCB_Equal
;PE: AL=1. FCB-Byte
;    CL=Ersatz für E5 (Lösch-Kennung)
;PA: AL=Geändertes Byte: E5->CL, 05->E5, sonst unverändert
;VR: AL,CL
	cmp	al,0E5h		;Gelöschter Eintrag?
	je	@@1		;auf CL setzen (Vorgabe)
	cmp	al,05h		;Ersatzbyte für E5?
	mov	cl,0E5h		;das ist das Byte (nicht Zeichen - DBCS!) E5
	jne	@@e
@@1:	mov	al,cl
@@e:	ret
endp

proc Copy_FCB_8P3
;Kopiert FCB-Dateiname in 8.3-Form nach DI, auch für FCB mit Leerzeichen!
;PE: BX=Zeiger auf Directory-Eintrag
;    ES:DI=Ziel (bei BX<>FCB_Name E5h am Anfang in '?', 05h in E5h wandelnd)
;    DS=CS (benötigt KEIN ES=DS wegen/für ax=71A8h)
;PA: DI vorgerückt (auf die Null), String nullterminiert und ohne Backslash
;VR: AL,CX=0,SI,DI
	mov	si,bx
Copy_FCB_8P3_from_SI:
	push	bx
	 mov	cx,'?'
	 cmp	si,ofs FCB_Name
	 lodsb
	 je	@@1		;aus lokalem FCB-Puffer NICHT wandeln!
	 call	Change_First_FCB_Byte
@@1:	 mov	bx,di		;Ein Leerzeichen vor dem Punkt zulassend...
	 mov	cl,8
	 call	Copy_Term_Spaces_1
	 mov	al,'.'
	 stosb			;Der Punkt löscht sich wieder, wenn keine Ext
	 mov	cl,3
	 call	Copy_Term_Spaces
	pop	bx
@@e:	mov	[by es:di],0
	ret
endp


proc Pick_Sector_From_DirEnt
;PE: BX=DirEnt-Zeiger (nur FAT!)
;PA: EAX=[CurSector]=[SuchSektor]=Sektor-Nummer
;    CY=1 wenn ungültige Cluster-Nummer
;VR: EAX,CL
	xor	ax,ax
	test	[DriveType],DT_FAT32
	jz	@@1
	mov	ax,[(TDirEnt bx).ClusH]
	and	ah,0Fh			;Obere 4 Bits undefiniert = FAT28!
@@1:	shl	eax,16
	mov	ax,[(TDirEnt bx).ClusL]
	;jmp	Cluster2Sector
endp

proc Cluster2Sector
;PE: EAX=Cluster-Nummer
;PA: EAX=[CurSector]=[SuchSektor]=Sektor-Nummer
;    CY=1 wenn ungültige Cluster-Nummer
;VR: EAX,CL
	or	eax,eax
	jz	_set_root_sector
	mov	cl,[DPB_Shift]
	sub	eax,2
	shl	eax,cl			;Cluster->Sektor
	add	eax,[DPB_UsrSec]
	cmp	[DPB_LastSec],eax	;Überlaufprüfung!
	jmp	_set_cur_and_such
_set_root_sector:
	mov	eax,[DPB_DirSec]
_set_cur_and_such:
	mov	[SuchSektor],eax
	mov	[CurSector],eax
	ret
endp

proc Cluster2FAT
;FU: Lädt zum Cluster EAX die FAT in den Puffer und stellt einen
;    Zeiger zur Verfügung (bei FAT12 muss ggf. ein weiterer Sektor
;    nachgelesen werden!)
;PE: EAX=Cluster-Nummer (2=erster Cluster)
;PA: EAX=Sektor-Nummer in 1. FAT
;    BX=Adresse FAT-Eintrag
;    CH Bit 0 = High-Nibble-Bit für FAT12
;VR: EAX,BX,EDX
	xchg	edx,eax
	mov	ah,3		;Nibbles pro FAT-Eintrag
	mov	al,[DriveType]
	test	al,DT_FAT16
	jz	@@1
	mov	ah,4
@@1:	test	al,DT_FAT32
	jz	@@2
	mov	ah,8
@@2:	movzx	eax,ah		;4 Bytes?
	;nun in EAX Nibbles pro Cluster-Eintrag
	mul	edx		;EDX:EAX=Nibble-Nummer

	mov	bx,ax
	and	bx,03FFh	;%1024=Nibble-im-Sektor
	shrd	eax,edx,10	;/1024=Sektor-Nummer
	add	eax,[DPB_FAT1Sec]
	push	eax bx cx
	 call	ReadSecEAX
	pop	cx bx eax
	jc	@@e
	mov	ch,bl		;letztes Bit retten
	shr	bx,1		;Nibble->Byte
	add	bx,ofs Sektor
@@e:	ret
endp

proc Alloc_Cluster	;NOT TESTED - scheint aber zu funktionieren
;FU: Clusterkette verlängern, neues Cluster löschen (mit Nullen füllen)
;PE: [SuchSektor]=1. Sektor des zu verlängernden Verzeichnisses
;    [num_cluster]=Anzahl Cluster des zu verlängernden Verzeichnisses
;PA: [sektor] geladen mit erstem Sektor des neuen Clusters
;    BX=Sektor-Zeiger
;    CY=1 bei Fehler
;    (Festplatte voll, volles Hauptverzeichnis [FAT16], DOS will nicht o.ä.)
;VR: [Alloc_Cluster_SFN],[Alloc_Cluster_FCB],[sektor] zerstört
	mov	eax,[SuchSektor]
	sub	eax,[DPB_UsrSec]
	jc	@@e0	;geht nicht im Hauptverzeichnis! (außer FAT32)
;Cluster-Kette des momentanen Vrz. kreuzverketten und verlängern!
	;Datei erzeugen und schließen
	lea	si,[ShortBuffer]
	lea	di,[Alloc_Cluster_SFN]
	mov	dx,di
	mov	cx,3		;3 Bytes
	rep	movsb		;Laufwerksbuchstabe:\
	mov	si,di		;SI für Datei->FCB
	mov	ax,5A00h
	stosb			;nullterminieren
	mov	cl,6		;system, hidden
	call	CallOld		;Temporäre Datei erzeugen
	jc	@@e0		;ging voll daneben!
	xchg	bx,ax		;Handle->BX
	mov	ah,3Eh
	call	CallOld		;Datei schließen
	jc	@@e0		;hm!
	call	ResetDrv
	;Datei aufsuchen
	lea	di,[Alloc_Cluster_FCB]
	mov	ax,2900h
	call	CallOld		;Dateiname formatieren ->ES:DI
	mov	eax,[DPB_DirSec]
	call	ReadSecEAX
	mov	bx,ofs Sektor
@@find_loop:
	lea	di,[Alloc_Cluster_FCB+1]
	call	Is_FCB_Equal_DI
	jz	@@found
	call	Next_DirEnt
	jnc	@@find_loop
@@e0:	jmp	@@e
@@found:
	;BX=DirEnt, modifizieren
	movzx	eax,[num_cluster]
	mov	cl,[DPB_Shift]
	shl	eax,cl		;EAX=Sektoren Verzeichnis
	shl	eax,9		;EAX=Bytes Verzeichnis
	mov	[(TDirEnt bx).fsize],eax
	mov	eax,[SuchSektor]
	sub	eax,[DPB_UsrSec]
	shr	eax,cl		;Cluster draus
	add	eax,2
	mov	[(TDirEnt bx).ClusL],ax
	ror	eax,16
	mov	[(TDirEnt bx).ClusH],ax	;Kreuzverbundene Cluster erzeugen
	push	bx
	 call	WriteNow
	 mov	si,0
	 jc	@@unhook
	;Datei öffnen, verlängern und schließen
	 mov	ax,3D02h	;zum Schreiben öffnen
	 lea	dx,[Alloc_Cluster_SFN]
	 call	CallOld
	 jc	@@unhook
	 xchg	bx,ax		;Handle
	 xor	cx,cx
	 xor	dx,dx
	 mov	ax,4202h	;ans Ende seeken
	 call	CallOld
	 xor	ax,ax
	 lea	di,[Sektor]
	 mov	dx,di
	 mov	cx,100h
	 rep	stosw		;Sektor löschen
	 call	InvalSector

	 mov	di,1
	 mov	cl,[DPB_Shift]
	 shl	di,cl
	 mov	cx,200h		;sektorweise
@@wr_loop:
	 mov	ah,40h		;schreiben
	 call	CallOld
	 jc	@@do_close
	 cmp	ax,cx
	 jc	@@do_close
	 dec	di
	 jnz	@@wr_loop	;wenn CY=0 und Z=0 springen
@@do_close:
	 adc	si,si		;CY retten
	 mov	ah,3Eh
	 call	CallOld
@@unhook:
	 adc	si,si		;CY einschieben
	pop	bx
	jnz	@@err
	push	bx
	;Datei hart löschen (Cluster-Kette nicht freigeben)
	 call	ReadSec
	pop	bx
	mov	[(TDirEnt bx).FName],0E5h	;einfach löschen
	call	WriteNow
@@e:	jnc	@@exi
@@err:	stc
	mov	[LastError],2	;"couldn't expand directory"
@@exi:	ret
endp

proc Calc_Next_Cluster pascal
;Berechnung für Next_Sektor, liest die FAT ein
;PE: EAX=(vorhergehender) Sektor (gerechnet ab UsrSec)
;    CL=Shift
;PA: EAX=nächster Sektor (erster Sektor des nächsten Clusters, ab UsrSec)
;    CY=1: Ende der Cluster-Kette, EAX=Cluster-Nr.
;VR: alle, [Sektor]-Inhalt zerstört
	shr	eax,cl
	add	eax,2		;EAX ist nun CLUSTER
	call	Cluster2FAT
	jc	@@e
	mov	eax,[bx]
	mov	dl,[DriveType]
	test	dl,DT_FAT32
	jnz	@@3
	movzx	eax,ax
	test	dl,DT_FAT16
	jnz	@@3a
;dicke Extrawurst für FAT12: Nachlese 2.Sektor, falls bx am Ende
	cmp	bx,ofs SektorEnde-1
	jnz	@@4
	pusha
	 call	ReadNextSec
	popa
	jc	@@e
	mov	ah,[Sektor]	;2. Byte vom Anfang nachlesen
@@4:	test	ch,1		;Nibble-Bit gesetzt?
	jz	@@5
	shr	ax,4
@@5:	and	ax,0FFFh
	cmp	ax,0FF7h
	jmp	@@3b
@@3a:
	cmp	ax,0FFF7h
	jmp	@@3b
@@3:	;nun wieder Cluster in Sektor umrechnen
	and	eax,0FFFFFFFh	;die obersten 4 Bit sind reserviert!
	cmp	eax,0FFFFFF7h
;Also bietet FAT32 max. 256 Mega-Cluster, bei heute üblichen LBA-Laufwerken
;mit 28-bit-Sektoradresse (also max. 128 GB) reicht das für 512-Byte-Cluster
@@3b:	cmc
	jc	@@e		;Ende der Clusterkette
	cmp	eax,2
	jc	@@e		;momentaner Cluster ist frei (falsch!)
	sub	eax,2
	shl	eax,cl
@@e:	ret
endp

proc Next_Sektor pascal		;nur FAT
;liefert nächsten Sektor der Clusterkette bzw. des Hauptverzeichnisses
;PE: [CurSector]=momentaner Sektor
;PA: [CurSector]=nächster Sektor, bereits gelesen
;    BX=Zeiger auf Sektor-Anfang
;    [num_cluster] inkrementiert bei Cluster-Wechsel
;VR: alle
	mov	eax,[CurSector]
	inc	eax
	cmp	[DPB_LastSec],eax
	jc	@@e		;war User-Bereich: Ende!
	sub	eax,[DPB_UsrSec];Hauptverzeichnis-Ende?
	cmc
	jz	@@e		;war Hauptverzeichnis: Ende! (mit CY=1)
	jnc	@@er		;war Hauptverzeichnis: es gibt weitere
	mov	cl,[DPB_Shift]
	mov	bx,1		;Maske bauen (low-16bit reicht)
	shl	bx,cl
	dec	bx		;0->0, 1->1, 2->3, 4->7 usw.
	test	ax,bx
	jnz	@@er		;noch im gleichen Cluster: weiter!
	dec	eax
	call	Calc_Next_Cluster
	jc	@@e		;kein nächstes Cluster: Ende!
	inc	[num_cluster]
@@er:	add	eax,[DPB_UsrSec]
	call	ReadSecEAX	;Sektor lesen
	lea	bx,[Sektor]	;Zeiger an Anfang stellen
@@e:	ret
endp


proc Next_DirEnt
;PE: BX=DirEnt-Zeiger
;PA: BX=vorgerückter DirEnt-Zeiger, ggf. mit neu gelesenem Sektor
;    CY=1: kein weiterer Sektor in Clusterkette
;VR: alle
	call	Check_CDFS
	jnz	CD_Next_DirEnt
	add	bx,32		;Größe von DirEnt
	cmp	bx,ofs SektorEnde
	cmc
	jc	Next_Sektor	;CY durchreichen
@@e:	ret			;noch im gleichen Sektor: OK
endp

proc IsDbcsLeadByte
;Testet DL auf Führungsbyte von Zwei-Byte-Zeichensätzen
;PE: DL=Zeichen oder Führungsbyte
;PA: CY=1: kein Führungsbyte
;VR: -
	push	ds si ax
	 lds	si,[lead_byte_table]
@@l:	 lodsw
	 cmp	ax,1
	 jc	@@e
	 cmp	dl,al
	 jc	@@e		;hoffentlich aufsteigend sortiert!
	 cmp	ah,dl
	 jc	@@l
@@e:	pop	ax si ds
	ret
endp

proc Upcase
;konvertiert AL in Großbuchstaben, auch für >=80h
;für Dateinamensvergleich bei LFN
;Darf nicht für TrailBytes aufgerufen werden!
;Dass es mit LeadBytes geht, dafür sorgt DOS mit einer 1:1-Tabelle;
;DOSLFN vertraut dieser (komischen) Sache nicht, umgeht Upcase auch dann.
;PE: AL=Zeichen
;PA: AL=Zeichen oder Großbuchstabe
;VR: AL
	cmp	al,80h
	jnc	@@a80
	cmp	al,'a'
	jb	@@1
	cmp	al,'z'
	ja	@@1
	bres	al,bit 5
@@1:	ret
@@a80:	push	ds bx
	 lds	bx,[uppercase_table]
	 xlat
	pop	bx ds
	ret
endp

proc Globbing
;FU: Dateinamen-Vergleich mit DOS-typischer Suchmaske
;PE: SI=MASKE (kann unter DOS auch Name sein)
;    DI=NAME  (beide Strings seien korrekte DBCS-Strings)
;    DS,ES=CS
;PA: CY=0 bei Treffer
;VR: AX,DX (DL geht bei HandleDBCS drauf, DH ist "Punkte-Zähler")
	mov	dh,0
@@r:	push	si di
@@l:	 lodsb			;MASKE
	 mov	ah,[di]		;NAME
	 cmp	ah,'.'
	 jne	@@1a
	 inc	dh		;NAME enthält Punkt
@@1a:	 cmp	al,'.'		;MASKE-Punkt (mit Sonderbedeutung)?
	 jne	@@nodot
	 cmp	[by si],0	;folgt Stringende?
	 je	@@dotatend	;Bedeutung: NAME darf keinen Punkt haben
	 cmp	[wo si],'*'	;folgt Stern und Stringende?
	 je	@@dotstar	;Bedeutung: NAME ohne Punkt darf zu Ende sein
@@nodot:
	 or	al,al
	 jz	@@end
	 cmp	al,'*'
	 jz	@@star
@@2:	 inc	di		;erst jetzt NAME-Zeiger vorrücken!
	 or	ah,ah
	 jz	@@f
	 cmp	al,'?'
	 jz	@@qm
	 mov	dl,al
	 call	IsDbcsLeadByte
	 jnc	@@leadbyte	;gibt's nicht mit Upcase!
	 call	UpCase
	 xchg	ah,al
	 call	UpCase
@@trail: cmp	al,ah
	 jz	@@l
@@f:	 stc
@@e:	pop	di si
	ret

@@end:	 cmp	al,ah		;Auch Null? CY=1 wenn ah<>0! (Stringenden
				; fallen nicht zusammen!)
	 jmp	@@e		;okay oder auch nicht!
@@dotatend:
	 or	dh,dh
	 jnz	@@f		;Fehler wenn Punkt enthalten!
	 jmp	@@e0
@@dotstar:
	 or	dh,dh
	 jnz	@@2		;weiter so, wenn Punkt enthalten
@@e0:	 cmp	dh,ah		;NAME muss zu Ende sein für CY=0
	 jmp	@@e
@@qm:			;bei Fragezeichen: ganzes Zeichen(!) übergehen
	 mov	dl,ah
	 call	IsDbcsLeadByte
	 jc	@@l
	 inc	di		;Trailbyte ungesehen übergehen
	 jmp	@@l
@@star:			;bei Stern
	 call	@@r		;Rekursion!
	 jnc	@@e		;wenn der Rest passt, dann ist's OK
	 mov	dl,[di]
	 or	dl,dl
	 jz	@@f		;Wenn NAME zu Ende, dann Fehler
	 inc	di
	 call	IsDbcsLeadByte
	 jc	@@star
	 inc	di
	 jmp	@@star		;mit nächstem Zeichen(!) weitermachen
@@leadbyte:
	 cmp	al,ah
	 jnz	@@f
	 lodsb			;MASKE Trail, darf nichts ungültiges sein!
	 mov	ah,[di]		;NAME Trail
	 inc	di
	 jmp	@@trail		;zum Vergleich der Bytes
endp

proc GlobbingEx
;FU: wie Globbing, testet jedoch auch noch bei Treffer, ob bei
;    File_Flag_DotAtEnd der Name keine Erweiterung hat
;    (Der Trick, im FCB das erste Zeichen der Erweiterung zu testen,
;     klappt bei CDs nicht, oder man müsste da erst nach FCB konvertieren)
;PE: SI=Name (umgekehrt als bei Globbing!!)
;    [CurPathComp]=Maske (immer ohne '.' am Ende)
;    [File_Flags]=Punkt-Merker "File_Flag_DotAtEnd"
;    DS,ES=CS
;PA: Z=1 bei Treffer
;    CY=0 (immer)
;VR: AX,DI(=Maske)
	mov	di,[CurPathComp]
	xchg	si,di
	push	dx
	 call	Globbing
	pop	dx
	xchg	si,di
	db	0D6h		;setalc
	or	al,al		;Umwandlung NC->Z
	ret
endp

proc BE_Uni2Oem			;Big Endian Version
	xchg	ah,al
endp
proc Uni2Oem
;Konvertiert Unicode-Zeichen zu OEM-Zeichen anhand [UniXlat]
;PE: AX=Unicode-Zeichen
;    DI=Speicherziel
;PA: AL=Oem-Zeichen oder zweites Byte bei DBCS (nur wenn IsDbcsLeadByte)
;       (Da TrailByte>=40h interessiert es nicht bei der Sonderzeichenbeh.)
;    AL='_' bei nicht konvertierbarem Zeichen sowie PF_Fail_Uni2Oem gesetzt
;    DI=vorgerücktes Speicherziel
;    Z=1 wenn AL=0
;VR: AX,DI
	cmp	ax,80h
	jc	@@e
	push	cx di
	 mov	di,[UniXlat]
	 or	di,di
	 jz	@@nc		;ohne Tabelle keine Übersetzung
	 mov	cx,80h		;WIRD HIER GEPATCHT!
UniTableLen = wo $-2
	 push	cx
	  repne	scasw		;wenn gefunden bei Index=0, dann CX=7Fh
	 pop	ax
	 jne	@@NoConv
	 sub	ax,cx		;aus Index 0 wird AX=1
	 add	ax,7Fh		;nun Index 0 ist AX=80h
	 or	ah,ah		;>=100h?
	 jz	@@e1		;1-Byte-Zeichen
	 dec	ah		;-100h
	 div	[by HIGH TrailMinLen]	;AH=TrailIndex, AL=LeadIndex
	 push	dx
	  xor	dx,dx
	  xchg	dh,ah		;TrailIndex retten, AH nullsetzen
	  inc	ax		;1-basiert
	  mov	di,[UniXlat]
	  mov	cx,80h		;wird NICHT gepatcht!
	  repne	scasw		;MUSS gefunden werden! Sonst Fehler in .TBL
	  mov	dl,0FFh		;AH ist 0
	  sub	dl,cl		;Index 0 -> Leadbyte 80h
;	  call	IsDbcsLeadByte	;Das sollte es sein!
	  xchg	dx,ax		;AL=LeadByte, AH=TrailIndex
	 pop	dx
;	 jc	@@NoConv	;So geht es nicht!
	 pop	di
	 stosb			;Lead-Byte schreiben
	 xchg	ah,al
	 add	al,[by LOW TrailMinLen]
	 jmp	@@e2
@@nc:
	 or	ah,ah		;>=100h?
	 jz	@@e1		;geht OK, ISO-Latin-1 annehmen
@@NoConv:
	 INT3			;sollte sehr selten vorkommen!
	 mov	al,'_'		;nicht konvertierbares Zeichen
	 BSET	[PFlags],PF_Fail_Uni2Oem
@@e1:	pop	di
@@e2:	pop	cx
@@e:	stosb
	or	al,al
	ret
endp

proc Oem2Uni
;FU: konvertiert jede Art von OEM-Zeichen in Unicode anhand [UniXlat]
;PE: SI=Quellstring
;PA: AX=Unicode-Zeichen
;    SI=vorgerückter Quellstring
;VR: EAX,SI
;BUG: Bei DBCS-Codeseite, aber OHNE LeadByteTable (die Situation beim Start
;     einer chinesischen Win9x/Me-Bootdiskette ohne Aufruf von PDOS95.BAT)
;     ist das Ergebnis von Oem2Uni dasselbe
	xor	eax,eax
	lodsb
	cmp	al,80h
	jc	@@e
	push	ecx dx
	 movzx	ecx,[UniXlat]
	 jcxz	@@e1		;ohne Tabelle keine Übersetzung: ISO-Latin-1
	 mov	dx,ax		;Kopie zum Test, DH=0
	 mov	ax,[ecx+2*eax-100h]
	 cmp	ax,80h		;ein LeadByte-Index? (!Forderung für SBCS!)
	 jnc	@@e1		;nein, OK
;	 call	IsDbcsLeadByte	;DL extra prüfen
	 xchg	ax,dx		;LeadIndex nach DX, AH=0 {LeadByte nach AL}
;	 jc	@@e1		;nein, Chinesisch ist noch nicht aktiv!
	 lodsb			;Trail-Byte "ziehen", AH=0
	 sub	al,[by LOW TrailMinLen]
	 ;jc	@@e1		;führt zu falschem Chinesisch (GB vs. GBK)
	 xchg	dx,ax		;LeadIndex nach AX, TrailByte nach DX
	 dec	ax		;Index 0 (ungenutzt)?
	 js	@@e1		;Wird Unicode FFFF draus!
	 mul	[by HIGH TrailMinLen]	;Index->Adresse des TrailByteVektors
	 add	ax,dx		;TrailIndex dazu
	 mov	ax,[ecx+2*eax+100h]
@@e1:	pop	dx ecx
@@e:	ret
endp

;proc noentry_Copy_Uni_Oem
;FU: Hilfsprogramm, um aus LFN_DirEnt die zerstückelten Unicodes zu holen
;PE: SI=Unicode-Zeichenkette
;    DI=Oem-Zeichenpuffer
;    CX=Anzahl Zeichen +1
;    DS,ES=CS
;    AL<>0 für "Arbeit", AL=0 für "Kurzschluss"
;PA: SI,DI vorgerückt
;    AL=0 wenn Ende erreicht
;    PF_Fail_Uni2Oem gesetzt bei "falschen" Zeichen
;VR; AX,CL
;@@l:	lodsw
;	call	Uni2Oem
;Copy_Uni_Oem:
;	or	al,al
;	loopnz	@@l
;	ret
;endp

;proc Copy_Oem_Uni
;Kopiert CX OEM-Zeichen in Unicode-Puffer
;Zumindest WindowsNT erfordert FFFF-Einträge nach einer terminierenden
;0000, deshalb arbeitet CH als Ende-Merker zum nächsten Aufruf
;PE: SI=Oem-Zeichenkette
;    DI=Unicode-Zeichenpuffer
;    CX=Anzahl Zeichen (CH=FFh: CL=Anzahl FFFF-Zeichen)
;    DS,ES=CS
;PA: SI,DI vorgerückt (SI bis max. hinter die Null)
;VR: AX,CX=0 (normal) oder CX=FFFF (Ende erreicht)
;	xor	ax,ax		;auch als Vorbereitung für FFFF
;	inc	ch
;	jz	@@f		;war FF
;	dec	ch
;@@l:	call	Oem2Uni
;	stosw
;	or	ax,ax
;	loopnz	@@l
;	jne	@@e
;@@f:	dec	ax		;=FFFF
;	rep	stosw
;	dec	cx		;Kennung für weitere
;@@e:	ret
;endp

proc calc_check
;FU: FCB-Prüfsumme berechnen
;PE: SI=FCB-Zeiger
;PA: AH=Prüfsumme
;VR: AX,CX=0
	mov	cx,11
	mov	ah,ch		;mit 0 vorbesetzen
@@l2:	ror	ah,1
	lodsb
	add	ah,al
	loop	@@l2
	ret
endp

proc Locate_DirEnt
;FU: von BX aus gültigen Nicht-LFN-DirEnt aufsuchen, dabei longname auffüllen
;    Gelöschte DirEnts werden einfach übergangen (hier noch kein Unerase)
;PE: BX=Zeiger in Sektorpuffer auf ein DirEnt oder LfnDirEnt
;    DS,ES=CS
;PA: BX=Zeiger auf DirEnt,
;    CY=1 wenn Ende
;    DL=1: LFN gültig,
;    DL=FF: LFN nur wegen Checksumme falsch (aber trotzdem ungültig!)
;    [longname] gefüllt mit langem Dateinamen, Leerstring wenn DL<>1
;    [shortname] gefüllt mit 8.3-Namen
;    [longpos_s] Position Startsektor LFN
;    [longpos_a] Zeiger BX in Sektor-Puffer
;    [PFlags]&PF_Fail_Uni2Oem gelöscht oder gesetzt
;VR: alle
;in der Schleife: DL=Sequenz-Nummer, DH=Prüfsumme

	mov	dl,0
@@l:	mov	al,[(TLfnDirEnt bx).count]
	cmp	al,1
	jc	@@e		;Ende!
	cmp	al,0E5h
	je	@@3a		;nächste Runde, LFN-in-Aufbau löschen
	cmp	[(TLfnDirEnt bx).attr],0Fh	;LFN-Kennung?
	jne	@@exk		;BX ist OK, zeigt auf normalen DirEnt
	test	al,40h		;"Letztes" Stückel LFN?
	jz	@@2
	mov	dl,al
	and	dl,3Fh		;Sequenz-Nummer
	jz	@@3		;ungültig, 0
	cmp	dl,20
	ja	@@3		;ungültig, >20
	mov	eax,[CurSector]
	mov	[longpos_s],eax	;(mögliche) Adresse sichern für's Löschen
	mov	[longpos_a],bx
	mov	dh,[(TLfnDirEnt bx).check]	;Prüfsumme
@@2a:
	mov	al,13*2
	mul	dl		;Adresse ermitteln
	lea	di,[longname-13*2]
	add	di,ax		;AL<>0 für Copy_Uni_Oem!
	lea	si,[(TLfnDirEnt bx).name1]
	push	di
	 mov	cx,5
	 rep	movsw
	 add	si,3
	 mov	cl,6
	 rep	movsw
	 lodsw			;add si,2
	 movsw
	 movsw
	pop	di
	xor	ax,ax
	mov	cl,13
	repne	scasw		;Unicode-Null suchen
	jz	@@t		;wenn Namensteil <13 Zeichen
	test	[(TLfnDirEnt bx).count],40h ;"Letztes" Stück?
	jz	@@3		;nein
	xor	ax,ax
	stosw			;terminieren!
	jmp	@@3
@@t:
	test	[(TLfnDirEnt bx).count],40h ;Muss auch "letztes" Stück sein!
	jz	@@3a		;Ungültig, DirEnt verwerfen
	jmp	@@3		;Terminierung nicht (mehr) erforderlich
@@2:
	cmp	dl,2		;Sequenz noch Null oder bereits 1?
	jc	@@3a		;ungültiger LFN
	dec	dl
	and	al,3Fh
	cmp	dl,al
	jne	@@3a		;Reihenfolge falsch
	cmp	dh,[(TLfnDirEnt bx).check]	;Prüfsumme gleich?
	je	@@2a		;Prüfsumme gleich
@@3a:
	mov	dl,0
@@3:
	push	dx
	 call	Next_DirEnt
	pop	dx
	jnc	@@l
@@e:	ret
@@exk:	;BX zeigt auf kurzen Dateinamen, Überprüfung von Checksum
	mov	di,ofs longname
	cmp	dl,1
	jnz	@@nolong
;	test	[ctrl],CTRL_ChkLnk
;	jz	@@e2		;Verbindung NICHT prüfen
	mov	si,bx
	call	calc_check	;SI->AL
	cmp	ah,dh
	jz	@@e2
	mov	dl,0FFh
@@nolong:
	mov	[wo di],0	;auch: Eintrag löschen
@@e2:	BRES	[PFlags],PF_Fail_Uni2Oem	;Für den Fall: kein LFN
	mov	si,di
@@l2:	lodsw
	call	Uni2Oem		;Es wird bestenfalls kürzer!
	jnz	@@l2
	mov	di,ofs shortname
	call	Copy_FCB_8P3
	clc
	ret
endp

proc noentry_StrIComp	;StringCompare mit UpCase
;PE: SI=Maske
;    DI=Name
;    CLD
;PA: CY=1: String DI größer als String SI
;    Z=0: Strings ungleich
;    Z=1: Strings gleich, AX=0
;    SI und DI zeigen hinter das erste ungleiche Zeichen
;    oder hinter die Null(en)
;VR: SI,DI,AX,Flags
@@2:
	or	ax,ax
	jz	@@e		;Beide Strings zu Ende
StrIComp:
	lodsb
	call	UpCase
	xchg	ah,al
	mov	al,[di]
	inc	di
	call	UpCase
	cmp	ah,al
	jz	@@2
@@e:
	ret
endp

proc Match_Attr
;Prüft ob Attribut mit [SearchAttr] passt
;PE: AL=Attribut
;PA: Z=1: passt
;    CY=0 (immer)
;VR: AL
	test	al,[by LOW  SearchAttr]
	jnz	@@e		;paßt leider nicht
Match_MM_Attr:			;Einstieg: Nur Must-Match-Attribut testen
	not	al
	test	al,[by HIGH SearchAttr]
@@e:	ret
endp

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;++ Neue, "objektorientierte" FindFirst/FindNext-Routine ++
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;Match&Stop-Routinen mit folgenden Parametern:
;PE: BX=DirEnt-Zeiger für aktuelles DirEnt
;    [CurSector]=Aktuelle Sektornummer
;    DX=User-Daten (frei verwendbar)
;PA: CY=1: Such-Ende einleiten (AX=Fehlercode 9912h)
;    Z=1: Treffer
;    DX=User-Daten (beim nächsten Aufruf wieder aktuell)
;VR: AX,CX,DX,SI,DI (BX darf außer bei CY=1 NICHT verändert werden!)

proc Match_CD_LFN_Proc
	mov	ax,ofs CD_LongName
	jmp	Match_CD_AX_Proc
endp

proc Match_CD_SFN_Proc
	mov	ax,ofs CD_Shortname
Match_CD_AX_Proc:
	cmp	[(TCD_DirEnt bx).r],SIZE TCD_DirEnt
	jc	@@e
	call	CD_CheckRootDot
	jnc	@@e		;mit Z=0
	call	ax
Match_Current:
	mov	di,[CurPathComp]
	call	StrIComp
	clc			;nie Fehler:-)
@@e:	ret
endp

proc Check_Virtual_Remove
;PE: [CurSector]:BX=Verzeichniseintrag
;PA: Z=1 wenn Eintrag virtuell gelöscht
;VR: AX,SI,DI
	cmp	[FuncNum],56h
	jne	@@e
	mov	di,[SearchAttr]
	cmp	di,1
	jc	@@e		;mit NZ
	add	di,6
	mov	si,ofs CurSector
	cmpsd
	jne	@@e
	mov	ax,bx
	scasw
@@e:	ret
endp

proc Match_LFN_Proc
;FU: Sucht für FAT lange UND kurze Namen
;PE: BX=Anfang irgend eines Verzeichniseintrags (LFN, SFN, gelöscht)
;    [CurPathComp]=Zu vergleichende Pfad-Komponente
;    [xxx]=(bei MOVE) zu ignorierender Verzeichnis-Eintrag
;PA: CY=1: Fehler: BX ist kein Verzeichniseintrag
;    BX=Anfang eines SFN-Verzeichniseintrags
;    weitere Ergebnisparameter wie bei Locate_DirEnt
;    Z=1: Name passt
;    Z=0: Verzeichniseintrag ist Label oder passt nicht
	call	Locate_DirEnt
	jc	@@e		;raus bei Fehler
;Bugfix 11/02: Volume Labels nicht verfolgen!
	test	[(TDirEnt bx).attr],8
	jnz	@@e		;raus bei Label
;Fix 12/02: Zu löschender Eintrag bei lfn_move übergehen
	call	Check_Virtual_Remove
	jnz	@@1
	or	ax,ax		;nie Null, daher Z=0
@@e:	ret
@@1:
;	cmp	dl,1		;LFN vorgefunden?
;	jne	@@cmpshort	;unnötig weil longname[0]=0 ohne LFN
	mov	si,ofs Longname
	call	Match_Current
	jz	@@e		;Treffer!
@@cmpshort:
	mov	si,ofs ShortName
	jmp	Match_Current
endp

proc Glob_LFN_Proc
;Globbing für FAT lange UND kurze Namen (hier gleich Win95-Verhalten)
	call	Locate_DirEnt
	jc	@@e		;raus bei Fehler
	mov	al,[(TDirEnt bx).attr]
	call	Match_Attr
	jnz	@@e		;raus, kein Treffer! (CY=0)
	cmp	dl,1
	jne	@@cmpshort
	mov	si,ofs LongName
	call	GlobbingEx	;LFN-Suche
	jz	@@e		;Treffer, raus!
@@cmpshort:
	mov	si,ofs ShortName
	call	GlobbingEx	;SFN-Suche mit LFN-Syntax
@@e:	ret
endp

proc DirScan pascal
;Allgemeine Routine zum "Scannen" eines Verzeichnisses
;dank Pointer zur Match&Stop-Routine
;PE: [CurSector]=Startsektor des Verzeichnisses
;    BX=MatchProc-Zeiger (bei NextDirScan muss dieser in [MatchPtr] stehen!)
;PA: CY=1: Nicht gefunden
;VR: AX,BX,CX,DX,SI,DI
	mov	[MatchPtr],bx
	call	ReadSec
	jc	@@e
	mov	bx,ofs Sektor	;der DirEnt-Zeiger
@@l:	call	[MatchPtr]
	jbe	@@e		;bei CY=1 (Fehler) oder Z=1 (gefunden)
NextDirScan:
	call	Next_DirEnt
	jnc	@@l
@@e:	ret
endp

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

proc Is_FCB_Equal
;FU: Testet momentanen DirEnt mit Gleichheit zu FCB_Name
;PE: BX=DirEnt-Zeiger (mit 1. Byte =05 für E5)
;    [FCB_Name] mit Vergleichsstring gefüllt
;PA: Z=1 wenn gleich
;VR: AL,DI,CX (CH=0)
	lea	di,[FCB_Name]
Is_FCB_Equal_DI:
	mov	si,bx
	xor	cx,cx
	lodsb			;aus Festplatten-Sektor entnehmen
	call	Change_First_FCB_Byte
	scasb
	jne	@@e
	mov	cl,5		;CH ist schon null
	repe	cmpsw		;Die restlichen 10 Bytes brauchen kein Extra
@@e:	ret
endp


;**** Hilfsprogramme für TRUENAME ****

proc IsEnd		;Testet AL auf Ende von Pfad-Komponente
	or	al,al
	jz	@@e
IsBS:	cmp	al,'\'
	jz	@@e
	cmp	al,'/'
@@e:	ret
endp

proc IsInvalidLfnChar
	push	di cx
	 mov	di,ofs Invalid_Lfn_Chars
	 mov	cx,8
	 repne	scasb
	pop	cx di
	ret
endp

proc HandleDBCS
;PE: AL=Zeichen, AH Bit 7 = Trailbyte-Flag
;PA: CY=1 wenn Trailbyte
;    NO RETURN mit AX=2 wenn falsches Trailbyte <40h (Notbremse)
;VR: DL(=AL), AH Bit 7
	shl	ah,1
	mov	dl,al
	jc	@@1
	call	IsDbcsLeadByte	;NC wenn's so ist!
	cmc
	rcr	ah,1		;Hinein das Bit! CY=0
	ret			;NC wenn kein Trail-Byte
@@1:	cmp	al,40h
	jc	SetErr2		;Ungültiges Trail-Byte (Parsen nicht möglich)!
				;Ist 3, wenn \/ folgt? (hmpf)
	shr	ah,1		;0 einschieben
	stc
	ret
endp

proc noentry_SwapSlashes
;FU: String parsen und DBCS-sicher \ zu / machen, 05h zurück zu E5h machen
;PE: DS:SI=Zeiger auf String
;    DH=0: bei Leerstring Ergebnis-SI auf letztes Zeichen, sonst auf '\0'
;    DH=1: Ergebnis-SI stets auf '\0'
;PA: SI vorgerückt entsprechend DH
;    Zeichen im Puffer entsprechend geändert
;    NO RETURN bei falschem Trail-Byte
;VR: SI,DX,AX (AH Bit 7 =0, andere Bits unverändert)
@@pal:	call	HandleDBCS
	jc	@@paf		;bei Trailbyte nicht auf \ testen!
	inc	dh		;1 Zeichen mitzählen
	cmp	al,'\'
	jne	@@paf
	mov	[by si-1],'/'	;auf Unix drehen, damit es kein Trail-Byte ist
SwapSlashes:
	cmp	[by si],05h
	jne	@@paf
	mov	[by si],0E5h	;Unsinn von DOS' TRUENAME rückgängig machen
@@paf:	lodsb
	or	al,al
	jnz	@@pal
	sub	dh,1		;CY setzen lassen, wenn Wurzelverzeichnis
	sbb	si,1		;auf die Null oder auf letzten /
	ret
endp

proc GetSubstRoot
;Ermittelt Startverzeichnis für geSUBSTetes Laufwerk oder schlicht X:\
;PE: FS:SI=Dateiname
;    DS=ES:DI=Puffer
;PA: Puffer gefüllt
;    [subst_drive]=DL=virtuelles Laufwerk, DH=0
;    BX=DI=Zeiger auf letzten '\' oder #0 im Puffer
;    SI um 2 vorgerückt, wenn Laufwerk angegeben
;    AX=Pfadquelle, Puffer+3 (normalerweise), größer bei UNC-Pfad
;    NO RETURN bei Fehler
;    [PFlags]???
;VR: EAX,BX,CX,DX,SI,DI
;N:  DOS' TRUENAME liefert bei IFS-Laufwerken (CDROM, Netzwerk) einen
;    Netzwerkpfad. Da IFS-Laufwerke nicht SUBST-bar sind, wird in diesem Fall
;    einfach 'Laufwerksbuchstabe:\' zurückgegeben
;    Der Pfad C:\DEV\NUL wird zu C:/NUL (mit Forward-Slash!) aufgelöst.
	push	di
	 mov	ax,[fs:si]
	 or	al,al
	 jz	SetErr3		;Gänzlich leer ist ein FEHLER!
	 cmp	ah,':'		;Laufwerk gegeben?
	 je	@@take_drive
	 mov	ah,19h		;aktuelles Laufwerk beschaffen
	 call	CallOld
	 add	al,'A'
	 jmp	@@2
@@take_drive:
	 segfs	movsw		;Laufwerk übertragen, SI+=2, DI+=2
	 call	upcase
@@2:	 movzx	dx,al		;DH=0
	 mov	ax,[fs:si]
	 call	IsBS
	 jnz	@@normal
	 mov	al,ah
	 call	IsBS
	 jnz	@@normal
	 mov	dl,0		;Kennung UNC-Laufwerk!
	pop	di
	stc
	jmp	@@e
@@normal:
	 mov	ax,'\'		;TRUENAME kein Backslash anhängen lassen
	 stosw
	pop	di
	push	si
	 mov	si,di		;Darf lt. RBIL auch übereinander liegen
	 mov	ah,60h
	 call	CallOldAndThrow	;So funktioniert's auch geSUBSTet
	 mov	ax,'\\'		;Netzwerkpfad? (Kann auch MSCDEX sein!)
	 scasw
	 jne	@@3
	 mov	eax,'\:?'	;'?' wird ersetzt
	 mov	al,dl
	 mov	[si],eax
	 call	strlenp1	;Ende von "\\S.\A." suchen
	 dec	di
@@3:	 inc	di
	 push	di dx
	  mov	dh,-3		;Kode in SwapSlashes arbeiten lassen!
	  call	SwapSlashes	;Terminierende Null oder Backslash suchen
	 pop	dx ax
	 mov	di,si
	pop	si
@@e:	mov     bx,di
	mov	[subst_drive],dl
	ret
endp

proc Truename
;DE: Kopiert DOS-Dateinamen in <longbuffer> und macht dabei ein TRUENAME:
;    * Prüft auf ungültige LFN-Zeichen ab
;    * holt KEINE Laufwerksdaten
;    * ermittelt ggf. das aktuelle Verzeichnis
;    * löst ..-Referenzen auf
;    * schneidet nachlaufende Leerzeichen von Komponenten ab
;    * schneidet nachlaufende Punkte ab, außer bei "*" in Komponente
;    * * und ? sind ungültige Zeichen vor \
;    * " \" wirkt wie "."
;    * wandelt alle '\' in '/' (wegen DBCS)
;    * fasst mehrere '\' '/' zusammen zu einem
;    * wandelt E5h NICHT zu 05h, wie das DOS' TRUENAME (AH=60h) tut! (brrr)
;BUG: interpretiert Netzwerkpfad "\\maschine\freigabe\pfadname" nicht
;     Der Pfad C:\DEV\NUL wird ohne PF_Lfn_Input zu C:/NUL aufgelöst (OK),
;     nicht aber bei gesetztem PF_Lfn_Input.
;
;PE: FS:SI=Dateiname, CLD
;    [PFlags]:PF_LFN_Input=Schalter, ob Dateiname "lang" oder "kurz"
;PA: <longbuffer> gefüllt mit TRUENAME entspr. Int21/7160/CX=0
;    NO RETURN bei Fehler
;    [subst_root]=Anzahl zu übergehender Zeichen für SUBST
;    [subst_drive]=virtuelles Laufwerk, =LongBuffer[0] wenn kein SUBST
;VR: EAX,BX,CX,DX,SI,DI (=alle)
	mov	di,ofs longbuffer
	BTST	[PFlags],PF_LFN_Input
	jnz	@@lfn_truename
	push	si di
	 call	GetSubstRoot
	pop	di si
@@unc:	push	ax ds
	 LD	ds,fs
	 mov	ah,60h
	 call	CallOld		;DOS noch einmal werkeln lassen (kein THROW)
	pop	ds si
	jc	Throw
	cmp	[by di],'\'	;Netzwerkpfad oder CDROM? (Kein Laufwerk?)
	jne	@@sfn1
	mov	al,dl		;nicht SUBSTbar, Laufwerk übernehmen
	or	al,al
	jz	@@sfn1		;Netzwerkpfad, nicht behandeln!
	push	di
	 mov	al,dl
	 stosb
	 mov	ax,'\:'
	 stosw
	 call	strcpy		;UNC-Netzwerkpfad killen - Rest vorkopieren
	pop	di
@@sfn1:	mov	si,di
	call	SwapSlashes
	jmp	@@ee
@@lfn_truename:
	call	GetSubstRoot	;liefert DH=0
	jc	@@unc
@@l0:	inc	dh
	segfs	lodsb		;DBCS-sicher: Führungs-Backslashes weg!
	call	IsBS
	jz	@@l0		;kein aktuelles Verzeichnis holen!
	dec	si		;also auf ersten Nicht-Backslash
	dec	dh
	jnz	@@0
	mov	al,'/'
	stosb
	xchg	si,di
	sub	dl,'@'		;für GetCurDir: 'A:'=1 usw.
	mov	ah,47h
	call	CallOldAndThrow	;Aktuelles Vrz. nach <longbuffer>
	call	SwapSlashes	;SI ans Ende rücken, dabei \ zu / machen
	xchg	di,si
@@0:	movzx	ax,[by fs:si]
;AH Bit 0 = ?*-Speicher (File_Flag_Wildcards) - um falschen Pfad auszuwerfen
;   Bit 2 = *-Speicher (File_Flag_Has_Star) - um Punkt rechts stehen zu lassen
;   Bit 3 = .-Speicher (File_Flag_Has_Dot) - um Leer-Pfade ("/ /") zu killen
;   Bit 7 = LeadByte-Speicher - um 5Ch ('\') als TrailByte durchzulassen
@@new0:	or	al,al		;Folgen Zeichen? (Mit Sicherheit kein \/)
	jnz	@@new_name	;nein, so stehen lassen
	cmp	di,ofs longbuffer+2	;Root-Backslash?
	jne	@@e
	inc	di		;stehen lassen!
@@e:	stosb
	;und als letztes alles vor dem letzten Backslash upcasen (eigentlich)
@@ee:	xchg	ax,bx
	sub	ax,ofs longbuffer+2
	mov	[subst_root],ax
	ret

@@scandot:
;Name, der mit '.' anfängt, könnte nur aus Punkten bestehen, und ist
;dann aktuelles, übergeordnetes usw. Verzeichnis!
	mov	cx,0		;Punkte-Zähler
	push	si		;Startzeiger retten
@@scn:	 inc	cx
	 segfs	lodsb
	 cmp	al,'.'
	 je	@@scn
	 call	IsEnd
	 jz	@@rmb
	 mov	al,'.'
	pop	si
	jmp	@@setd		;ist ein "normaler" Name, von vorn
;Ende einer Punktkette erreicht, in CX=Anzahl Punkte

@@rmb:	pop	dx		;Zeiger verwerfen
@@rl:	dec	di		;CX Ebenen aufsteigen, AL enthält '\' oder 0
	cmp	[by di],'/'	;DBCS-sicher
	jne	@@rl
	cmp	di,bx		;Der SUBST-Pfadanteil ist "heilig"!
	loopnz	@@rl
	jcxz	@@new0		;wenn CX<>0 dann ging es nicht "hoch genug"
	jmp	SetErr3

@@new_name:
	BTST	ah,File_Flag_Wildcards
	jnz	@@err2		;Vor / sind keine ?* erlaubt!
	mov	al,'/'		;ja, jetzt Pfad-Trenner setzen
	stosb		;Überlauf-Überwachung unnötig, Reserve am Puffer-Ende
@@l1:	segfs	lodsb
	call	IsBS
	jz	@@l1		;mehrfache Slash/Backslash zusammenfassen
	and	ax,0FFh		;AH: wenigstens das Has_Dot löschen
	jz	@@e
	cmp	al,'.'
	jz	@@scandot
	jmp	@@be
	;Klappt nicht mit Unterprogramm wegen neuer Globbing-Routine
@@l3a:	cmp	al,'?'		;Prüfbits in AH setzen
	jne	@@3b
	BSET	ah,File_Flag_Wildcards
@@3b:	cmp	al,'.'
	jne	@@3a
@@setd:	BSET	ah,File_Flag_Has_Dot
@@3a:	cmp	al,'*'
	jne	@@l3
	dec	si
@@3al:	inc	si
	cmp	[fs:si],al	;Mehrere Sterne zusammenfassen
	je	@@3al
	BSET	ah,File_Flag_Has_Star or File_Flag_Wildcards
@@l3:	;normaler Name
	cmp	di,ofs longbuffer_end-1
@@err2:	jnc	SetErr2
	stosb

	segfs	lodsb
@@be:	call	HandleDBCS
	jc	@@l3		;bei Trail-Byte unverändert speichern
	call	IsInvalidLfnChar
	jnz	@@l3a		;erlaubt, kopieren
	call	IsEnd
	clc
	jnz	@@err2		;unerlaubtes Zeichen = "Datei nicht gefunden"
	mov	dh,al		;Pfad-Trenner oder Ende nach DH retten
@@l2:	;Ende erreicht, rückwärts Punkte und Leerzeichen löschen
	dec	di
	mov	al,[di]
	cmp	al,' '
	je	@@l2		;Leerzeichen sofort weg!
	cmp	al,'/'
	je	@@sla		;nur Leerzeichen: noch kein Fehler!
	cmp	al,'.'
	jne	@@t2e		;Nicht löschen, wenn * enthalten, zusammenfas.
	BTST	ah,File_Flag_Has_Star	;Stern enthalten?
	jz	@@l2		;nein, der Punkt muss weg
	cmp	[di-1],al	;Punkt davor?
	je	@@l2		;dann muss der Punkt doch noch weg
@@t2e:	inc	di
@@t2f:	mov	al,dh
	jmp	@@new0

@@sla:	BTST	ah,File_Flag_Has_Dot	;Irgendwo ein Punkt gewesen?
	jz	@@t2f		;der Slash bleibt! (Nicht auf @@t2e gehen.)
	jmp	SetErr5		;Das ist ein ganz besonderer Fall!
			;An dieser Stelle hat Win95a noch richtige Bugs!
			;Die zu emulieren habe ich keine Lust.

endp

proc start_stuff
;das langweilige TRUENAME und Laufwerksparameter beschaffen...
;PE: FS:SI=Dateiname
;    [PFlags]:PF_LFN_Input=Schalter, ob Dateiname "lang" oder "kurz"
;PA: NO RETURN bei Fehler
;    CY=0 && Z=1: OK, Wurzelverzeichnis
;    CY=0 && Z=0: OK, Unterverzeichnis, TRUENAME steht in longbuffer
;    SI=longbuffer+3
;    DI=shortbuffer+3 (Laufwerk:\ schon hineinkopiert)
;    [CurSector] gefüllt mit Startsektor des Hauptverzeichnisses
	call	Truename		;THROWt bei Fehler
	mov	al,[longbuffer]
	sub	al,40h
	call	GetDrvParams		;egal was dabei rauskommt!
	mov	si,ofs longbuffer
	mov	di,ofs shortbuffer
	movsd				;Laufwerk:\NULL
	dec	si			;bleiben im Ziel stehen
	dec	di
Set_Root:
;"Current"-Sektor(en) auf "Hauptverzeichnis" setzen, für MakeLongName
	;call	reset_cache_ptr
	mov	[fastopen_ptr],ofs fastopen_buf
	BSET	[PFlags],PF_Follow	;immer mit Verfolgung ansetzen
	call	_set_root_sector
	call	Check_CDFS
	jz	@@nocd
	push	si di
	 lea	si,[Medium.jol.joltree.rootdir]
	 lea	di,[Search.jol.j.sect]
	 movsd			;Startsektor
	 movsw			;Länge
	 lea	si,[Medium.jol.isotree.rootdir]
	 lea	di,[Search.jol.i.sect]
	 movsd			;Startsektor
	 movsw			;Länge
	pop	di si
@@nocd:	cmp	[by si],0		;CY immer 0, Z setzen
@@e:	ret
endp

proc same_stuff
;für file_locate und path_locate
;Funktioniert auch im Rückfallmodus; da wird einfach der Dateiname in FCB_Name
;nach ShortBuffer (DI) kopiert, d.h. da wird ein echter 8.3-Name draus
;PE: PF_Follow=0: Cache umgehen und Sektor nicht "verfolgen"
;    (führt zum Setzen von [Longpos_s]:[Longpos_a])
;PA: CY=1 wenn nicht gefunden, AL=2 oder 3 je nach Funktionsklasse
;    DI=Short-Buffer-Zeiger vorgerückt (nur wenn nicht NIL gewesen)
	push	si di
	  cmp	[DriveType],1
	  jc	@@fallback
	  test	[PFlags],PF_Follow
	  jz	@@nocache		;ohne Follow nie in Cache gehen!
	  call	find_in_cache
	  jz	@@shortcut
@@nocache:
	  test	[File_Flags],File_Flag_NDevice
	  jz	@@fallback		;dirty hack 11/01
	  call	Check_CDFS
	  jnz	@@cdfs
	  mov	bx,ofs Match_LFN_Proc
	  call	DirScan
	  jc	@@e
	  test	[PFlags],PF_Follow
	  jz	@@shortcut
	  call	Pick_Sector_From_DirEnt
	  jmp	@@catshort
@@e:	pop	di si
	mov	al,[FuncNum]
	cmp	al,3Ch			;LFN-Funktion mit Verzeichnissen?
	mov	al,3
	jc	@@e1
	mov	al,2			;"file not found"
	BT	[wo PFlags],1		;PF_Follow
	adc	al,0			;modify to "path not found"
	stc
@@e1:	ret
@@fallback:
	  mov	si,ofs FCB_Name
	  mov	di,ofs ShortName
	  call	Copy_FCB_8P3_from_SI
	  mov	[LongName],0		;nie langen Namen liefern
	  jmp	@@shortcut

@@cdfs:
	  test	[File_Flags],(File_Flag_Is_LFN or File_Flag_Char_High)
	  jz	@@test_short
	  mov	bx,ofs Match_CD_LFN_Proc
	  call	CD_Ping_DirScan
	  jnc	@@catshort		;gefunden!
@@test_short:
	  mov	bx,ofs Match_CD_SFN_Proc
	  call	CD_Pong_DirScan
	  jc	@@e			;Konsistenzfehler
@@catshort:
	  call	put_to_cache
@@shortcut:
	 pop	di
	 or	di,di
	 jz	@@skip_copy		;bei Ziel=NIL nicht kopieren!
	 mov	si,ofs ShortName
	 call	strcpy			;gewonnenen Namen nach DI (ShortBuffer)
	 dec	di
@@skip_copy:
	pop	si
	ret
endp

proc file_locate
;verfolgt angegebenen Pfad bis zum Schluss
;Wird ziemlich selten gebraucht, weil diese Funktion nicht zur Manipulation
;des DirEnts zu gebrauchen ist! (lfn_chdir, lfn_attr, lfn_shortname)
;PE: FS:SI=Dateiname
;    [PFlags]:PF_LFN_Input=Schalter, ob Dateiname "lang" oder "kurz"
;PA: NO RETURN bei Fehler (THROW zu "Pfad nicht gefunden")
;    [ShortBuffer] gefüllt mit 8.3-Pfad
;A:  [Longpos_s]:[Longpos_a] sind nicht immer gesetzt!
	call	start_stuff
	jz	@@e			;ist root (und OK)

@@l:	call	Gen_Alias
	call	same_stuff
	jc	SetError
	call	MakeBSlash
	jnz	@@l
	or	al,al			;Z=0
@@e:	ret
endp

proc path_locate
;verfolgt angegebenen Pfad, aber nicht den Dateinamen
;PE: FS:SI=Dateiname
;    [PFlags]:PF_LFN_Input=Schalter, ob Dateiname "lang" oder "kurz"
;PA: NO RETURN bei Fehler (z.B. Pfad nicht gefunden)
;    [ShortBuffer] gefüllt mit 8.3-Pfad und abschließendem Backslash
;    [CurPathComp]=Zeiger auf Dateiname
;BUG: Check_Device ist Sache von TRUENAME!
	call	start_stuff
	jz	@@root			;nur root ist hier auch Fehler!

@@l:	call	Gen_Alias
	BTST	[PFlags],PF_Slash
	jz	@@ckdev			;fertig gefunden, in [CurPathComp]...
	call	same_stuff
	jc	SetError
	call	MakeBSlash
	jmp	@@l
@@ckdev:
	call	Check_Device
@@e:	ret
@@root:	cmp	[FuncNum],4Eh
	;"Keine weiteren Dateien" meldet DOS bei FindFirst auf "c:\"
	mov	al,05h		;ansonsten "Zugriff verweigert"
	jne	@@ex
	mov	al,12h
@@ex:	jmp	SetError
endp

proc MakeBSlash
;FU: Backslash und \0 an ES:DI anhängen, wenn [PFlags]:PF_Slash
;    gesetzt ist
;PE: DI=Zielpuffer
;    [PFlags]:PF_Slash
;    SI=Quellpuffer (um auf 0 zu testen)
;PA: SI und DI inkrementiert falls Flag gesetzt
;    AX=005Ch
;VR: AX, DI
	mov	ax,'\'			;Backslash und Null
	BTST	[PFlags],PF_Slash
	jz	@@1
	stosw
	dec	di
	inc	si
@@1:	cmp	[si],ah			;Null? (CY=0)
	ret
endp

proc dirent_locate
;ermittelt DirEnt-Zeiger bx für Datei
;PA: NO RETURN bei Fehler mit "path_locate"
;    CY=1 wenn Datei nicht gefunden (AL=2) oder AL=3 bei Verzeichnis-Fkt.
;    [ShortBuffer] gefüllt mit 8.3-Pfad und abschließendem Backslash (?)
;    BX=DirEnt-Zeiger
;    [longpos_s]+[longpos_a]=LFN-DirEnt-Zeiger (auch bei CY=1 verändert!)
;N:  Ein nachlaufender Backslash wird hier nicht angesetzt!
	call	path_locate
File_DirEnt_Locate:
	BTST	[File_Flags],File_Flag_Wildcards
	jnz	SetErr3			;an dieser Stelle nicht erlaubt
	BRES	[PFlags],PF_Follow
	push	di
	 call	same_stuff		;jetzt ohne Cache!
	 jc	@@e
	 call	MakeBSlash
	 cmp	[DriveType],0		;FallBack-Modus?
	 jne	@@e
	 BTST	[File_Flags],File_Flag_Is_LFN
	 jnz	SetErr5			;im Fallback-Modus unzulässig!
	 BRES	[File_Flags],File_Flag_LowerCase	;kein LFN erzeugen!
	 mov	ax,4300h
	 call	SFN_CallOld		;Existenz-Test
	 jnc	@@e
	pop	di			;Zeiger stehen lassen!
	ret

@@e:	pop	cx
	ret
endp

proc MakeLongName
;Erzeugt "langen" Dateinamen, nur für lfn_pwd und lfn_longname
;PE: SI=Zeiger auf kurzen Dateinamen (LW-Parameter schon geladen)
;    FS:DI=Ziel (langer Dateiname im Anwender-Adressraum)
;    [subst_root]=Zeichenzahl für SUBST
;PA: CY=1: da ging was schief!
;VR: alle, [subst_root]
	cmp	[by si],0	;Sonderfall für lfn_pwd und lfn_longname
	jz	@@ez		;nichts zu tun im Wurzelverzeichnis!
	add	[subst_root],si
	call	Set_Root

@@l:	call	Gen_Alias
	push	di
	 xor	di,di		;hier: keinen "kurzen Pfad" bauen
	 call	same_stuff
	 jc	SetError
	pop	di
	cmp	si,[subst_root]	;noch vor den auszugebenden Komponenten?
	jbe	@@1
	push	fs di
	push	ds
	mov	bx,ofs LongName
	cmp	[by bx],1
	jnc	@@havelong
	mov	bx,ofs ShortName	;Zeiger auf kurzen Namen
@@havelong:
	push	bx
	call	fstrcpy		;Name kopieren
	add	di,ax
	BTST	[PFlags],PF_Slash
	jz	@@1
	mov	[by fs:di],'\'
	inc	di
@@1:	BTST	[PFlags],PF_Slash
	jz	@@2
	inc	si
@@2:	cmp	[by si],0
	jnz	@@l
@@ez:	mov	[by fs:di],0
@@e:	ret
endp

verteiler:	DVT	39h,lfn_mkdir	;w DS:DX
		DVT	3Ah,lfn_rmdir	;w DS:DX
		DVT	3Bh,lfn_chdir	;r DS:DX
		DVT	41h,lfn_unlink	;w DS:DX       CX SI
		DVT	43h,lfn_attr	;? DS:DX BL    CX SI DI
		DVT	47h,lfn_pwd	;r DS:SI DL
		DVT	4Eh,lfn_ffirst	;r DS:DX ES:DI CX SI
		DVT	4Fh,lfn_fnext	;r BX    ES:DI    SI
		DVT	56h,lfn_move	;w DS:DX ES:DI
		DVT	60h,lfn_name	;r DS:SI ES:DI CX
		DVT	6Ch,lfn_creat	;? DS:SI    BX CX DX DI
		DVT    0A0h,lfn_volinfo	;- DS:DX ES:DI BX CX DX
		DVT    0A1h,lfn_fclose	;r BX
		DVT    0A7h,lfn_timeconv;- DS:SI       BX CX DX
		DVT    0A8h,lfn_genshort;- DS:SI ES:DI DX
		DVT    0AAh,lfn_subst	;- DS:DX BH(=0,1,2) BL(=LW)
		db	0
		_CASE

;Programm-Verteiler-Tabelle für <lfn_attr>
pvt_attr	dw	ofs attr_getattr	;via DOS
		dw	ofs attr_setattr	;via DOS
		dw	ofs attr_getphyssize
		dw	ofs attr_settimem
		dw	ofs attr_gettimem
		dw	ofs attr_settimea
		dw	ofs attr_gettimea
		dw	ofs attr_settimec
		dw	ofs attr_gettimec
;Programm-Verteiler-Tabelle für <lfn_attr bei CDFS, nur Lesezugriffe>
cd_pvt_attr	dw	ofs cd_attr_getattr	;via DOS
		dw	ofs cd_attr_getphyssize
		dw	ofs cd_attr_gettimem
		dw	ofs cd_attr_gettimea	;liefert 0
		dw	ofs cd_attr_gettimec	;liefert 0-0-0

;Alle Verteiler-Funktionen werden mit Stapelrahmen sowie veränderten
;Registern AX=7100h, DS=ES=CS, BP=Rahmenzeiger und DI=?? aufgerufen.
;Bei Aufruf ist das Richtungsflag gelöscht (aufsteigend)
;Sie müssen in AX den Rückgabewert liefern und ansonsten auf den Stapel
;zugeifen.

proc lfn_mkdir
	;1. Finden des LFN-Eintrags
	mov	si,dx
	call	path_locate
	call	Check_CDFS_Throw	;auf CD schlecht möglich:-)
	call	File_DirEnt_Locate
	jnc	SetErr5		;Existiert bereits: Fehler!
	;2. geeigneten, nicht bereits vorhandenen FCB-Namen ermitteln
	call	build_unique_fcb_name_start_1
	call	SFN_AL_CallOld
	jc	SetError
	call	InvalSector
	;sollte an Cache angehangen werden! (hier noch nicht)
	call	ResetDrv
	;4. LFN dazubasteln
	call	install_long_filename
	jc	@@del
	ret
@@del:	and	[DriveType],not DT_Dirty ;Sektor doch nicht schreiben
	mov	ah,3Ah
	call	SFN_CallOld	;Verzeichnis löschen
	jmp	SetErr5
endp

proc lfn_chdir
	mov	si,dx		;noch unzerstört...
	call	file_locate
	jz	@@1		;Root
	BTST	[PFlags],PF_Slash	;12/02
	jz	@@1		;kein Backslash am Ende
	mov	[by di-1],ah	;Backslash am Ende entfernen (so tut es 9x)
@@1:	mov	di,ofs ShortBuffer
	call	ModifyBuffer	;Nie auf dem Hostlaufwerk!
	mov	dx,di
	mov	ah,3Bh
	jmp	CallOld
endp

proc lfn_rmdir
	call	Find_Longname_For_Deletion
	call	SFN_AL_CallOld
	jc	Throw
	jmp	loesch_longpos
endp

proc lfn_pwd
;etwas brutal den User-Puffer missbrauchen, dann TRUENAME werkeln lassen
	mov	al,dl
	push	si
	 or	al,al
	 jz	@@1
	 add	al,'@'
	 mov	ah,':'
	 mov	[fs:si],ax
	 add	si,2
@@1:	 mov	[wo fs:si],'.' ;das beschafft das aktuelle Verzeichnis!
	pop	si
	call	start_stuff	;macht den Rest und kümmert sich ums SUBST
	mov	si,ofs LongBuffer+3
	mov	di,[Client_SI]	;FS steht noch
	jmp	MakeLongName
endp

;+++++++++++ 3 Unterprogramme für LFN_Create ++++++++++++++++
proc	Copy_FCB_8P3_from_FCB_to_DI
	push	si di
	 mov	si,ofs FCB_Name
	 call	Copy_FCB_8P3_from_SI
	pop	di si
	ret
endp

proc build_unique_fcb_name_start_1
	mov	si,1
build_unique_fcb_name:
;FU: Erstellt eindeutigen, neuen FCB-Namen
;PE: [FCB_Name]=bereits vom LFN abgeleiteter kurzer Name ohne Schlange
;    [File_Flags]=Schalter für Schlangen-Erzeugung (mit [ctrl])
;    SI=Hint für Schlange
;    [ShortBuffer] gefüllt mit Pfad
;    DI=Zeiger ans ShortBuffer-Ende (hinter Slash, dort soll der Name hin)
;PA: [FCB_Name] geeignet modifiziert (garniert mit "~1" o.ä.)
;    [ShortBuffer] voll gefüllt
;    NO RETURN bei Alias-Überlauf, AL=5, oder unzulässiges Wildcard, AL=3
;VR: AX,BX,CX,DX,DI
;BUG: Sollte bei RENAME den zu entfernenden Dateinamen beachten (strcmp)
	call	Copy_FCB_8P3_from_FCB_to_DI
	mov	al,[File_Flags]
	test	al,(File_Flag_Is_LFN or File_Flag_Lowercase) ;wieso LC???
	jz	@@e		;gar nicht "typisch lang": fertig!
	test	al,File_Flag_Wildcards
	jnz	SetErr3
	mov	bx,ofs FCB_Name+31	;Scratch-Byte für's erste Mal
	test	al,File_Flag_Is_LFN
	jz	@@no_tilde	;nur wegen Kleinbuchstaben noch keine Tilde!
	BTST	[ctrl],CTRL_Tilde
	jz	@@no_tilde	;nicht mit Tilde starten
@@put_tilde:
	mov	[by bx],' '	;Vorherige Ergänzung im FCB_Name löschen
	call	Poke_Number_Over_FCB
	call	Copy_FCB_8P3_from_FCB_to_DI
@@no_tilde:
	mov	ax,4300h	;Dateiattribute holen (als Existenz-Test)
	call	SFN_CallOld
	cmc
	jnc	@@e		;nicht existent: fertig!
	inc	si
	jnz	@@put_tilde	;nächster Versuch, sonst Umrundungs-Fehler
	jmp	SetErr5
@@e:	call	strlenp1	;ans Ende rücken
	dec	di
	jmp	MakeBSlash	;falls da, sollte es später schön krachen
endp

proc strlenp1
;FU liefert String-Länge+1 (also Alloc-Länge), max. 200h
;PE: ES:DI=Stringzeiger
;PA: AX=String-Länge+1
;    DI=Zeiger hinter die terminierende Null
;VR: AX,CX,DI
	mov	ax,200h
	mov	cx,ax
	repne	scasb
	sub	ax,cx		;String-Länge +1
	ret
endp

proc make_free_dirent_space
;FU: Freien Speicher im Verzeichnis finden bzw. bereitstellen
;PE: [CurPathComp]=ASCIIZ langer Dateiname
;    [SuchSektor]=Startsektor aktuelles Verzeichnis
;    [FCB_Name]=zu suchender kurzer Dateiname
;PA: CY=1 wenn kein freier Platz vorhanden
;    (DOS-Fehler oder Hauptverzeichnis voll oder Festplatte voll)
;    [longname]=Unicode-Dateiname
;    [longpos_s]:[longpos_a]=LFN-Sektoradresse
;    [DirEnt_Copy]=Kopie des (gelöschten) "kurzen" Verzeichnis-Eintrags
;    Verzeichnis-Eintrag gelöscht (markiert oder schon geschrieben)
;    [LFN_DirEnts]=Anzahl nötiger LFN-Verzeichniseinträge (etwa: Länge/13)
;VR: alle, [num_cluster],[sektor]
	mov	si,[CurPathComp]
	mov	di,ofs LongName	;Kann zu kurz sein!!!
	mov	cx,-2
@@le:	inc	cx
	call	Oem2Uni
	stosw
	or	ax,ax
	jnz	@@le
	push	cx		;= String-Länge -1
	 dec	ax
	 mov	cl,12		;CH=0 wenn mindestens 1 Zeichen(!)
	 rep	stosw		;12x FFFF, wie es Win9x tut, hintenan
	pop	ax
	mov	cl,13		;Unicode-Zeichen pro Eintrag
	div	cl		;Anzahl Einträge in AL
	inc	al		;1..13->1, 14..26->2 usw.
	mov	[LFN_DirEnts],al
	;4.2: Freiraum suchen, dabei "eigenen" DirEnt herausrechnen
	;DH=Scan-Flags	Bit0	"eigenen" DirEnt gefunden (zur Kontrolle)
	;		Bit1	genügend zusammenhängenden Freiraum gefunden
	;		Bit2	Ende (00) gefunden, nur noch Freiraum suchen
	;		Bit3	Clusterketten-Ende wurde erreicht
	;DL=Zähler freie DirEnts
	mov	eax,[SuchSektor]
	mov	[num_cluster],1
	call	ReadSecEAX
	jc	@@e0		;wenn's schief geht
	lea	bx,[Sektor]	;der DirEnt-Zeiger
	xor	dx,dx
@@l1:
	BTST	dh,bit 2
	jnz	@@f_eol		;end-of-loop?
	mov	al,[bx]
	or	al,al		;Ketten-Ende
	jz	@@f_end
	cmp	al,0E5h
	jz	@@f_era
	BTST	[(TDirEnt bx).attr],bit 3 ;Volume Label (oder LFN-Eintrag)?
	jnz	@@f_vol		;FCB-Vergleich zwecklos oder sogar falsch
	call	Is_FCB_Equal
	jz	@@f_fcb
@@f_vol:mov	dl,0		;Schluss mit Leerraum
	jmp	@@to_next
@@f_fcb:
	bts	dx,0+8		;setzen, schon gesetzt?
@@e0:	jc	@@e		;Fehler, wenn 2x gefunden (sollte nie sein)
	mov	si,bx
	mov	di,ofs DirEnt_Copy
	mov	cx,10h
	rep	movsw		;als Kopie sicherstellen
	mov	[by bx],0E5h	;und löschen
	or	[DriveType],DT_Dirty
@@f_era:
	BTST	dh,bit 1	;Schon genügend Freiraum gefunden?
	jnz	@@to_next
@@f_era1:
	or	dl,dl
	jz	@@f_newspace
	dec	dl
	jnz	@@to_next
	BSET	dh,bit 1	;OK, gefunden
	jmp	@@to_next
@@f_newspace:
	mov	dl,[LFN_DirEnts]	;Zähler laden
	mov	eax,[CurSector]
	mov	[longpos_s],eax		;(potentiellen) Anfang merken
	mov	[longpos_a],bx
@@to_next:
	mov	al,dh
	not	al
	test	al,3		;Freiraum UND DirEnt gefunden?
	jz	@@e		;ja, fertig
	push	dx
	 call	Next_Dirent
	pop	dx
	jnc	@@l1		;nächste Runde
	BSET	dh,bit 3
@@f_end:
	BSET	dh,bit 2
@@f_eol:
	BTST	dh,bit 0
	stc
	jz	@@e		;Fehler: FCB nicht gefunden!
	BTST	dh,bit 1
	jnz	@@e		;Freiraum wurde schon gefunden!
	BTST	dh,bit 3	;schon kein DirEnt mehr Platz im Cluster?
	jz	@@f_era1	;doch!
	push	dx
	 call	FlushDirty
	 call	Alloc_Cluster	;mit Nullen gefüllt und bereitgestellt
	pop	dx
	jc	@@e		;z.B. wenn Festplatte rappelvoll
	BRES	dh,bit 3	;weiter mit normalem Next_DirEnt
	jmp	@@l1
@@e:	ret
endp

proc CTRL_write_test
	BTST	[ctrl],CTRL_Write
	jnz	@@e
	mov	[LastError],1	;Verbotener Schreibzugriff
ilfn_retu:
@@e:	ret
endp


proc install_long_filename
;FU: Baut langen Dateinamen in Verzeichniseintrag ein,
;    wenn es die Art des "langen" Dateinamens erforderlich macht
;PE: [CurPathComp]=ASCIIZ langer Dateiname
;    [SuchSektor]=Startsektor aktuelles Verzeichnis
;    [FCB_Name]=zu suchender kurzer Dateiname
;PA: [longpos_s]:[longpos_a]=LFN-Sektoradresse
;    Sektorinhalt wird sofort ausgeschrieben
;VR: ?,[longname],[num_cluster],[sektor]
	test	[File_Flags],(File_Flag_Is_LFN or File_Flag_Lowercase)
	jz	ilfn_retu	;nicht basteln!
install_long_filename_noflagtest:
	call	CTRL_Write_test
	jz	ilfn_retu	;Bastelverbot
	;1: Anzahl der notwendigen LFN-Verzeichniseinträge berechnen
	call	make_free_dirent_space
	jc	ilfn_retu	;Fehler!
	;2: alles OK für LFN-Eintragung
	mov	eax,[longpos_s]
	call	ReadSecEAX
	jc	ilfn_retu	;Fehler!
	mov	bx,[longpos_a]
	;3: Langen Dateinamen einsetzen
	mov	si,ofs FCB_Name
	call	calc_check
	xchg	dh,ah
	mov	dl,[LFN_DirEnts]
;Schleife mit DL=Eintrags-Nummer, DH=Checksumme
	mov	cx,40h		;für den Anfang
@@l2:	mov	al,13*2
	dec	dl
	mul	dl
	inc	dl
	mov	si,ofs LongName
	add	si,ax		;1->+0, 2->+13 usw.
	mov	di,bx
	mov	al,dl
	or	al,cl		;am Anfang 40h, später 0
	stosb
	mov	cl,5		;CH ist bereits 0
	rep	movsw
	mov	ax,0Fh
	stosw			;Attribut
	mov	al,dh
	stosb			;Prüfsumme
	mov	cl,6
	rep	movsw
	xor	ax,ax
	stosw			;Startcluster 0
	movsw
	movsw
	or	[DriveType],DT_Dirty
	push	dx
	 call	Next_DirEnt
	pop	dx
	jc	@@e
	xor	cx,cx
	dec	dl
	jnz	@@l2
	;4: Kurzen Dateinamen (mit Creation_Time) eintragen
	cmp	[DirEnt_Copy.timec],0
	jnz	@@k2
	mov	eax,[DirEnt_Copy.timem]
	mov	[DirEnt_Copy.timec],eax
@@k2:	mov	si,ofs DirEnt_Copy
	mov	di,bx
	mov	cx,10h
	rep	movsw
	call	WriteNow
lfn_creat_retu:
@@e:	ret
endp

proc lfn_creat
	;1. Finden des LFN-Eintrags
	call	path_locate
;Vermeidung von zuviel "locate_dirent", wenn der VC einfach seine
;drei DIRINFO-Dateien sucht...
	mov	ah,[Client_DL]
	mov	al,[File_Flags]
	shr	ah,4
	push	ax
	 test	al,(File_Flag_Is_LFN or File_Flag_Char_High)
	 jnz	@@locate_dirent	;Suche muss sein
	 dec	ah		;Create als Option?
	 jnz	@@pop_open_only	;nein, bloß Name kopieren reicht
	 test	al,File_Flag_LowerCase	;Nur Großbuchstaben?
	 jz	@@pop_open_only	;dann nicht erst suchen, sofort erzeugen
@@locate_dirent:
 INT3
	 call	File_DirEnt_Locate	;verändert [CurSector] auf momentanen
	pop	ax
	jnc	@@open		;Nur öffnen: ganz einfach!
	dec	ah
	jnz	SetErr2		;oberes Nibble muss 1 sein (sonst Code 2)!
	call	Check_CDFS_Throw ;auf CD ist CREAT schlecht möglich:-)
	call	CTRL_Write_test
	jz	@@open_only	;8.3-Name erzeugen lassen (ohne Schlange?!)
	;2. geeigneten, nicht bereits vorhandenen FCB-Namen ermitteln
	mov	si,1
	test	[Client_BH],4	;Wirklich DI als Hint benutzen?
	jz	@@no_DI_hint
	mov	si,[Client_DI]
@@no_DI_hint:
	call	build_unique_fcb_name
	jmp	@@creat
@@pop_open_only:
	pop	ax
@@open_only:
	mov	si,ofs FCB_Name
	call	Copy_FCB_8P3_from_SI
@@open:	;3. Aufruf des OldInt21
	mov	[File_Flags],File_Flag_NDevice
				;"nicht basteln"-Code vermerken
@@creat:
	mov	ax,[Client_CX]	;create-Attribut
	test	[File_Flags],(File_Flag_Is_LFN or File_Flag_Lowercase)
	jz	@@nopatch	;nicht R/O entfernen
	mov	[SearchAttr],ax
	BRES	al,bit 0	;zunächst ohne Schreibschutz erzeugen
@@nopatch:
	mov	dx,[Client_DX]	;Aktion (unverändert)
	call	SFN_6C_CallOld	;die Universalfunktion rufen
	xchg	bx,ax
	mov	[Client_CX],cx	;gemachte Aktion
	call	InvalSector
	dec	cx
	dec	cx
	jnz	retu1		;Nur wenn "created" weitermachen
	;kann an Cache angehangen werden! (hier noch nicht)
	test	[File_Flags],(File_Flag_Is_LFN or File_Flag_Lowercase)
	jz	retu1		;nicht basteln!
	mov	ax,4400h	;Ist es gar ein Zeichentreiber?
	call	CallOldAndThrow	;möglicherweise unnötig mit dem CX-Test oben
	BTST	dx,bit 7	;Datei oder Zeichentreiber-Bit
	jnz	retu1		;nicht basteln!
	;4. LFN dazubasteln, aber nicht an geöffneter Datei!!
	mov	ah,3Eh		;also Datei schließen
	call	CallOld
	jc	@@del		;Irrläufer! (Löschversuch eigentlich zwecklos)
	call	ResetDrv
	call	install_long_filename
	jnc	@@open_again
@@del:	and	[DriveType],not DT_Dirty ;Sektor doch nicht schreiben
	mov	ah,41h
	call	SFN_CallOld	;Datei löschen (egal ob's funktioniert)
	jmp	SetErr5
 ;Noch mal öffnen
@@open_again:
	mov	ax,[SearchAttr]	;(Neues) Attribut
	mov	dx,2		;Aktion = "truncate" - das Attribut wirkt!
	;jmp	SFN_6C_CallOld
endp

proc SFN_6C_CallOld
;FU: OldInt21/AX=6C00 (Extended Open/Create) aufrufen, für lfn_creat
;PE: AX=Attribute, DX=CreateFlags
	xchg	cx,ax
	lea	si,[ShortBuffer]
	mov	bx,[Client_BX]	;Access/Share-Flags
	mov	ax,6C00h
	call	CallOldAndThrow
	mov	[Client_AX],ax	;Datei-Handle
retu1:	ret
endp

proc lfn_move
;Vorgehensweise:
;Bildung des SFN für beide Dateinamen (also zwei ShortBuffer
; erforderlich, dafür muss der Heap herhalten,
;Vormerken: Löschposition (wie bei SFN_unlink), FCB-Name und LFN
; für neuen Namen
;Aufruf der DOS-Funktion RENAME
;alten DirEnt löschen; SFN-LFN-Verknüpfung NICHT in Tunnel schieben!
;neuen DirEnt setzen
;Sonderfall:
;Bilden beide (unterschiedlichen) LFN den gleichen SFN,
;wird _nicht_ die DOS-Funktion gerufen, sondern alles von Hand gemacht!
;(Zurzeit wird aber schlichtweg zu einem anderen Schlangen-Zähler umbenannt)
;1. Quelldatei in SFN umwandlen
	mov	[SearchAttr],0
	call	Find_Longname_For_Deletion
;2. Quelldatei-SFN wegkopieren
	mov	di,ofs ShortBuffer
	call	strlenp1	;AX=Alloc-Länge
	add	ax,12		;Platz für Quell-LFN-Lösch-Info
	call	iLocalAlloc
	jc	retu1		;kein Platz!
	mov	[SearchAttr],di	;ZWECKENTFREMDUNG
	mov	[throw_fi],ofs EMessage	;bei Fehler Speicher freigeben
	mov	si,ofs longpos_s
	mov	cx,3
	rep	movsw
	mov	si,ofs CurSector
	movsd
	xchg	bx,ax
	stosw
	mov	si,ofs ShortBuffer
	call	strcpy		;hinein in den Speicher!
;3. Zieldatei in SFN umwandeln
	mov	fs,[Client_ES]
	mov	si,[Client_DI]
	call	path_locate
	call	Check_CDFS_Throw	;auf CD schlecht möglich:-)
	call	File_DirEnt_Locate
	jnc	SetErr5		;Existiert bereits: Fehler!
;4. geeigneten, nicht bereits vorhandenen FCB-Namen ermitteln
	call	build_unique_fcb_name_start_1
;5. SFN-Funktion aufrufen
	mov	si,[SearchAttr]
	lea	dx,[si+12]
	mov	di,ofs ShortBuffer

	mov	ah,56h
	call	CallOldAndThrow
	call	ResetDrv
;6. Quell-LFN entfernen, SI steht noch
	mov	di,ofs longpos_s
	mov	cx,3
	rep	movsw
	call	Loesch_longpos	;evtl. vorhandenen Dateinamen killen!
;7. Ziel-LFN dazubasteln
	call	install_long_filename
;Und was tun, wenn's schiefging?

FreeSA:
	mov	ax,[SearchAttr]
	jmp	LocalFreeAX	;löscht CY, wenn Zeiger OK
endp

proc EMessage
;FU: Fehler-Ausstieg nur für lfn_move
	push	ax
	 call	FreeSA
	pop	ax
	stc
	ret
endp

proc lfn_unlink
	cmp	[Client_SI],1
	je	@@wild
	jc	lfn_rmdir
@@err2:	jmp	SetErr2
@@wild:
	call	Start_FindFirst
	mov	al,[DriveType]
	cmp	al,1
	jc	@@err2		;im Rückfallmodus (noch) kein Delete mit *
	test	[File_Flags],File_Flag_NDevice
	jz	@@err2
	test	al,DT_CDFS
	jnz	@@err2

;	INT3
	push	di
	 mov	bx,ofs Glob_LFN_Proc
	 call	DirScan		;auf FAT ganz einfach!
	pop	di
	jc	@@err2
@@l:	lea	si,[ShortName]
	push	di
	 call	strcpy
	 call	SFN_AL_CallOld	;Auch bei Fehler weitermachen
	 cmp	[LongName],0
	 jz	@@sk		;nichts zu löschen
	 test	[DriveType],DT_SmartOS
	 jnz	@@sk		;Turbo: Sektor nicht invalidieren
	 call	Loesch_longpos	;SOO einfach darf's wohl nicht sein!
@@sk:	 call	NextDirScan
	pop	di
	jnc	@@l
	clc
	jmp	InvalSector
endp

proc Find_Longname_For_Deletion
;FU: Kopf-Funktion für SFN/LFN-unlink/rmdir/move
;PE: FS:DX=zu löschender Dateiname
;    [PFlags]:PF_LFN_Input=Schalter, ob Dateiname "lang" oder "kurz"
;PA: [longpos_s]:[longpos_a]=Sektoradresse LFN-Eintrag
;    [ShortBuffer]=kurzer Datei-Pfad (durchaus mit Kleinbuchstaben)
;    [ShortName]=kurzer Dateiname (so wie vorgefunden, also Großbuchstaben)
;    [LongName]=langer Dateiname (so wie vorgefunden)
;    [CurSector]:BX=Sektoradresse SFN-Eintrag
;    NO RETURN wenn zu löschender Name nicht gefunden wurde
	mov	si,dx
	call	dirent_locate
	jc	SetError
	cmp	[longname],0	;wirklich mit langem Namen?
	jz	@@w		;nein
	test	[DriveType],DT_SmartOS	;hat das OS bereits einmal gelöscht?
	jz	@@e		;nein, müssen's vielleicht selber tun
@@w:	mov	[longpos_a],0	;nichts anschließend zu löschen
@@e:	ret
endp

proc Tunnel_Save2
;Einfach ShortBuffer und LongName retten, die von Find_Longname_For_Deletion
;übrig geblieben sind
	xor	ax,ax
	mov	si,ofs Tunnel2
	mov	[si],ax
	mov	di,ofs LongName
	cmp	[di],al		;Ist da überhaupt einer?
	jz	@@e
	call	strlenp1
	add	ax,11+5
	call	iLocalAlloc
	jc	@@e		;kein Speicher? Nicht so schlimm...
	mov	[si],di
	mov	si,ofs FCB_Name
	mov	cx,11
	rep	movsb
	mov	si,ofs DPB_drive
	movsb
	mov	si,ofs SuchSektor
	movsd
	mov	si,ofs LongName
	jmp	strcpy

Tunnel_Save:
	xor	cx,cx
	xchg	[tunnel2],cx
	jcxz	@@e
	xchg	ax,cx
@@tunnel_free:
	lea	di,[tunnel]
	jmp	XchgDIPtr	;kümmert sich um Null-Zeiger
@@e:	ret			;Ansprung-Return in der Mitte

Tunnel_Restore:
;Getunnelten langen Dateinamen bei Passung zum kurzen dazusetzen
;VR: alle
	mov	cx,[tunnel]
	jcxz	@@e		;Nichts zu wollen!
	mov	si,[Client_DX]
	cmp	[FuncNum],56h	;sfn_move?
	jne	@@ok
	mov	fs,[Client_ES]
	mov	si,[Client_DI]
Tunnel_Restore_1:
@@ok:	call	InvalSector	;könnte noch falsches DirEnt enthalten
	call	DirEnt_Locate	;muss existieren! Ansonsten: Alternativ-THROW
	jc	@@e		;DirEnt nicht gefunden
	cmp	[longname],0
	jnz	@@e		;Hat schon langen Dateinamen (open_existing)
	mov	di,[tunnel]
	mov	si,ofs FCB_Name	;Wäre schöner alles hinteinander
	mov	cx,11
	repe	cmpsb
	jne	@@e
	mov	si,ofs DPB_Drive
	cmpsb
	jne	@@e
	mov	si,ofs SuchSektor
	cmpsd
	jne	@@e
	mov	[CurPathComp],di
	call	install_long_filename_noflagtest
	xor	ax,ax
	jmp	@@tunnel_free	;Tunnel "verbrauchen"
endp

proc sfn_create			;AH=3Ch
	mov	si,12h
	jmp	@@1
sfn_createnew:			;AH=5Bh
	mov	si,10h
@@1:	xchg	si,dx
	mov	bx,2		;read/write access, compatibility mode
sfn_createex:			;AH=6Ch
	mov	[SearchAttr],cx
	call	@@try		;Stattdessen stets DOS4+ Extended Open rufen!
	cmp	[FuncNum],6Ch
	jne	@@2
	mov	[Client_CX],cx	;bedingt CX setzen
@@2:	dec	cx
	dec	cx		;"created"? (war =2?)
	clc
	jnz	@@e		;nein, kein LFN ansetzen (CY=0)
	mov	cx,[tunnel]
	jcxz	@@e		;Nichts zu wollen!
	push	bx si		;Dateiname und OpenMode retten
	 xchg	bx,ax
	 mov	ah,3Eh
	 call	CallOldAndThrow	;Schließen!
	 call	Tunnel_Restore_1
	pop	si bx
	mov	cx,[SearchAttr]
	btc	cx,0		;Schreibgeschützt?
	jnc	@@3		;nein, SetFAttr überspringen
	mov	ax,4301h	;Attribut muss entfernt werden!
	mov	dx,si
	call	PushedCallOld
	BSET	cx,bit 0
@@3:	mov	dx,0002h	;TRUNCATE, CX wird eingesetzt (wurde erprobt)
@@try:	mov	ax,6C00h	;Nochmals öffnen, diesmal CX _nicht_ setzen!
PushedCallOld:
	push	ds
	 mov	ds,[Client_DS]
	 call	CallOld
	 mov	[Client_AX],ax	;Handle oder Fehlerkode
	pop	ds
	jc	Throw		;bei Fehler muss alles so bleiben
@@e:	ret
endp

proc sfn_process
;Unabhängig von der Stellung des Schalters "Schreiben" muss beim
;Löschen von Dateien und Verzeichnissen _immer_ der LFN-Eintrag
;mit gelöscht werden!
;Ansonsten blieben im Verzeichnis LFN-Einträge zurück, die das Löschen
;des Verzeichnisses verhindern, und das wäre weitaus schlimmer.
;Glücklicherweise wird Int21 immer sequentiell (im Gänsemarsch)
;gerufen, dadurch sollten Reentranzprobleme unter Windows vom Tisch sein.
;Beim Löschen in der NT-DOS-Box kommt das NT zuvor (dieses löscht
; selbständig den LFN-Teil, genauso auch das (nackte) MS-DOS7)
;BUG: Da fehlen noch die FCB-Funktionen fcb_unlink (AH=13h),
;     fcb_creat (AH=16h), fcb_move (AH=17h)
	xchg	al,ah
	mov	[FuncNum],al	;für Tunnel_Restore und sfn_createXX
	;0. Extrawürste für handle-liefernde Funktionen
	cmp	al,3Ch
	je	sfn_create
	cmp	al,5Bh
	je	sfn_createnew
	cmp	al,6Ch
	je	sfn_createex
	;1. Finden des LFN-Eintrags
	test	ah,PF_Tunnel_Save	;AH=[PFlags]
	jz	@@1
	call	Find_Longname_For_Deletion
@@1:	;2. Aufruf des OldInt21
	mov	ax,[Client_AX]	;rmdir oder unlink oder rename
	mov	dx,[Client_DX]	;Dateiname
	push	es
	 mov	es,[Client_ES]	;nur für move/rename
	 mov	di,[Client_DI]	;nur für move/rename
	 call	PushedCallOld	;Throw geht nicht, falsches DS
	pop	es
	test	[PFlags],PF_Tunnel_Save
	jz	@@e1
	test	[ctrl],CTRL_Tunnel
	jz	Loesch_longpos	;Wenn's nicht erst in den Tunnel kommt...
	call	Tunnel_Save2	;Ergebnis in <tunnel2> "parken"
Loesch_longpos:
	call	terminate_cache	;evtl. vorhandenen Dateinamen killen!
	;Dieser muss genaugenommen in den "Tunnel" geschoben werden!
	;3. Löschen des LFN-Eintrags
	call	InvalSector	;ist nun auf jeden Fall ungültig!
	mov	bx,[longpos_a]
	or	bx,bx
	jz	@@e1		;nichts zu tun!
	mov	eax,[longpos_s]
	push	bx
	 call	ReadSecEAX
	pop	bx
	mov	al,[(TLfnDirEnt bx).count]
	cmp	al,0E5h		;Schlaues Betriebssystem am Werk?
	jne	@@nosmart
	or	[DriveType],DT_SmartOS	;nie mehr nachfummeln müssen!
@@nosmart:
	test	al,80h
	jnz	@@e1		;hat das OS (z.B. WinNT) schon gelöscht o.ä.!
	test	al,40h		;Letztes Stückel?
	jz	@@e1		;irgendwas ist faul
	and	al,3Fh		;Nummer
@@loesch:
	cmp	[(TLfnDirEnt bx).attr],0Fh
	jne	@@ep		;wieder ist was faul (aber doch schreiben)
	mov	[(TLfnDirEnt bx).count],0E5h
	or	[DriveType],DT_Dirty	;schreiben lassen
	dec	al
	jz	@@ep		;Ende erreicht
	push	ax
	 call	Next_DirEnt
	pop	ax
	jc	@@ep		;sollte eigentlich nie passieren
	cmp	al,[(TLfnDirEnt bx).count]	;Folge-Glied?
	je	@@loesch	;alles noch in Ordnung!
@@ep:	call	FlushDirty	;sicherheitshalber sofort schreiben (Diskette!)
@@e1:
	test	[PFlags],PF_Tunnel_Restore
	jz	@@2
	call	Tunnel_Restore
@@2:	test	[PFlags],PF_Tunnel_Save
	jz	@@e
	call	Tunnel_Save	;Endgültig in <tunnel> retten
	clc
@@e:	ret
endp

proc Check_NoRO
;FU: Testet aktuelles Laufwerk auf NoRO-Kandidat und, wenn ja, löscht
;    Schreibschutzattribut
;PE: CX oder CL = Attribut
;PA: CX oder CL = modifiziertes Attribut
;VR: CL,AL
	test	[ctrl],CTRL_RoBit
	jnz	@@e		;alles belassen
	mov	al,[DriveType]
	cmp	al,1		;unbekannt: NORO
	jc	@@1
	test	al,DT_CDFS	;auf CDs sowieso NoRO
	jz	@@e
@@1:	BRES	cx,1		;Schreibschutz-Attribut weg!
@@e:	ret
endp

;<lfn_attr>-Unterprogramme
;PE: BX=DirEnt-Zeiger
;    CY=0
;N: DOS6.2: although SFN GetAttr of "X:\" fails on a CD drive,
;   LFN GetAttr never fails (was bug until 0.22d)
proc lfn_attr_subroutines
cd_attr_getattr:
attr_getattr:			;via DOS ohne spezielle (CD-)Verrenkungen
	mov	cx,10h
	cmp	[by ShortBuffer+3],0	;nur "X:\" ?
	jz	@@wrcx		;Extrawurst!
	mov	ax,4300h
	call	SFN_CallOld	;also GetAttr
	jc	@@e
	call	Check_NoRO
@@wrcx:	mov	[Client_CX],cx
@@e:	ret
attr_setattr:
	mov	cx,[Client_CX]
	mov	ax,4301h
	jmp	SFN_CallOld
cd_attr_getphyssize:
	mov	eax,[(TCD_DirEnt bx).fsize]
	mov	cl,11			;2^11=2048
	jmp	@@getphyssize
attr_getphyssize:
	mov	eax,[(TDirEnt bx).fsize] ;mit Clusterverschwendung...
	mov	cl,[DPB_Shift]		;für max. 64K-Cluster
	add	cl,9			;2^9=512 Bytes pro Sektor
@@getphyssize:
	mov	bx,1
	shl	bx,cl
	dec	bx
	test	ax,bx
	jz	@@FullCluster
	or	ax,bx
	inc	eax			;aufrunden
@@FullCluster:
	mov	[Client_AX],ax
	ror	eax,16
	mov	[Client_DX],ax
@@retu:	clc
	ret
attr_settimem:
;Fehlt noch: Anpassung der Verzeichniseinträge "." und ".."
;im untergeordneten Verzeichnis (falls Verzeichnis), oder was macht Win9x?
	mov	ax,[Client_DI]
	rol	eax,16
	mov	ax,[Client_CX]
	cmp	[(TDirEnt bx).timem],eax
	je	@@retu
	mov	[(TDirEnt bx).timem],eax
@@Dirty_Sec:
	or	[DriveType],DT_Dirty
	ret
cd_attr_gettimem:
	call	CD_Get_Time
	jmp	@@attr_gettimem
attr_gettimem:
	mov	eax,[(TDirEnt bx).timem]
@@attr_gettimem:
	push	eax
	pop	[Client_CX]		;LOW
	pop	[Client_DI]		;HIGH
	ret
attr_settimea:
	mov	ax,[Client_DI]
	cmp	[(TDirEnt bx).timea],ax
	je	@@retu
	mov	[(TDirEnt bx).timea],ax
	jmp	@@Dirty_Sec
attr_gettimea:
	mov	ax,[(TDirEnt bx).timea]
cd_attr_gettimea:
	mov	[Client_DI],ax
	ret
attr_settimec:
	mov	ax,[Client_DI]
	rol	eax,16
	mov	ax,[Client_CX]
	mov	[(TDirEnt bx).timec],eax
	mov	ax,[Client_SI]
	mov	[(TDirEnt bx).timec10ms],al
	jmp	@@Dirty_Sec
cd_attr_gettimec:
	call	CD_Get_Time
	jmp	@@attr_gettimec
attr_gettimec:
	mov	dl,[(TDirEnt bx).timec10ms]
	mov	eax,[(TDirEnt bx).timec]	;DWORD
@@attr_gettimec:
	mov	[Client_CX],ax		;LOW
	shr	eax,16
	mov	[Client_DI],ax		;HIGH
	mov	dh,0
	mov	[Client_SI],dx
	ret
endp lfn_attr_subroutines

proc lfn_attr
	mov	si,dx
	mov	al,bl
	cmp	al,9
	jnc	@@e5		;Fehler: falsche Subfunktion
	mov	dx,ofs file_locate
	cmp	al,2
	jc	@@1		;immer erlaubt, weil Schreiben via DOS
	mov	dx,ofs dirent_locate
	call	CTRL_Write_test
	jnz	@@1		;Schreiben erlaubt
	test	al,1
	jnz	@@e5		;verbotener Schreibzugriff!
@@1:	call	dx		;also je nach [Client_BL]
	jc	@@e
	mov	ax,ofs cd_pvt_attr
	mov	si,[Client_BX]
	and	si,0FFh
	call	Check_CDFS
	jnz	@@iscd
	mov	ax,ofs pvt_attr
	add	si,si
@@iscd:	test	si,1		;ungerade Nummer?
	jnz	@@e5
	add	si,ax
	xor	ax,ax
	jmp	[wo si]
@@e5:	mov	al,5
@@e:	jmp	SetError
endp

proc InitFill
;FU: Win32_Find_Data-Record-Zeiger laden und EAX löschen; CH laden;
;    Client_CX löschen (return OEM oder?)
	mov	es,[Client_ES]
	mov	di,[Client_DI]
	mov	ch,[by LOW Client_SI]
	xor	eax,eax
	mov	[(TW32FindData es:di).sname],al
	mov	[Client_CX],ax
	ret
endp

proc FillFD
;Routine für FindFirst/FindNext: W32FindData-Record füllen
;PE: [Client_ES]:[Client_DI]=FindData-Zeiger
;    BX=DirEnt-Zeiger
;    [Client_SI]=DateTime_Format (Bit 0)
;PA: FindData gefüllt; Zeitformat=DOS
;    [Client_CX]=0 oder 1 (Unicode_Conversion_Flags)
;VR: ES,DI,SI,EAX,EDX,CH
	call	InitFill
	call	copy_attr_and_time
	xor	eax,eax
	stosd				;SizeH
	xchg	edx,eax
	stosd				;SizeL
	call	stosq0			;res
	mov	si,ofs LongName
	cmp	[by si],al
	jz	@@short_only
	push	di
	 call	strcpy			;"Langer" Dateiname
	pop	di
	mov	al,[PFlags]		;evtl. Konvertierungsfehler?
	and	al,PF_Fail_Uni2Oem	;muss Bit 0 sein!
	mov	[Client_CL],al		;Konvertierungsfehler mitteilen
	add	di,260			;auf "kurzen" Namen
@@short_only:
	mov	si,ofs ShortName
	jmp	strcpy
endp

proc PutValues
;Routine für FindFirst/FindNext: Handle-Puffer füllen
;PE: DI=Handle-Puffer
;    [SearchAttr]=Such-Attribut
;    BX=DirEnt-Zeiger
;    [CurSector]=Sektornummer
;PA: Handle-Puffer gefüllt
;VR: EAX,DX,SI,DI,CX
	mov	al,MAGIC_hFind		;Magic für "gültiges Handle"
	stosb
	mov	al,[DPB_Drive]
	stosb
	mov	ax,[SearchAttr]
	stosw
	call	Check_CDFS
	jnz	@@cd
	mov	ax,bx
	stosw
	lea	si,[CurSector]
	movsd
	jmp	@@w
@@cd:	lea	si,[Search.jol]
	mov	cx,11
	rep	movsw
@@w:	;mov	al,[File_Flags]
	;stosb
	mov	si,[CurPathComp]
	call	strcpy			;den String hinterher!
	ret
endp

proc Check_Valid_BX
;Testet ob BX=FindFirst/FindNext-Handle gültig ist
;PA: NO RETURN wenn ungültig (AL=6)
;    SI=BX+1
;    Z=0 für Fallback-Modus
	or	bx,bx		;Nullhandle extra (wegen VC-Fehler)
	jz	@@f_vc		;AX (=7100h) belassen!
	mov	si,bx
	lodsb
	cmp	al,MAGIC_hFind
	jz	@@ok
	cmp	al,MAGIC_FB_hFind
	jz	@@ok_Z0
@@f_vc:	mov	al,6		;invalid handle (s.a. Int21/AH=59)
	jmp	SetError
@@ok_Z0:or	al,al
@@ok:	ret
endp

proc Alloc_Find_Handle
;FU: Speicherreservierung für FindFirst
;PE: [CurPathComp]=Suchausdruck (wegen Speicherplatzbedarf später!)
;PA: DI=[Client_AX]=Zeiger auf entsprechend Platz
;    NO RETURN AL=4=handle table full
	mov	di,[CurPathComp]
	call	strlenp1
	add	ax,SIZE TFindInfo	;zz. unabhänging FAT/Joliet
	call	Check_CDFS
	jz	_Alloc_Find_Handle
	add	ax,2+2+22-(SIZE TFindInfo)
_Alloc_Find_Handle:		;Einstieg für Rückfall...
	call	iLocalAlloc
	mov	al,4	;handle table full
	jc	SetError
	mov	[Client_AX],di	;return Handle
	ret
endp

proc FB_Alloc_Find_Handle
;FU: Speicherreservierung für FindFirst im Rückfallmodus
;PE: [CurPathComp]=Suchausdruck (wegen Speicherplatzbedarf später!)
;PA: DI=[Client_AX]=Zeiger auf entsprechend Platz
;    CY=1: AX=9904=handle table full
	xor	ax,ax
	test	[File_Flags],File_Flag_Is_LFN
	jz	@@1		;keinen Dateinamen einbeziehen!
	mov	di,[CurPathComp]
	call	strlenp1
@@1:	add	ax,SIZE TFB_FindInfo
	jmp	_Alloc_Find_Handle
endp

proc FB_FillFD
	call	InitFill	;liefert CH(!)
	mov	cl,[DTA.attr]
	call	Check_NoRO
	xchg	cl,al
	stosd
	call	stosq0
	call	stosq0
	mov	eax,[DTA.time]
	call	evtl_time_dos_win_dl0	;verwendet CH
	xor	eax,eax
	stosd			;SizeHigh
	mov	eax,[DTA.fsize]
	stosd
	call	stosq0		;2 langweilige reservierte Felder
	lea	si,[DTA.fname]
	jmp	strcpy
endp

proc noentry_FB_Check_Found
;FU: Testet Dateinamen und Attribut im DTA gegen zu suchenden
;    "langen" Suchausdruck (der z.B. die Suche nach "*1" unterstützt)
;    sowie gegen das noch ausstehende Must-Match-Attribut.
;    Bei Fehltreffer Auslösung von FindNext bis zum Treffer oder CY=1
;    Das Attribut 0Fh wird hier extra herausgeworfen...(???)
;PE: DOS DTA auf [DTA] gesetzt und gefüllt
@@l:	mov	al,[DTA.attr]
	cmp	al,0Fh		;ein LFN (auf FAT)?
	je	@@retry
	call	Match_MM_Attr
	jnz	@@retry
	test	[File_Flags],File_Flag_Is_LFN
	jz	@@e		;kein Dateiname zu vergleichen
	lea	si,[DTA.fname]
	call	GlobbingEx
	jz	@@e		;OK, Name geht durch
FB_Find_Next:			;Einstieg für FB_fnext
@@retry:mov	ah,4Fh		;FindNext
FB_Find_First:			;Einstieg mit AH=4Eh
	call	CallOld
	jnc	@@l
@@e:	ret
endp

proc FB_ffirst
;FU: FindFirst im Rückfallmodus
	call	same_stuff	;hier: Suchmaske (meist ????????.???)

	call	DTA_Init

	mov	cx,[SearchAttr]
	not	cl
	mov	dx,ofs ShortBuffer
	mov	ah,4Eh
	call	FB_Find_First
	jc	DTA_Done	;wenn's nichts zu finden gab
	call	FB_Alloc_Find_Handle
	jc	DTA_Done	;kein Platz im Heap (pardon!)
	mov	al,MAGIC_FB_hFind
	stosb
	call	Store_DTA
	mov	al,[by HIGH SearchAttr]
	stosb
	mov	al,[File_Flags]
	stosb
	test	al,File_Flag_Is_LFN
	jz	@@no_name
	mov	si,[CurPathComp]
	call	strcpy		;liefert CY=0
@@no_name:
	jmp	FD_Fill
endp

proc FB_fnext
;FU: FindNext im Rückfallmodus
;PE: BX=(SI-1)=Heap-Zeiger (Such-Handle)
	call	DTA_Init
	mov	di,ofs DTA
	mov	cx,21		;eigentlich könnte die DTA auch ganz gut
	rep	movsb		;im Heap residieren, aber ich bin ja geizig..
	lodsb
	mov	[by HIGH SearchAttr],al
	lodsb
	mov	[File_Flags],al
	mov	[CurPathComp],si	;hier: egal ob Name gespeichert ist!
	call	FB_Find_Next
	jc      DTA_Done
	mov	di,bx		;BX (=Handle) bis dahin nicht geändert(?)
	inc	di
	call	Store_DTA	;die 21 Bytes als Aufhänger zum Weitersuchen
FD_Fill:
	call	FB_FillFD
	;jmp	DTA_Done
endp
proc DTA_Done
;VR: DX; Flags (speziell CY) werden gerettet!
	pushf
	push	ds ax
	 lds	dx,[old_dta]
	 mov	ah,1Ah
	 call	CallOld
	pop	ax ds
	popf
	ret
endp

proc DTA_Init
;VR: AX,DX(=ofs dta)
	call	InvalSector
	push	es bx
	 mov	ah,2Fh
	 call	CallOld
	 SES	[old_dta],bx
	pop	bx es
	mov	dx,ofs dta
	mov	ah,1Ah
	jmp	CallOld
endp

proc Store_DTA
;FU: 21 Bytes des DTA nach ES:DI kopieren
;PA: DI entsprechend erhöht
;VR: CX,SI,DI
	lea	si,[DTA]
	mov	cx,21
	rep	movsb		;die 21 "undokumentierten" Bytes der DTA
	ret
endp

proc Check_Device
;D: If file name is a DOS device, DOSLFN should work in FallBack mode
;   and let DOS handle these special files
;I: [FCB_Name]=file name to check
;   [File_Flags]=plausibility bits before comparing
;   [DriverChain]=const FAR pointer to DOS device driver chain
;O: [File_Flags].NDevice=0 if device, unchanged if not
;   CY always clear
;M: CX
	test	[File_Flags],(File_Flag_Is_LFN or File_Flag_Wildcards)
	jnz	@@e		;cannot be a DOS device
	push	es bx si di
	 mov	bx,ofs DriverChain
@@l:	 les	bx,[es:bx]
	 inc	bx		;was xxxx:FFFF
	 jz	@@ex		;documented end of chain
	 dec	bx
	 BTST	[wo es:bx+4],8000h	;character device?
	 jz	@@l		;no, continue with next driver
	 lea	di,[bx+10]
	 lea	si,[FCB_Name]
	 mov	cx,4
	 repe	cmpsw
	 jne	@@l		;no match, continue with next driver
	 BRES	[File_Flags],File_Flag_NDevice
@@ex:	pop	di si bx es
@@e:	clc
	ret
endp

proc Start_FindFirst
;FU: Gemeinsame Routine für lfn_ffirst und lfn_unlink mit Platzhalterzeichen
;PE: FS:DX=Suchmaske
;    [Client_CX]=Such-Attribute
;    [PFlags]:PF_LFN_Input=1
;PA: [File_Flags]=Eigenschaften der letzten Komponente
;    [DriveType]=Laufwerkstyp
;    CX=[SearchAttr]=bearbeitete Such-Attribute
;    CY=Pfad nicht gefunden
	mov	si,dx
	call	path_locate
	BRES	[PFlags],PF_Follow
	mov	ax,[Client_CX]	;Attribute
	cmp	al,8		;Nur Volume Label?
	jne	@@1
	mov	ah,al		;dann gilt hier eine Must-Match-Ausnahme!
@@1:	or	al,21h		;ARCHIVE und READONLY immer "durchlassen"
	not	al		;als "Auswerf-Maske" umdrehen
	mov	[SearchAttr],ax
	xchg	cx,ax		;für's (lange) Label der CD
@@e:	ret
endp


proc lfn_ffirst
	INT3
	call	Start_FindFirst
	jc	@@e

	mov	al,[DriveType]
	cmp	al,1
	jc	FB_ffirst	;das ganze im Rückfallmodus
	test	[File_Flags],File_Flag_NDevice
	jz	FB_ffirst
	test	al,DT_CDFS
	jnz	@@oncd

	mov	bx,ofs Glob_LFN_Proc
	call	DirScan		;auf FAT ganz einfach!
	jmp	@@k
@@oncd:
	push	di
	 lea	si,[Search.jol.i.sect]
	 lea	di,[Search.jol.restart]
	 movsd
	 movsw
	pop	di
	test	cl,8			;Volume Label requested?
	jnz	@@2
	cmp	di,ofs ShortBuffer+3	;root dir?
	jnz	@@2			;volume label only on root!
	call	CD_Make_Volume_Label
	jnc	@@e
@@2:
	mov	bx,ofs Glob_CD_LFN_Proc
	call	CD_Ping_DirScan	;auf CDFS ungleich komplizierter!
	;mov	bx,ofs CD_Sektor	;zurückstellen!?
@@k:	jc	SetErr2		;file not found
@@f:	;Eintrag gefunden, mit bx=DirEnt-Zeiger
	call	Alloc_Find_Handle
	call	PutValues
	call	FillFD
@@e:	ret
endp

proc lfn_fnext
	call	Check_Valid_BX
	jnz	FB_fnext
	lodsb
	inc	al
	call	GetDrvParams	;tut meist nichts, wenn AL gleich geblieben
	lodsw
	mov	[SearchAttr],ax	;Attribute
	call	Check_CDFS
	jnz	@@cd
	;weiter mit FAT
	mov	[MatchPtr],ofs Glob_LFN_Proc
	lodsw
	xchg	bx,ax		;Sektorzeiger
	lea	di,[CurSector]
	movsd			;Sektor-Nummer
	jmp	@@gem

@@cd:	;weiter mit CDFS (Joliet)
	mov	[MatchPtr],ofs Glob_CD_LFN_Proc
	lea	di,[Search.jol]
	mov	cx,11
	rep	movsw
	call	CD_LFN_Load
@@gem:
	;lodsb
	;mov	[File_Flags],al
	mov	[CurPathComp],si	;Maske folgt (Zeiger in Heap)
	push	bx
	 call	ReadSec
	pop	bx
	jc	@@alldrives
	call	NextDirScan	;Bei CDROM liegt der Hase im Pfeffer...
	jc	@@alldrives
	call	Check_CDFS
	jz	@@alldrives		;Was für eine Gülle hier!
	call	SetSuchSektor_SFN
	push	di
	 lea	di,[Search.jol.j]
	 call	CD_SaveDirInfoFull	;neuer Zeiger für PutValues
	pop	di
	call	CD_SFN_Load
	mov	[MatchPtr],ofs Match_CD_Sectorpointer_Proc
	call	CD_NextDirScan_Circular
	jc	@@e			;Konsistenzfehler!
	call	CD_Shortname
	lea	di,[Search.jol.i]
	call	CD_SaveDirInfoFull
	clc

@@alldrives:
	jc	SetErr18	;no more files

	mov	di,[Client_BX]	;find handle
	call	PutValues
	call	FillFD
	mov	ah,4Fh		;"undokumentierter" Returnwert
	mov	[Client_AX],ax
@@e:	ret
endp

proc lfn_fclose
	call	Check_Valid_BX
	xchg	ax,bx
	jmp	LocalFreeAX	;Handle-Tabellen-Eintrag freigeben
endp

proc CheckModifyBuffer
;PE: DI=Pufferadresse
;PA: Puffer und Pufferadresse zu virtuellem Laufwerk modifiziert,
;    wenn Bit 7 von Client_CH gesetzt ist
;VR: AX,DI
	BTST	[Client_CH],bit 7
	jz	@@e		;Nicht modifizieren
ModifyBuffer:
	mov	al,[subst_drive]
	or	al,al		;Netzlaufwerk?
	jz	@@e		;Nicht patchen!!
	add	di,[subst_root]
	push	di
	 mov	ah,':'
	 stosw
	 cmp	[by di],0	;Backslash könnte entfernt sein
	 jnz	@@1
	 mov	ax,'\'
	 stosw			;terminiert nachsetzen
@@1:	pop	di
@@e:	ret
endp

proc lfn_name
public lfn_name
	cmp	cl,3		;nur 0..2
	jnc	erre
	cmp	cl,1
	jc	lfn_truename
	jz	lfn_shortname
	jmp	lfn_longname
erre:	stc
	ret
endp

proc lfn_truename
	call	Truename
	mov	di,ofs longbuffer
	jmp	_copy_to_client
endp

proc lfn_shortname
	call	file_locate
	mov	di,ofs shortbuffer
_copy_to_client:
	call	CheckModifyBuffer
	push	[Client_ES] [Client_DI]
	push	ds di
	call	fstrcpyBS
	ret
endp


proc lfn_longname
	call	start_stuff
	mov	si,ofs longbuffer
	cmp	[by si],'/'	;UNC?
	mov	ax,'\\'
	je	@@2
	lodsw
	BTST	[Client_CH],bit 7
	jnz	@@1
	mov	[subst_root],0	;Hostpfad liefern
	jmp	@@2
@@1:	mov	al,[subst_drive];virtuelles Laufwerk liefern
@@2:	mov	fs,[Client_ES]
	mov	di,[Client_DI]
	mov	[fs:di],ax	;Laufwerk:
	inc	si		;hinter den Root-Backslash
	scasw			;=add di,2
	mov	[by fs:di],'\'
	inc	di
	jmp	MakeLongName
endp

proc lfn_genshort
;Aus Dateiname (ohne Pfad) kurzen Dateinamen (Alias) generieren
;Seiteneffekt: arbeitet bis zum Backslash (Pfad-Komponente)
	cmp	[Client_DL],11h		;nur OEM->OEM wird unterstützt!
	jne	erre
	cmp	[Client_DH],2		;nur 0 (FCB) oder 1 (8.3)
	jnc	erre
	mov	si,ofs longbuffer
	push	ds si
	push	[Client_DS] [Client_SI]
	call	fstrcpy			;lokal kopieren

	call	Gen_Alias		;nach FCB_Name

	mov	es,[Client_ES]
	mov	di,[Client_DI]
	cmp	[Client_DH],0
	mov	bx,ofs FCB_Name
	je	@@want_fcb

	call	Copy_FCB_8P3	;hier mit ES<>DS!
	ret

@@want_fcb:
	mov	si,bx
	mov	cx,11
	rep	movsb
	ret
endp

proc lfn_timeconv
public lfn_timeconv
	cmp	[Client_BL],1
	jc	@@todos
	jz	@@towin
	stc
	ret
@@todos:
	segfs	lodsd
	xchg	edx,eax
	segfs	lodsd
	xchg	edx,eax
	call	time_win_dos
	jc	@@e		;kann versagen...
	ror	eax,16		;Hi<->Lo
	mov	[Client_CXDX],eax
	mov	[Client_BH],dl
	clc
@@e:	ret
@@towin:
	mov	eax,[Client_CXDX]
	rol	eax,16		;Hi<->Lo
	mov	dl,[Client_BH]
	mov	es,[Client_ES]
	mov	di,[Client_DI]
	;jmp	time_dos_win	;versagt nie
endp

proc time_dos_win ;TODO
;FU: Zeit-Umwandlung DOS->WIN
;    Die Implementierung wäre geradezu Luxus; deshalb einfach eine
;    "Abbildung", die zumindest eine korrekte Sortierung ermöglicht,
;    und eine Rückkonvertierung zum vorhergehenden FAT-Format ermöglicht
;PE: EAX=DOS-Dateizeit
;    DL=DOS-10-ms-Schritte
;    [TimeOffset]=Zeitzonen-Umrechnungszahl
;    ES:DI=Speicher zum Schreiben der Zeit (stosq)
;PA: EAX:EDX=Win-Dateizeit (100-ns-Schritte seit 1.1.1601)
;    CY=1: fehlerhafte Angaben (z.B. 13. Monat o.ä.)
;VR: EAX,EDX
	xchg	edx,eax
	shl	eax,24		;stimmt ungefähr (Faktor 500)
stosq:
	stosd
	xchg	edx,eax
	stosd
	ret
endp

proc stosq0
	xor	eax,eax
	stosd
	stosd
	ret
endp

proc evtl_time_dos_win_dl0
;FU: Wandelt je nach CH die Dateizeit ins Win-Format
;    oder macht nichts außer EDX zu löschen; für FindFirst/FindNext
;PE: EAX=DOS-Dateizeit
;    DL=DOS-10-ms-Schritte (nur bei Einsprung evtl_time_dos_win)
;    CH=Schalter DOS (<>0) oder Win (=0)
;    ES:DI=Speicherort für Win32-Zeit
;PA: EDX:EAX=DOS- oder Win-Dateizeit
;VR: EAX,EDX,DI
	mov	dl,0
evtl_time_dos_win:
	or	ch,ch
	jz	time_dos_win
	xor	edx,edx
	jmp	stosq
endp

proc time_win_dos ;TODO
;FU: Zeit-Umwandlung WIN->DOS
;PE: EDX:EAX=Win-Dateizeit (100-ns-Schritte seit 1.1.1601)
;    [TimeOffset]=Zeitzonen-Umrechnungszahl
;PA: EAX=DOS-Dateizeit
;    DL=DOS-10-ms-Schritte
;    CY=1: außerhalb des Bereiches 1980..2107
;VR: EAX,EDX
;Zur Zeit einfach als "Komplementärfunktion" implementiert
	shr	eax,24
	xchg	edx,eax
	ret
endp

proc lfn_volinfo
public lfn_volinfo
;Laufwerks-Informationen beschaffen
	mov	si,dx
	call	start_stuff
	xor	eax,eax
	mov	[Client_DXBX],260*65536+4006h
		;Länge Pfad (DX), BX=4007h wäre mit case-sensitiver Suche
	dec	al			;255
	xchg	[Client_AXCX],eax	;Länge Dateiname<->Länge VolType
	cmp	ax,4			;zu kleiner Puffer?
	jc	@@novolinf		;Nicht einschreiben!
	mov	es,[Client_ES]
	mov	di,[Client_DI]
	call	Check_CDFS
	jnz	@@cd
	mov	eax,'TAF'		;lies: "FAT",0
	stosd
@@novolinf:
	clc
@@e:	ret
@@cd:	mov	eax,'SFDC'		;lies: "CDFS"
	stosd
	mov	al,0
	stosb
	ret
endp

proc copy_attr_and_time
;BE: Gemeinsame Routine für FillFD (FindFirst/FindNext)
;    und GetFileInfoByHandle (wie auch immer der Dateiname zu ergattern ist)
;    Dummerweise geht nicht mehr, da "Datenträgernummer" dazwischen kommt
;    Für FAT und CDFS (per Fallunterscheidung)
;PE: BX=DirEnt-Zeiger
;    ES:DI=Puffer für attr,timec,timea,timem
;    CH=Schalter für Zeit-Konvertierung, =0 für Win-Dateizeit
;PA: DI vorgerückt
;    EDX=Datei-Größe
;VR: EAX,EDX,DI
	xor	eax,eax
	call	Check_CDFS
	jnz	@@cd
	mov	al,[(TDirEnt bx).attr]	;gefundenes Attribut
	stosd
	mov	eax,[(TDirEnt bx).timec];creation time
	mov	dl,[(TDirEnt bx).timec10ms]
	call	evtl_time_dos_win
	mov	ax,[(TDirEnt bx).timea]	;access time (nur Datum)
	shl	eax,16
	call	evtl_time_dos_win_dl0
	mov	eax,[(TDirEnt bx).timem];modification time
	call	evtl_time_dos_win_dl0
	mov	edx,[(TDirEnt bx).fsize];Dateigröße (max. 2GB)
	ret
@@cd:
	call	CD_Get_Attr
	stosd
	call	CD_Get_Time		;auf 1 Sekunde genau
	call	evtl_time_dos_win	;creation time
	xchg	edx,eax
	push	eax
	 call	stosq0			;access time unbekannt
	pop	eax
	call	stosq			;modification time
	mov	edx,[(TCD_DirEnt bx).fsize];Dateigröße (max. 2GB)
	ret
endp

proc lfn_subst		;nur "Query Subst"
;Die anderen Funktionen, "Create Subst" (BH=0, DS:DX=Zu verbindender Pfad)
;und "Terminate Subst" (BH=1) erfordern das Patchen in DOS-Strukturen
	cmp	bh,2
	stc
	mov	ax,7100h
	jnz	@@e	;Durchläufer: "Nicht unterstützte Funktion"
;etwas brutal den User-Puffer missbrauchen
	mov	si,dx
	mov	eax,'\:?'	;das beschafft das SUBST-Verzeichnis!
	mov	al,bl
	add	al,'@'
	mov	[fs:si],eax
	call	Truename	;spuckt bei falschem Laufwern
	push	fs [Client_DX]
	push	ds ofs longbuffer
	call	fstrcpyBS
@@e:	ret
endp

;******************************************************************
;** FastOpen-Cache
;** hält die Zuordnung "kurzer Name", "langer Name" und "Startsektor"
;** zur Vermeidung exzessiver Festplattenzugriffe
;**  reset_cache_ptr vor Dateinamen-Suche aufrufen
;**  für jede Pfadkomponente find_in_cache aufrufen,
;**  bei jedem Versager und Auffindung put_to_cache aufrufen!
;******************************************************************
;Cache-Aufbau: ab Wurzel:
;<tiefe> mal
; ASCIIZ kurzer Dateiname (alias)
; ASCIIZ langer Dateiname (longname)
; DWORD Startsektor
;BYTE 0 Ende der Kette
;[fastopen_ptr] zeigt auf aktuellen Eintrag

;proc reset_cache_ptr
;	mov	[fastopen_ptr],ofs fastopen_buf
;	ret
;endp

proc find_in_cache
;Vergleicht [CurPathComp] mit [fastopen_ptr]
;Wenn das klappt, kopiert langen Dateinamen nach [longname] ???
; und CurPathComp nach shortbuffer (Anhängen an DI)
;Wenn nicht, vergleicht [CurPathComp] mit vorgerücktem [fastopen_ptr]
;Wenn das klappt, kopiert Alias nach [shortbuffer] (Anhängen an DI)
;Wenn nicht, dann raus mit Z=0
;Startsektor nach [CurSector] schreiben
;PA: Z=1: gefunden
;	[CurSector]=Startsektor (FAT), [CD_?FN_Cur]=Startsektoren (Joliet)
;	[ShortName]=kurzer Dateiname
;	[LongName] =langer Dateiname
;	[fastopen_ptr] vorgerückt
;    Z=0: nicht gefunden
;	[fastopen_ptr]^=0 Endemarkierung gesetzt
;VR: EAX, SI, DI (nur bei Z=1)
	mov	di,[fastopen_ptr]
	cmp	[by di],1	;Null als Kennung für "kein Cache-Eintrag"
	jc	@@e
	mov	si,[CurPathComp]
	call	StrIComp
	jz	@@f
	;nicht "kurz" gefunden: "lang" suchen!
	dec	di		;zumindest AUF die Null zurück!
	mov	al,0
	push	cx
	 mov	cx,-1
	 repne	scasb		;DI hinter die Null von ShortName
	pop	cx
	mov	si,[CurPathComp]
	call	StrIComp
	jnz	terminate_cache
@@f:
	mov	si,[fastopen_ptr]
	mov	di,ofs ShortName
	call	strcpy		;kurz nach "ShortName" (dann nach "shortbuffer")

	mov	di,ofs LongName
	call	strcpy		;lang nach "LongName" (ggf. leere Zeichenk.)

	lodsd
	call	_set_cur_and_such	;belanglos bei CDFS
	call	Check_CDFS
	jz	@@nocd
	lea	di,[Search.jol.i.sect]
	stosd			;Start und Länge übertragen
	movsw
	lea	di,[Search.jol.j.sect]
	movsd			;2x
	movsw
	cmp	al,al		;Z setzen
@@nocd:	mov	[fastopen_ptr],si
@@e:	ret
endp

proc terminate_cache
;FU: FastOpen-Kette terminieren
	mov	di,[fastopen_ptr]
	mov	al,0
	stosb			;Ende-Kennung hier eintragen
	ret
endp

proc put_to_cache
;Tut beide Dateinamen in Puffer mit Startsektor, Abschluss mit Doppelnull
;PE: [CurSector]=Startsektor (bei CDFS: [CD_Search.i.sect] und [CD_Search.j.sect])
;	Über die Link-Tabelle ist eigentlich nur das Speichern EINES Sektors
;	bei CD erforderlich!
;    [LongName] =langer Dateiname (ggf. leer)
;    [ShortName]=kurzer Dateiname (Zeiger)
;PA: [fastopen_ptr] vorgerückt
;    [fastopen_ptr]^=0 Endemarkierung gesetzt
;VR: SI,DI,EAX
	mov	di,[fastopen_ptr]
	mov	si,ofs ShortName
	call	strcpy		;liefert AL=0

	mov	si,ofs LongName
	call	strcpy		;ggf. nur die Null speichern, wenn kein LFN

	lea	si,[CurSector]
	call	Check_CDFS
	jz	@@nocd
	lea	si,[Search.jol.i.sect]
	movsd			;Startsektor
	movsw			;Länge in Sektoren
	lea	si,[Search.jol.j.sect]
	movsw			;naja, auch 6 Bytes
@@nocd:
	movsd
	mov	[fastopen_ptr],di
	mov	al,0
	stosb			;Endekennung
	ret
endp

;******************************************************************
;** LocalAlloc-Speicher (Heapverwaltung)
;** für die kleineren Allozierungen bei FindFirst/FindNext usw.
;** Speichervergabe erfolgt in 4-Byte-Stückelung.
;** Der Freispeicher ist in einer verketteten Liste:
;** 1 WORD Next-Pointer
;** 1 WORD Größe dieses Freispeicher-Blocks (inkl. der beiden WORDs)
;** Belegter Speicher hat am Anfang:
;** 1 WORD Größe dieses belegten Blocks (inkl. dieses WORDs)
;** und zurückgegeben wird ein Zeiger DAHINTER
;******************************************************************

;Konstanten für Allokations-Strategie:
LMEM_FIRST	= 0	;etwa wie LMEM_Fixed
LMEM_LAST	= 1	;etwa wie LMEM_MoveAble

iLocalAlloc:
;PE: wie LocalAlloc mit fester Allokations-Strategie
	mov	dh,LMEM_FIRST

proc LocalAlloc pascal
;PE: AX=geforderte Speichermenge in Bytes
;    DH=Allokations-Strategie
;PA: CY=1: kein Speicher mehr frei!
;    CY=0 und DI=Zeiger auf Speicherblock (fertig für STOSx)
;VR: CX,DI,DH,AX(=tatsächliche Alloc-Größe inkl. Größen-WORD)
uses bx,si
;## auf 4-Byte-Stückelung aufrunden, dazu 2 Bytes für Größen-Merker
	add	ax,5
	and	al,not 3
	mov	si,[LocalHeap]
	xor	di,di
@@l2:	mov	cx,si
	mov	si,[si]		;di=erster (oder weiterer) Freispeicher
	or	si,si		;Ende erreicht?
	jz	@@r
	cmp	[si+2],ax	;Block hat genug Platz?
	jc	@@l2		;nein, nächsten Freispeicher suchen
	mov	bx,cx		;Kandidat gefunden: BX=Vorheriger Freispeicher
	mov	di,si		;Kandidat gefunden: DI=Jetziger Freispeicher
	test	dh,1
	jnz	@@l2
@@r:
	cmp	di,1
	jc	@@nomem
	mov	cx,[di+2]
	sub	cx,ax
	jz	@@ganz		;Block ganz aufbrauchen (aushängen)
;## Zeigerkette bearbeiten
	test	dh,1
	jnz	@@hinten
	mov	si,di
	add	di,ax		;auf neue Position
	mov	[bx],di
	movsw			;Next-Pointer
	mov	[di],cx		;neue (kleinere) Größe
	lea	di,[si-2]
	jmp	@@1
@@hinten:
	mov	[di+2],cx	;neue (kleinere) Größe eintragen
	add	di,cx		;Hier ist unser Speicher
	jmp	@@1
@@ganz:
	mov	si,[di]		;Next-Pointer holen
	mov	[bx],si		;am vorhergehenden Knoten einsetzen
@@1:	stosw			;Größe allozierter Block eintragen
	ret
@@nomem:mov	[LastError],4	;"not enough memory"
	ret
endp


proc JoinMem
;FU: Zwei Freispeicher zusammenziehen, wenn möglich
;PE: DI=Zeiger auf ersten Freispeicher
;VR: AX,SI,DI
	mov	si,[di]
	mov	ax,[di+2]
	add	ax,di
	cmp	si,ax		;können zusammengezogen werden?
	jne	@@e		;nein (CY=1: fataler Fehler!)
	movsw			;Next-Pointer vorziehen
	lodsw
	add	[di],ax		;Größen addieren
@@e:	ret
endp

proc FreeDIPtr
;PE: DI=Zeiger auf WORD mit dem Heap-Zeiger
;PA: PWORD nullgesetzt, CY=1: ungültiger oder Null-Zeiger
;VR: AX,CX,DI
	xor	ax,ax
XchgDIPtr:
	xchg	[di],ax
LocalFreeAX:
	xchg	di,ax
	;jmp	LocalFree
endp

proc LocalFree pascal
;PE: DI=Speicherblock-Zeiger
;## "Freispeicher-Umgebung" auf mögliche "Blasenbildung" abtesten,
;## dabei ggf. nach vorn UND nach hinten verbinden!
;PA: CY=1: Versuch, freien Speicher freizugeben (m.a.W.: ungültiger Zeiger)
;VR: AX,CX
uses bx,si
	sub	di,2		;auf das Größen-WORD
	jc	@@e		;war wohl NIL-Zeiger
	mov	cx,[LocalHeap]
@@l:	mov	bx,cx
	mov	cx,[bx]
	jcxz	@@nomem		;wird einziger oder letzter Freispeicher!
	cmp	cx,di
	jc	@@l		;wenn hier Gleichheit rauskommt, wär's Fehler!
	stc
	jz	@@e
@@nomem:
	mov	ax,[bx+2]	;Größe des Vorblocks
	add	ax,bx
	cmp	di,ax		;DI muss nun größer/gleich AX sein
	jc	@@e		;sonst: Versuch der Freigabe von freiem Speicher
	mov	[bx],di
	xchg	[di],cx
	mov	[di+2],cx	;nun CX=Größe Speicherblock
	call	JoinMem		;rechts
	mov	di,bx
	call	JoinMem		;links
@@e:	ret
endp

;LocalInit -> see transient code area!

proc IncInDosFlag
	push	ax
	 mov	al,1		;modified by "i" switch
InDosFlagInc = by $-1
	 jmp	@@1
DecInDosFlag:
	push	ax
	 mov	al,-1		;modified by "i" switch
InDosFlagDec = by $-1
@@1:	 push	ds
	  push	8086		;modified by installation
InDosFlagSeg = wo $-2
	  pop	ds
	  lahf
	  add	[8086],al	;modified by installation
InDosFlagOfs = wo $-2
	  sahf
	 pop	ds
	pop	ax
	ret
endp


;============ cutting line, CDROM only code follow this line ================

;content of link table:
;1.WORD=Anzahl Verzeichnisse (n)
;2..n+1.DWORD: ISO-Sektornummern
;n+2..2n+1.DWORD: Joliet-Sektornummern


;**************************
;** CDROM initialization **
;**************************

CD_VD_ID	db	'CD001',1	;Erkennung für ISO-Volume
JLT$:		dz	'.JLT'		;Erweiterung für Link-Tabelle

proc IoctlRead
;FU: Führt einen "IOCTL READ" auf das CD-Laufwerk aus
;PE: BX=Pufferzeiger (28+7 Bytes)
;    CX=Laufwerk (0=A:)
;    DX=1. und 2. Byte des Datenpuffers
;PA: DI=BX+30 (hinter dem 2. Byte des Puffers)
;    CY=1 bei Fehler
;VR: AX,DX,DI
	push	cx
	 mov	di,bx
	 mov	ax,28
	 stosw			;len,sub
	 mov	al,3
	 stosw			;cmd,LOBYTE(status)
	 mov	al,0
	 mov	cl,5
	 rep stosw		;HIBYTE(status)..mediadescriptor
	 lea	ax,[bx+28]
	 stosw			;LOWORD(bufferptr)
	 mov	ax,es
	 stosw			;HIWORD(bufferptr)
	 mov	ax,7
	 stosw			;buffersize
	 mov	al,0
	 mov	cl,4
	 rep stosw		;startsector, volumeptr
	 xchg	ax,dx
	 stosw
	pop	cx
	MUX	1510h
	mov	al,[bx+4]
	add	al,al		;shift out ERROR bit (status bit 15)
	ret
endp

proc DetermineVolStart
;FU: Wo ist der Anfang der letzten Session?
;    (MSCDEX liefert diese Info leider NICHT bei Int2F/1505!)
;PE: CX=Laufwerk (0=A:)
;PA: CY=1 Fehler
;    CY=0: EAX=Volume-Start
;VR:
	mov	bx,ofs Sektor
	mov	dl,10		;GetAudioDiskInfo
	call	IoctlRead
	jc	@@e
	mov	dx,[bx+28+1]	;dl=first, dh=last
@@l:	push	dx
	 mov	dl,11		;GetAudioTrackInfo
	 call	IoctlRead
	pop	dx
	jc	@@e
	test	[by bx+28+6],40h	;Data track?
	jnz	@@ok
	dec	dh		;from back to front
	cmp	dh,dl		;(at least MSCDEX hides all non-last sessions
	jnc	@@l		; and returns first==last)
@@e:	ret
@@ok:
	mov	al,60
	imul	[by bx+28+4]	;min
	xchg	dx,ax
	mov	al,[bx+28+3]	;sec
	sub	al,2
	cbw
	add	ax,dx
	cwde
	imul	eax,75
	movsx	edx,[by bx+28+2]	;frame
	add	eax,edx
cd_init_retu:
	ret
endp

proc CD_Init	;HIER GIBTS ARBEIT
;weitere Informationen per Sektorzugriff beschaffen!
;Solange Volume-Deskriptoren einlesen, bis Schluss-Deskriptor...
;FU: CD (als solche) und Joliet-CD erkennen
;PE: CX=Laufwerk (0=A:)
;PA: CY=1: Fehler, kein MSCDEX, kein CD-Laufwerk oder eben kein Joliet
	call	DetermineVolStart
	jc	cd_init_retu	;Sprungdistanz klein halten
	add	eax,15
	mov	[CurSector],eax	;beginnend mit erstem Volume-Deskriptor
	or	[DriveType],DT_CDFS
@@next_vtoc:
	call	ReadNextSec	;nächstes VTOC
	jc	@@e		;nichts zu lesen!
	mov	si,ofs CD_Sektor
	lodsb
	mov	di,ofs CD_VD_ID
	push	cx
	 mov	cl,3		;CH (Laufwerk) sowieso 0
	 rep	cmpsw		;gleiche ID enthalten?
	pop	cx
	jne	@@err		;falscher Aufbau der VTOC
	dec	al
	jz	@@p
	dec	al
	jz	@@s
	sub	al,0FDh		;war FFh?
	jnz	@@next_vtoc	;alle anderen Deskriptoren ignorieren
	jmp	@@last		;FF = Abschluss
@@p:	;PVD gefunden!
	add	si,40-7		;Datenträgerbezeichnung
	mov	di,[argv0file]
	mov	dx,821h
@@next:	dec	dl
	jz	@@ends
	lodsb
	cmp	al,'0'
	jb	@@next
	cmp	al,'9'
	jbe	@@take
	cmp	al,'A'
	jb	@@next
	cmp	al,'Z'
	ja	@@next
@@take:	stosb
	dec	dh
	jnz	@@next
@@ends:	mov	si,ofs JLT$
	call	strcpy
	lea	di,[Medium.jol.isotree]
	mov	al,0
	jmp	@@gem		;zur gemeinsamen "Datenrettung"
@@s:	;SVD gefunden!
	add	si,88-7
	lodsw
	cmp	ax,'/%'		;Joliet-SVD?
	jne	@@next_vtoc	;nein, diesen ignorieren
	lodsb
	cmp	al,'@'
	je	@@jo
	cmp	al,'C'
	je	@@jo
	cmp	al,'E'
	jne	@@next_vtoc
@@jo:	lea	di,[Medium.jol.joltree]
	mov	al,DT_DrvPar
@@gem:
	or	[DriveType],al
	mov	eax,[CurSector]
	stosd
	lea	bx,[(CD_Sektor+156)]	;Hauptverzeichnis Little Endian
	call	CD_RetrieveDirInfo	;Rootsektor und Länge übertragen
	jmp	@@next_vtoc
@@last:
	cmp	[DriveType],(DT_CDFS or DT_DrvPar) ;Beide Deskriptoren OK?
	je	@@read_jlt
@@err:	stc
@@e:	ret

@@read_jlt:
	mov	dx,[argv0]
	DOS	3D00h		;open .JLT file (can recurse!)
	jc	@@nojlt
	xchg	bx,ax
	mov	dx,ofs CD_Sektor
	mov	cx,8
	DOS	3Fh		;_lread
	cmp	[dword CD_Sektor],4
	jne	@@cl
	mov	ax,[wo CD_Sektor+4]
	push	ax
	 shl	ax,3
	 push	ax
	  add	ax,2
	  call	iLocalAlloc
	 pop	cx
	pop	ax
	jc	@@cl
	mov	[Medium.jol.linktbl],di
	stosw
	mov	dx,di
	DOS	3Fh		;_lread
@@cl:
	pushf
	 DOS	3Eh		;_lclose
	popf
	ret
@@nojlt:
	cmp	[CurSector],32	;Erste Session?
	jnc	@@will_nicht	;Link-Tabelle ist (sicherlich) falsch!
	call	ReadNextSec ;Nachfolgenden Sektor lesen (ist Link-Tabelle!?)
	mov	si,ofs CD_Sektor
	lodsw
	cmp	ax,'eC'		;start of "CeQuadrat"
	jne	@@will_nicht
	add	si,2Ch-2
	lodsd			;Anzahl Verzeichnisse
	mov	bx,ax
	shl	bx,2		;DWORD-Offset
	push	ax
	 mov	ax,bx		;8 Bytes pro Verzeichnis
	 inc	ax		;und 2 Bytes für die Anzahl
	 add	ax,ax
	 call	iLocalAlloc
	pop	ax
	jc	@@ex		;Speichermangel!!
	mov	[Medium.jol.linktbl],di
	stosw
	mov	cx,ax
	jcxz	@@ex		;Temporäre Notbremse
@@l:
	lodsd			;Joliet
	mov	[di+bx],eax	;hinten
	movsd			;ISO vorn
	loop	@@l		;Link-Tabelle OK
@@ex:	ret
@@will_nicht:
	mov	[LastError],3	;"can't find link table"
	stc
	ret
endp

proc CD_Done
	call	Check_CDFS
	jz	@@e
	lea	di,[Medium.jol.linktbl]
	call	FreeDIPtr
@@e:	ret
endp

;******************
;** CDROM access **
;******************

proc CD_CheckRootDot
;06/02: filter out "." and ".." entries from root directory
;PE: BX=CD_DirEnt-Zeiger
;    [CurSector]=momentaner CD-Sektor
;PA: CY=0: Ist "." oder ".." im Hauptverzeichnis, dazu Z=0 (no match)
;    CY=1: Ist nicht der Fall
;VR: -
	push	eax
	 mov	eax,[CurSector]
	 cmp	eax,[Medium.jol.isotree.rootdir]
	 je	@@1
	 cmp	eax,[Medium.jol.joltree.rootdir]
@@1:	pop	eax
	stc
	jne	@@e
	cmp	[(TCD_DirEnt bx).fnamelen],1
	stc
	jnz	@@e
	cmp	[(TCD_DirEnt bx).fname],2
	cmc
@@e:	ret
endp

proc CD_check_updir
;FU: Lädt SI auf Name-Zeiger
;    Testet auf '.' und '..' (Länge 1 und Name=(binär)0 bzw. (binär)1)
;PE: BX=CD_DirEnt-Zeiger
;    DI=wo der Name hin soll
;PA: CY=0: normales DirEnt,
;	AL=Länge Name (in Bytes)
;	SI=Zeiger Name in CD_DirEnt
;	VR: SI,AL
;    CY=1: DirEnt='.' oder '..'
;	[DI] gefüllt mit "." oder ".." (noch nicht nullterminiert!)
;	VR: SI,DI,AL=0
	lea	si,[(TCD_DirEnt bx).fnamelen]
	lodsb
	cmp	al,2		;Länge 1?
	jnc	@@e
	cmp	[by si],2	;Null oder Eins?
	jnc	@@e
	lodsb
	or	al,al		;Null?
	mov	al,'.'
	stosb
	jz	@@1
	stosb			;Zwei Punkte wenn's 1 war
@@1:	add	al,-'.'		;AL=0 und CY setzen
	;stosb
@@e:	ret
endp

;proc CD_Prepare_CX
;FU: Hilfsroutine
;PE: AL=Zeichenzahl (ASCIIs oder UNICODEs)
;    BX=PCD_DirEnt
;PA: CX=Zeichenzahl (bei Dateinamen gekürzt um 2 für ";1"-Anhängsel)
;	mov	ah,0		;gegenüber CBW: Katastrophenschutz!
;	xchg	cx,ax		;Zeichen-Zähler in CX
;	test	[(TCD_DirEnt bx).flags],2	;Verzeichnis?
;	jnz	@@e		;ja, nicht kürzen!
;	sub	cx,2
;@@e:	ret
;endp

proc CD_Longname
;Kopiert Joliet-Namen des Directory-Eintrags BX in [Longname]
;Vereinfachende Annahme: Verzeichnisse haben kein Anhängsel,
;Dateien haben immer das Anhängsel ";1" (Versionsnummer)
;PE: BX=CD_DirEnt-Zeiger
;PA: SI=ofs Longname (also fertig für Vergleich)
;VR: AX,CX,SI,DI
	mov	di,ofs Longname
	push	di
	 call	CD_check_updir
	 jc	_termAL
	 shr	al,1		;bei CY=1 offensichtlich Fehler, ignorieren
	 movzx	cx,al
@@l:	 lodsw
	 call	BE_Uni2Oem	;hier: Motorola-Interpretation
;	 call	IsInvalidLfnChar
;	 jz	@@1
;	 stosb
@@1:	 loopnz	@@l
	 jmp	_terminate
endp

proc CD_Shortname
;Kopiert ISO-Namen des Directory-Eintrags BX in [ShortName]
;Da Zeichen >80h ohnehin nicht ISO-konform sind, werden sie vereinfachend
;als OEM angenommen
;Vereinfachende Annahme: Verzeichnisse haben kein Anhängsel,
;Dateien haben immer das Anhängsel ";1" (Versionsnummer), also 2 Zeichen
;PE: BX=CD_DirEnt-Zeiger
;PA: SI=ofs ShortName (also fertig für Vergleich)
;VR: AX,CX,SI,DI
	mov	di,ofs ShortName
	push	di
	 call	CD_check_updir
	 jc	_termAL
	 MIN	al,20		;Notbremse!! Nicht perfekt!!
				;Besser: Kürzung (TRUENAME) zu 8.3
	 movzx	cx,al
	 rep	movsb		;String einfach schaufeln
_terminate:
	 BTST	[(TCD_DirEnt bx).flags],2	;Verzeichnis?
	 jnz	@@1		;Nicht auf "Version" prüfen
	 mov	al,[di-1]
	 sub	al,'1'
	 cmp	al,9
	 jnc	@@1		;keine Ziffer 1..9
	 cmp	[by di-2],';'	;KANN schiefgehen, wenn nur 1 Ziffer!
	 jne	@@1
	 dec	di
	 dec	di
@@1:	 xchg	cx,ax		;effektiv AX=0
_termAL:
	 stosb			;Null-Terminierung
	pop	si
	ret
endp

proc CD_Get_Attr
;FU: Liefert DOS-Attribut für CD-Verzeichniseintrag
;    Liefert nur die Bits DIRECTORY (10h), HIDDEN (02h) aus DirEnt
;    und READONLY (01h) aus ctrl-Bit
;PE: BX=CD_DirEnt-Zeiger
;PA: AL=AX=Attribut
;VR: AX (AH=0)
	mov	al,[(TCD_DirEnt bx).flags]
	mov	ah,0
	shl	al,6
	add	ax,ax		;DIR-Bit einschieben
	shl	ah,2		;dazwischen 2 Bit Luft
	add	ax,ax		;HIDDEN-Bit einschieben, jetzt AL=0
	test	ah,8		;Directory?
	jnz	@@1		;niemals schreibgeschützt wie MSCDEX
	bt	[wo ctrl],1	;CTRL_RoBit-->CY
@@1:	xchg	ah,al
	adc	al,al		;RO-Bit einschieben (je nach Vorgabe)
	ret
endp

proc shld_proc
	shl	edx,cl
	or	dl,al
	lodsb			;gleich nächstes Element holen
	ret
endp

proc CD_Get_Time
;FU: Liefert DOS-Zeit von CD (das ist i.d.R. die Zeit der letzten Änderung)
;PE: BX=CD_DirEnt-Zeiger
;PA: EAX,DL=Zeit im DOS-Format (DH=Zeitzone)
;VR: EAX,EDX
	push	si bx cx
	 lea	si,[(TCD_DirEnt bx).year]
	 mov	bx,ofs shld_proc
	 lodsb			;Jahr (seit 1900)
	 sub	al,80		;1980-1900
	 mov	cl,7
	 call	bx		;7 Bits für's Jahr
	 mov	cl,4		;Monat 4 Bits
	 call	bx
	 inc	cl		;Tag 5 Bits
	 call	bx
	 call	bx		;Stunde auch 5 Bits
	 inc	cl		;Minute 6 bits
	 call	bx
	 shr	al,1		;Sekunde in 2-s-Schritten
	 sbb	ah,ah		;CY in AH 8x retten
	 dec	cl		;Sekunde 5 bits
	 call	bx
	 xchg	ah,al
	 and	al,100		;entweder 0 oder 100 (dezimal!)
	 xchg	edx,eax
	pop	cx bx si
	ret
endp


proc SetSuchSektor_LFN
;PE: BX=DirEnt-Zeiger ISO
;PA: [SuchSektor]=EAX=zu suchender Joliet-Sektor
;VR: EAX,CX
	db	0B9h		;mov cx,nnnn
SetSuchSektor_SFN:
;PE: BX=DirEnt-Zeiger Joliet
;PA: [SuchSektor]=EAX=zu suchender ISO-Sektor
;VR: EAX,CX
	xor	cx,cx		;2 bytes
	mov	eax,[(TCD_DirEnt bx).sect]
	BTST	[(TCD_DirEnt bx).flags],bit 1
	jz	@@e		;don't search for files!
	push	di bx
	 mov	di,[Medium.jol.linktbl]
	 mov	bx,[di]
	 shl	bx,2		;DWORDs (changes flags) = offset between
	 or	cx,cx
	 mov	cx,[di]
	 jnz	@@1
	 add	di,bx
	 neg	bx
@@1:	 inc	di
	 inc	di
	 repne	scasd		;fast scan for sector
	 jne	@@2		;if not found (error!)
	 mov	eax,[di+bx-4]	;get the companion
@@2:	pop	bx di
@@e:	mov	[SuchSektor],eax
	ret
endp

proc CD_Make_Volume_Label
;Jede CD habe ein Volume Label... oder?
	mov	eax,[Medium.jol.joltree.voldesc]
	call	ReadSecEAX

	call	InitFill
	mov	al,8		;Attribut "Volume Label"
	stosd
	mov	al,ah
	mov	cx,20
	rep	stosw		;10 DWords nur Nullen
	mov	si,ofs CD_Sektor+28h
@@l:	lodsw
	call	BE_uni2oem	;"langes" Label (ist nullterminiert)
	jnz	@@l
	LD	es,ds
	call	Alloc_Find_Handle
	mov	[Search.jol.j.entry],ofs CD_Sektor	;zurückstellen!
	call	PutValues
	clc
	ret
endp

proc CD_RetrieveDirInfo
;PE: BX=CD-Verzeichniseintrag
;    DI=Struktur aus Sektor (DWORD) und Anzahl (WORD)
;VR: EAX,DI (um 6 nach hinten)
	mov	eax,[(TCD_DirEnt bx).sect]
	stosd
	mov	eax,[(TCD_DirEnt bx).fsize]
	shr	eax,11		;genauer: durch Sektorlänge (krumm!) teilen
	stosw
	ret
endp

proc CD_LFN_Follow
	test	[PFlags],PF_Follow
	jz	@@1
	push	di
	 lea	di,[Search.jol.j.sect]
	 call	CD_RetrieveDirInfo
	pop	di
@@1:	mov	[Search.jol.j.entry],bx
	ret
endp

proc CD_SFN_Follow
	test	[PFlags],PF_Follow
	jz	@@1
	push	di
	 lea	di,[Search.jol.i.sect]
	 call	CD_RetrieveDirInfo
	pop	di
@@1:	mov	[Search.jol.i.entry],bx
	ret
endp

proc CD_LoadDirInfoFull
;PE: SI=Struktur aus Sektor (DWORD) und Anzahl (WORD)
;PA: BX, [CurSector] und [CD_Residual] gefüllt
	lodsw			;Sektorzeiger
	xchg	bx,ax
	lodsd
	mov	[CurSector],eax
	lodsw
	mov	[CD_Residual],ax
	ret
endp

proc CD_SaveDirInfoFull
;PE: DI=Struktur aus Sektor (DWORD) und Anzahl (WORD)
	mov	ax,bx
	stosw
CD_SaveDirInfo:
	mov	eax,[CurSector]
	stosd
	mov	ax,[CD_Residual]
	stosw
	ret
endp

proc CD_LFN_Load
;PA: BX, [CurSector] und [CD_Residual] mit Joliet-Suchdaten gefüllt
	push	si
	 lea	si,[Search.jol.j]
	 call	CD_LoadDirInfoFull
	pop	si
	ret
endp

proc CD_SFN_Load
;PA: BX, [CurSector] und [CD_Residual] mit ISO-Suchdaten gefüllt
	push	si
	 lea	si,[Search.jol.i]
	 call	CD_LoadDirInfoFull
	pop	si
	ret
endp

proc CD_Ping_DirScan
;FU: Bei CDFS muß zweimal gesucht werden! Entweder von lang nach kurz
;    (Regel) oder von kurz nach lang (Sonderfall)
;PE: BX=Vergleichsmethode für DirScan [GLOBBING reicht eigentlich!]
;PA: CY=1 fataler Fehler
;    CY=0 und Z=0: Nicht gefunden
;    CY=0 und Z=1: gefunden
	push	bx
	 call	CD_LFN_Load ;setze Suchdaten auf Joliet (warum BX verwerfen?)
	pop	bx
	call	DirScan
	jc	@@e
	call	CD_LFN_Follow		;Falls nächstes Verzeichnis...
	call	SetSuchSektor_SFN
	call	CD_SFN_Load
	mov	bx,ofs Match_CD_Sectorpointer_Proc
	call	DirScan			;mit DX=Nummer
	jc	@@e			;Fehler! (Konsistenzfehler)
	call	CD_SFN_Follow
	call	CD_Shortname
	clc
@@e:	ret
endp

proc CD_Pong_DirScan
;PA: CY=1 fataler Fehler oder nicht gefunden
;    CY=0 gefunden
	push	bx
	 call	CD_SFN_Load
	pop	bx
	call	DirScan
	jc	@@e
	call	CD_SFN_Follow
	call	SetSuchSektor_LFN
	call	CD_LFN_Load
	mov	bx,ofs Match_CD_Sectorpointer_Proc
	call	DirScan			;mit DX=Nummer
	jc	@@e			;Fehler!
	call	CD_LFN_Follow
	call	CD_Longname
	clc
@@e:	ret
endp

proc CD_Next_DirEnt
	add	bl,[(TCD_DirEnt bx).r]
	adc	bh,0
	cmp	bx,ofs CD_SektorEnde
	jnc	@@cknext
	cmp	[(TCD_DirEnt bx).r],SIZE TCD_DirEnt ;sector not filled up?
	jnc	@@e
@@cknext:
	dec	[CD_Residual]		;sector follows?
	stc
	jz	@@e
	call	ReadNextSec		;hier keine FAT-Ärgernisse!
	mov	bx,ofs CD_Sektor
@@e:	ret
endp

proc CD_NextDirScan_Circular
	push	bp
	mov	bp,sp
	push	[CurSector]	;bp-4
	push	bx		;bp-6
	call	ReadSec
	jc	@@e
	mov	bx,[bp-6]	;der DirEnt-Zeiger
	jmp	@@2
@@l:	call	[MatchPtr]
	jbe	@@e		;bei CY=1 (Fehler) oder Z=1 (gefunden)
@@2:	call	CD_Next_DirEnt
	jnc	@@1
	mov	eax,[Search.jol.restart]
	call	ReadSecEAX
	mov	bx,ofs CD_Sektor
	jc	@@e
	mov	ax,[Search.jol.restlen]
	mov	[CD_Residual],ax
@@1:	cmp	bx,[bp-6]
	jne	@@l
	mov	eax,[CurSector]
	cmp	eax,[bp-4]
	jne	@@l
	stc
@@e:	leave
	ret
endp

proc Match_CD_Sectorpointer_Proc
;Nicht einfach DirEnts (DX) abzählen!
	cmp	[(TCD_DirEnt bx).r],SIZE TCD_DirEnt
	jc	@@e
	mov	eax,[SuchSektor]
	cmp	eax,[(TCD_DirEnt bx).sect]	;Treffer?
	clc
@@e:	ret
endp

proc Glob_CD_LFN_Proc
;Bug oder Feature? Unter Win9x trifft der Suchausdruck "*1" sowohl
;"Programme von 1991" (LFN) als auch "WURSTE~1" (SFN für "Wurstegal")
;Wegen der Bereitstellung beider Namen erfordert die Funktion
;FindFirst/FindNext das ständige Bereithalten zweier Sektoren.
;Aber leider ist die Reihenfolge der DirEnts nicht zwangsweise gleich:-(,
;sodass der Aufwand, um ein DirEnt nicht zweimal zu finden, immens steigt!
	mov	ax,ofs CD_Longname
	cmp	[(TCD_DirEnt bx).r],SIZE TCD_DirEnt
	jc	@@e
	call	CD_CheckRootDot		;12/02
	jnc	@@e
	call	ax
	call	CD_Get_Attr
	call	Match_Attr
	jnz	@@e
	call	GlobbingEx
@@e:	ret
endp

;================ common buffer (to be moved into heap) ================
	align	2
tunnel	dw	0	;Zeiger auf Tunnel-Info (muss initialisiert sein!)
tunnel2	dw	0	;Kopie für MOVE-Vorgang

label Residente_Puffer byte

union TMedium
 jol	TDI_JOL	<>	;all specific medium Information
ends
Medium	TMedium	<>

union TSearch
 jol	TFI_JOL	<>	;all file find information
ends
Search	TSearch	<>

SearchAttr	dw	?	;Such-Attribut, enthält im Low-Teil
	;die invertierten Attribut-Flags und im High-Teil die
	;Must-Match-Attribute. Das Attribut 88h dient (später) dem
	;Aufsuchen gelöschter Einträge wie bei Novell-DOS
MatchPtr	dw	?	;"Virtuelle" Methode Match&Stop
SuchSektor	dd	?	;für gegenseitige Suche auf Joliet-CD
	;auch: Start-Sektor Ziel-Vrz. bei mkdir, creat und move
num_cluster	dw	?	;Anzahl Cluster für Verzeichnis

subst_root	dw	?	;Anzahl Zeichen für SUBST
		;in Truename: Zeiger in LongBuffer, normal LongBuffer+2
throw_fi dw	?		;Zeiger auf Exit-Behandlung (wegen lfn_move)
CD_Residual	dw	?	;count of directory sectors


;fastopen_cache
fastopen_ptr	dw	?
fastopen_buf	db	280 dup (?)	;Länge: 64+13 +256 +16*4: zu kurz!

;diverse Puffer
longname	db	256 dup (?)	;Puffer für LFN von NextDirEnt()
		db	256+28 dup (?)	;Platz für Unicode-LFN (Heap!!)
longbuffer	db	260 dup (?)	;Zwischenpuffer für Zerlegung/TRUENAME
label longbuffer_end byte
		db	?		;1 Byte hier spart 6 Bytes im Code
	alignv	4

Sektor		db	512 dup (?)	;0.5K Platz für Festplattensektor
label SektorEnde byte
Alloc_Cluster_SFN db	3+13 dup (?)	;(temporärer) Dateiname
Alloc_Cluster_FCB db	1+11+4 dup (?)	;ungeöffneter FCB - für Alloc_Cluster
DefLocalHeap_FATONLY dw	2 dup (?)
ISRE_FATONLY:

;	org Sektor
;cbsize		dw	?		;nur für GetDrvParam (FAT32)
;dpb		TExDPB	<?>		;nur für GetDrvParam (FAT32)
	org Sektor
;nur für Rückfallmodus benutzt
old_dta		dd	?
dta		TSearchRec	<?>

	org Sektor
CD_Sektor	db	2048 dup (?)	;2K für CDFS-Sektor
label CD_SektorEnde byte

CD_Backup	db	2048 dup (?)

CD_BackupSektor	dd	?

DefLocalHeap	dw	2 dup (?)
ISRE:

;==========================================================================
	org Residente_Puffer		;keine Null-Orgien!
;==== BEGIN CRITICAL INITIALIZATION SECTION - MUST OVERLAY BUFFERS ====
String_Table	dw	?
LocalHeapSize	dw	?	;Angabe bei /M
WorkDir		dd	?	;Arbeitsverzeichnis: .JLT/.TBL/.386 (/P)

proc LocalInit pascal
;PE: AX = size of local heap in bytes, including four "wasted" bytes
;    DI = start address of local heap
	add	ax,0Fh		;Bis zum Paragrafen-Ende aufrunden
	add	ax,di
	jnc	@@1
	mov	ax,0fff0h	;begrenzen auf berechenbare 64K-16
@@1:	shr	ax,4
	xchg	bx,ax
	DOS	4Ah		;Speicherblockgröße verändern
	jnc	@@2
	DOS	4Ah		;noch einmal (bei Fehler max. Größe so!)
@@2:	xchg	ax,bx
	shl	ax,4		;wieder Bytes
	sub	ax,di
	BRES	ax,3		;in 4-Byte-Häppchen abrunden! (Unnötig)
	mov	[LocalHeap],di
	mov	[LocalHeapSize],ax
	push	ax
	 lea	ax,[di+4]
	 stosw			;Erster Freispeicher-Zeiger
	 xor	ax,ax
	 stosw			;strategische Null
;verhindert auf einfache Weise das ungewollte "Zusammenfassen nach links"
	 stosw			;NIL-Pointer, kein weiterer Freispeicher
	pop	ax
	sub	ax,4
	stosw			;Zusammenhängender Freispeicher
	ret
endp

proc CopyWorkDir
;Kopiert [WorkDir] nach ES:DI und hängt ein Backslash dran,
;PA: DI=Zeiger hinter Backslash
	push	es
	push	di
	push	[WorkDir]
	call	fstrcpy		;liefert glücklicherweise AX=Zeichenzahl
	add	di,ax
	mov	al,'\'
	stosb
	ret
endp

proc CriticalInit
;"Kritischer Abschnitt" der Initialisierung (wegen Speicher-Überlappungen)
	call	LocalInit
;Arbeitsverzeichnis in Heap kopieren
	push	[WorkDir]	;gleich als DWord!
	call	fstrlen
	add	ax,14
	call	iLocalAlloc
	jc	@@noload	;darf hier nie passieren!
	mov	[argv0],di
	call	CopyWorkDir
	mov	[argv0file],di
;Unicodetabelle automatisch oder wie angefordert laden (nach Heap-Init!)
	mov	dx,0
UserUniFile = wo $-2		;Angabe bei /Z
	or	dx,dx
	jnz	@@userload	;Von Hand laden
	DOS	6601h
	jc	@@noload	;DOS weiß nichts über Codeseiten
	push	[argv0]		;Dateiname (mit Pfad) für Fehlermeldung
	 call	LoadCP
	 jmp	@@el1
@@userload:
	push	dx		;für Fehlermeldung
	 call	LoadUniFile
@@el1: 	 jnc	@@el2
	 call	AusgabeStringNr
	 call	AusgabeNL
@@el2:	pop	dx
@@noload:
;Zeiger verbiegen (Int21 und Int2F)
	mov	dx,ofs NewInt21
	DOS	2521h		;Set Int21
	mov	dx,ofs NewInt2F
	mov	al,2Fh
	DOS			;Set Int2F
;Environment freigeben
	push	es
	 mov	es,[2ch]	;Segment Environment
	 DOS	49h		;ENV-Speicher ab es freigeben
	pop	es
;Test des Hochladens und Anzeige
	mov	ax,cs
	cmp	ah,0a0h
	mov	bl,1		;$HOCH
	jc	@@NoHi		;unten
	call	AusgabeStringNr
@@NoHi:	inc	bl		;Installiere
;DosLFN aktivieren und Speicherverbrauch anzeigen
Activate:
	or	[es:Ctrl0],80h	;setzen
	mov	ax,[LocalHeap]
	add	ax,[LocalHeapSize]
	add	ax,4+1Fh	;hier: inklusive MCB
	BRES	ax,0Fh
	;BL-numerierten Text ausgeben
	P8086
TXTOut:	push	ax
	call	AusgabeStringNr	;Textausgabe
	pop	ax
	;Programm beenden
	mov	bh,bl
	call	AusgabeNL
	cmp	bh,2		;Meldung "Resident"?
	jnz	@@exi		;nein, normales Programmende
	P386
	mov	dx,[LocalHeap]
	add	dx,[LocalHeapSize]
	shr	dx,4		;Speicherbedarf in Paragrafen umrechnen
	DOS	3100h		;Resident beenden
@@exi:
	P8086
	call	PrintLastError
	DOS	4C00h
endp

proc LoadString
;FU: String-ID (BL) in String-Zeiger (SI) und Längen-Info (CX) umsetzen
;VR: AX,BL,CX,SI
	push	es di
	 inc	bl
	 xor	al,al
	 mov	di,[String_Table]	;deutsch oder englisch
	 LD	es,ds
@@l:	 mov	si,di			;immer Anfang merken
	 mov	cx,-1
	 repne	scasb			;Null suchen und Länge bestimmen
	 dec	bl
	 jnz	@@l
	 not	cx
	 dec	cx			;jetzt CX=String-Länge
	pop	di es
ple_ret:ret
endp

proc PrintLastError
;PE: ES = resident segment
;PA: LastError=0
;VR: AX,BL
	xor	ax,ax
	xchg	[es:LastError],al
	or	al,al
	jz	ple_ret
	mov	bl,FIRSTERRORSTRING
	push	ax
	 call	AusgabeStringNr		;"Fehler: %d "
	pop	ax
	add	bl,al
	call	AusgabeStringNr		;Fehlerbeschreibung
;	jmp	AusgabeNL
;@@e:	ret
endp

;*****************************
;** printf() Marke Eigenbau **
;*****************************

	P286
AusgabeNL:
	mov	bl,0
proc AusgabeStringNr c
;FU: String aus String-Tabelle mit Nummer BL ausgeben,
;    dabei Formatierung mit einer Mini-PRINTF-Funktion und 0A->0D0A-Expansion
local @@numberpuffer:BYTE:34,@@flags,@@space,@@preci
	pusha
	call	LoadString
	push	es
	 LD	es,ds
	 mov	di,ofs printf_buffer
	 lea	bx,[bp+4]
@@l:	 lodsb
	 or	al,al
	 jz	@@e
	 cmp	al,'%'
	 je	@@esc
	 cmp	al,0ah
	 je	@@0a
	 stosb
	 jmp	@@l
@@0a:
	 mov	ax,0a0dh
	 stosw
	 jmp	@@l
@@esc:
	 xor	ax,ax
	 mov	[BP-6],ax
	 mov	[BP-4],ax
	 mov	[BP-2],ax
	 call	EasyPrintfHandler
	 jmp	@@l
@@e:
	 mov	cx,di
	 mov	dx,ofs printf_buffer
	 sub	cx,dx			;Anzahl Zeichen
	 mov	bx,1			;stdout
	 DOS	40h			;schreiben (BlockWrite)
	pop	es
	popa
	ret
endp

;************ PRINTF: Komplette %-Behandllung ***************

SwitchChars	db	'LFNhl0.-+# '

	P386
proc PreprocessHandler
;NUR FÜR TINY-MODELL und 386
;verarbeitet eine Sequenz, zum Verketten gemacht!
;Kann alle Präprozessor-Sachen: #,0,-,*,Feldbreite,Präzision,h,l,N,F
;
;PE: DS:SI=Eingabedaten
;    ES:DI=Ausgabedaten
;    SS:BP-8=PRINTF-Daten: OLen,Flags,Space,Precis
;    SS:BX=Externe Daten (hier: für "*"-Feldbreiten-Platzhalter)
;PA: DS:SI->da geht's weiter
;    AL=(unbekanntes) Zeichen
;    CY=1 = konnte kein End-Zeichen umsetzen
@@l:	lodsb
	mov	cx,11
	push	di
	 mov	di,ofs SwitchChars
	 repne	scasb	;any known switch character?
	pop	di
	jne	@@nf
	bts	[bp-6],cx
	jmp	@@l
@@nf:
	cmp	al,'*'
	je	@@st
	cmp	al,'0'
	jc	@@gu
	cmp	al,'9'
	ja	@@gu
@@nu:			;scan the number
	dec	si
	push	bx
	 mov	bx,10
	 call	inw	;DS:SI->AX, DS:SI moved forward
	pop	bx
	jmp	@@nu0
@@st:
	mov	ax,[bx]	;get number from arglist
	inc	bx
	inc	bx
@@nu0:
	test	[by bp-6],bit 4
	jnz	@@pr
	mov	[bp-4],ax	;field width
	jmp	@@l
@@pr:
	mov	[bp-2],ax	;precision (not used if prec bit not given)
	jmp	@@l
@@gu:			;give up, unknown character (including zero)
	ret
endp

proc printf_FillChar
;PE: AX=number of characters
	xchg	cx,ax
	mov	al,' '
	test	[by bp-6],bit 5
	jz	@@1
	mov	al,'0'
@@1:	rep	stosb
	ret
endp

proc printf_strlen
;PE: DS:SI=String, AX=MaxLen (=precis oder 0FFFFh)
	push	di
	 mov	di,si
	 mov	cx,ax
	 push	ax
	  mov	al,0
	  repne	scasb
	  inc	cx
	 pop	ax
	 sub	ax,cx
	pop	di
	ret
endp

proc printf_postfill
	xor	[by bp-6],bit 3	;Bit kippen
endp
proc printf_prefill
;PE: AX=auszugebende Zeichenzahl
	test	[by bp-6],bit 3
	jnz	@@e		;left aligned: do nothing!
fill0:
	push	ax
	 sub	ax,[bp-4]
	 jnc	@@e0
	 neg	ax		;free width
	 call	printf_FillChar
@@e0:	pop	ax
@@e:	ret
endp


proc printf_itoa
;BL=Zahlenbasis; negativ wenn Zahl vorzeichenbehaftet
;EAX=Zahl
;[BP-6]=Flags, Bit 15=1 für große Hex-Buchstaben
;DI=ASCII-Puffer
;PA:DI=Ende Puffer (nicht terminiert!)
	mov	dx,[bp-6]
	xchg	ecx,eax
	or	bl,bl
	jns	@@1
	neg	bl		;jetzt positiv machen
	or	ecx,ecx
	mov	al,'-'
	js	@@putn
	test	dl,bit 2	;Merker "+"
	mov	al,'+'
	jnz	@@put
	test	dl,bit 0	;Merker " "
	mov	al,' '
	jnz	@@put
	jmp	@@1
@@putn:	neg	cx
@@put:	stosb
@@1:	test	dl,bit 1	;Merker "#"
	jz	@@2
	cmp	bl,8
	mov	al,'0'
	jne	@@no_8
	stosb
@@no_8:	cmp	bl,2
	mov	ah,'b'
	je	@@do_2
	cmp	bl,16
	mov	ah,'x'
	jne	@@2
@@do_2:	stosw
@@2:	xchg	ecx,eax		;wieder zurück!
	xor	cx,cx		;Zähler der PUSHes
	movzx	ebx,bl
@@l1:	inc	cx
	xor	edx,edx
	div	ebx
	push	dx		;eine Ziffer
	or	eax,eax
	jnz	@@l1
@@l2:	pop	ax		;herausholen in umgekehrter Reihenfolge
	add	al,'0'		;(Alternative: Puffer von hinten füllen!)
	cmp	al,'9'
	jbe	@@3
	add	al,7
	test	[by bp-5],bit 7
	jnz	@@3
	add	al,20h		;Kleinbuchstaben
@@3:	stosb
	loop	@@l2
	ret
endp

proc EasyPrintfHandler
	call	PreprocessHandler
	cmp	al,'%'		;muss als Extrawurst gebraten werden!
	je	@@perc
	push	si
	 cmp	al,'s'
	 je	@@s
	 lea	si,[bp-34-6]
	 cmp	al,'c'
	 je	@@c
	 mov	dl,-10
	 cmp	al,'d'
	 je	@@num
	 cmp	al,'i'
	 je	@@num
	 mov	dl,10
	 cmp	al,'u'
	 je	@@num
	 mov	dl,8
	 cmp	al,'o'
	 je	@@num
	 mov	dl,2
	 cmp	al,'b'		;Nicht Standard, aber sehr nützlich!
	 je	@@num
	 mov	dl,16
	 cmp	al,'x'
	 je	@@num
	 or	[by bp-5],bit 7
	 cmp	al,'X'
	 je	@@num
	 cmp	al,'p'		;Zeiger als große Hex-Zahlen ausgeben
	 je	@@num
	pop	si
	dec	si
	stc
	ret

@@perc:	stosb
	ret

@@num:	 mov	eax,[bx]
	 test	[by bp-6],bit 6
	 jnz	@@long
	 movzx	eax,ax
	 or	dl,dl
	 jns	@@nume
	 movsx	eax,ax
	 jmp	@@nume
@@long:	 inc	bx
	 inc	bx
@@nume:	 push	di bx
	  mov	di,si
	  mov	bx,dx
	  call	printf_itoa
	  sub	di,si		;Anzahl Zeichen
	  xchg	di,ax		;nach AX
	 pop	bx di
	 jmp	@@stout
@@c:
	 mov	si,bx		;Diese Adresse ist Quelle
	 mov	ax,1
	 jmp	@@stout
@@s:
	 mov	ax,[bx]
	 or	ax,ax		;NULL-Pointer?
	 jz	@@stout		;Dann nichts ausgeben!
	 xchg	si,ax
	 mov	ax,0FFFFh
	 test	[by bp-6],4	;precis given?
	 jz	@@1
	 mov	ax,[bp-2]	;use precis as maximum length!
@@1:	 call	printf_strlen
@@stout:	;Einsprung mit DS:SI=Stringzeiger, AX=String-Länge
	 inc	bx
	 inc	bx
	 call	printf_prefill
	 mov	cx,ax
	 rep	movsb
	 call	printf_postfill
	pop	si
	ret
endp
printf_buffer	=	$
;der Hilfe-String ist zwar wesentlich länger, aber damit endet das Programm,
;und nachfolgender Code wird nicht mehr benutzt.

Alt_String_Table =	$+80	;einige Strings im "kritischen Bereich"
;==== END CRITICAL INITIALIZATION SECTION ====

;==== dieser Init-Code wird von printf() überschrieben! ====
proc InstChk
;FU: Installations-Test
;PA: Bit7(CH)=0: Installationscheck erfolgreich, dann:
;    ES: Segmentadresse der residenten Routine, sonst =DS
;    Bit0(CH)=0: Zeiger Int21 nicht von anderen verbogen
;    Bit1(CH)=0: Zeiger Int2F nicht von anderen verbogen
;    DS:[OldInt21], DS:[OldInt2F]: gelesene Zeiger
;VR: AX,BX,CH,DX(=ES)
;Außerhalb von InstChk: Bit6(CH)=1 wenn irgendein Schalter akzeptiert
		mov	dx,REQcode
		DOS	REQfunc		;Install-Test
		xor	ch,ch
		cmp	ax,ANScode	;gleich?
		jz	@@T21I
		BSET	ch,bit 7
		mov	dx,ds
@@T21I:
		DOS	3521h		;Get Int21
		SES	[OldInt21],bx
		cmp	bx,ofs NewInt21
		jnz	@@T21FLT
		mov	bx,es
		cmp	bx,dx
		je	@@T21OK
@@T21FLT:	inc	ch		;Bit 0 setzen
@@T21OK:
		DOS	352Fh		;Get Int2F
		SES	[OldInt2F],bx
		cmp	bx,ofs NewInt2F
		jnz	@@T2FFLT
		mov	bx,es
		cmp	bx,dx
		je	@@T2FOK
@@T2FFLT:	BSET	ch,bit 1
@@T2FOK:
		mov	es,dx
		ret
endp

proc getargv0
;PA: [workdir] gesetzt
;    Eigener Programmname auf Pfad ohne Backslash gekürzt
;    AX=0
;VR: AX,CX,SI,DI
	push	es
	 mov	es,[2ch]	;Segment Environment
	 xor	di,di
	 xor	ax,ax
	 db	0B9h		;mov cx,< irgendeine Zahl > 7FFF >
@@such:	  repne	scasb
	 scasb
	 jnz	@@such
	 scasw			;number of "extensions"
	 jz	@@cannot	;no extension (DOS <3)
	 call	set_workdir
	 mov	si,di
	 repne	scasb
	 dec	di
	 mov	al,'\'
	 std
	 repne	scasb
	 cld
	 inc	di
	 mov	al,0
	 stosb			;make a path from file name
@@cannot:
	pop	es
	ret
endp

proc transient
;== 1. Meldung ==
	PRINT	Text0		;Meldung sofort
;== 2. Installations-Test ==
	call	InstChk		;setzt ggf. ES auf Fremdroutine
	;nun ch=Statusregister:
	;Bit0&1: Deinstallation nicht möglich
	;Bit4: Option Z gegeben
	;Bit5: Option M gegeben
	;Bit6: Schalter angegeben
	;Bit7:   Noch nicht installiert
	test	ch,bit 7
	jz	@@nostartup	;resident
;== 3. Standard-Sprache festlegen ==
	push	cx
	 mov	dx,ofs fname_buffer
	 DOS	3800h		;Land-Info holen
	 jc	@@k
	 xchg	bx,ax		;AX ist besser im Zugriff!
	 or	ah,ah
	 jnz	@@k
	 cmp	al,41		;Schweiz
	 je	@@de
	 cmp	al,43		;Österreich
	 je	@@de
	 cmp	al,49		;Deutschland
	 jne	@@k
@@de:	 mov	[language],'D'
@@k:
;== 4. argv[0] extrahieren, daraus Pfad für WorkDir basteln ==
	 call	getargv0
;== 5. Anwesenheit von MSCDEX prüfen und Vorgabe stellen ==
	 xor	bx,bx
	 MUX	15h		;AL=0
	 or	bx,bx
	 jz	@@nomscdex
	 BSET	[ctrl0],CTRL_CDROM
@@nomscdex:
	pop	cx
@@nostartup:
	 mov	al,[es:language]
	 call	SetStringResourcePointer
;== 6. Kommandozeile parsen und Aktionen durchführen ==
	 mov	si,81h
	 cld
;==== HIERHIN DARF DER LÄNGSTE STRING REICHEN! ====
@@scancl:lodsb
	 call	Upcase
	 push	es
	  push	ds
	  pop	es
	  mov	di,ofs cmd_verteiler
	  call	case
	 pop	es
	 jc	@@scancl
	 call	[wo di]
	 jmp	@@scancl
endp

;all diese Routinen dürfen SI (oder nur nach weiterer Parameter-
;Auswertung) und CH nicht verändern!
cmd_verteiler:	dvt	0dh,Install
		dvt	'?',help
		dvt	'H',help
		dvt	'U',UnInst
		dvt	'D',DisActiv
		dvt	'W',SetWrite
		dvt	'~',SetTilde
		dvt	'T',SetTunnel
		dvt	'C',SetCDROM
		dvt	'I',SetInDOS
		dvt	'R',SetRoBit
		dvt	'Z',LoadUni
		dvt	'M',SetHeapSize
		dvt	'L',SetLang
		dvt	'P',SetWorkDir
		dvt	'S',ShowStatus
		db	0

;**************************************
;* Kommandozeilen-Schalter-Behandlung *
;**************************************
	_INW3

proc skip_one_equal_colon_space
	lodsb
	cmp	al,':'
	je	@@e
	cmp	al,'='
	je	@@e
	cmp	al,' '
	je	@@e
	dec	si
@@e:	ret
endp

proc Expect_ASCIIZ
;FU: Parst Kommandozeile nach einem Dateinamen
;PE: SI=Zeiger nach Schalterzeichen
;PA: DX=Zeiger auf Dateiname
;    SI=Zeiger nach Nullterminierung des Dateinamens
;    Kommandozeile für Nullterminierung modifiziert
	call	skip_one_equal_colon_space
	mov	dx,si		;Dateiname (kurz)
@@l1:	lodsb
	cmp	al,21h		;Ende suchen
	jnc	@@l1
	mov	[by si-1],0	;terminieren!
	cmp	al,0Dh		;war letztes Argument?
	jne	@@1
	mov	[si],al		;0Dh verschieben nach hinten
@@1:	ret
endp

proc LoadUni
;BE: Unicode-Tabelle im Volkov-Commander-Tabellenformat (siehe TBL.TXT) laden
;    Hier noch nicht, erst nach Heap-Initialisierung möglich!
	call	Expect_ASCIIZ
	mov	[UserUniFile],dx
	BTST	ch,bit 7	;Resident?
	jnz	@@ex		;Nein, erst Heap initialisieren!
	push	dx		;für Fehlermeldung
	 DOS	3D00h		;zum Lesen öffnen
	 mov	bl,3		;"Kann nicht öffnen"
	 jc	@@e		;Datei nicht gefunden o.ä.
	 push	si cx ds
	  LD	ds,es
	  call	ReadUniFile
	 pop	ds cx si
@@e:	pop	ax
	jc	TxtOut0
@@ex:	BSET	ch,bit 4	;Merker, verhindert Auto-Load
	ret
endp

proc SetHeapSize
	mov	bl,29
	test	ch,bit 7	;Installiert?
	jz	txtout2		;ja, kann HeapSize (noch) nicht verändern!
	call	skip_one_equal_colon_space
	call	InW3		;Zahl einlesen
	mov	bl,23
	jc	txtout0
	cmp	ax,600		;1 Sektor und noch etwas Platz (nicht für CD)
	jc	txtout0
	cmp	ax,50000	;>50KB ist bei 64KB Segment kaum möglich
	ja	txtout0
	mov	[LocalHeapSize],ax
	BSET	ch,bit 5
	ret
endp

proc txtout2
	call	AusgabeStringNr
	mov	bl,31		;Hinweis
txtout0:jmp	txtout
endp

proc SetWorkDir
	mov	bl,28
	test	ch,bit 7
	jz	txtout2
	call	Expect_ASCIIZ
	mov	bl,27
	push	cx
	 mov	di,ofs truename_buf
	 push	si
	  mov	si,dx
	  DOS	60h
	 pop	si
	 jc	txtout0
	 mov	dx,di
	 DOS	4300h		;attrib->Prüfen auf Verzeichnis
	 jc	txtout0
	 test	cl,10h
	 jz	txtout0
	 push	di		;leidiges nachlaufendes Backslash entfernen,
	  call	strlenp1	;insbesondere wegen Interpretation von
	  sub	di,2		;"C:\\xyz" als Netzwerkressource xyz!
	  cmp	[by di],'\'	;Auch wenn jetzt nur "C:" übrig bleibt,
	  jne	@@1		;ein Backslash setzt die Software noch dran.
	  mov	[di],ah
@@1:	 pop	di
	pop	cx
set_workdir:
	SES	[WorkDir],di
	ret
endp

proc SetCDROM	;Schalter für CD-ROM-Unterstützung
	mov	bl,30
	test	ch,bit 7
	jz	txtout2
	mov	cl,CTRL_CDROM
	jmp	SetPlusMinus
endp
proc SetWrite	;Schalter für Schreibzugriff
	mov	cl,CTRL_Write
	jmp	SetPlusMinus
endp
proc SetTilde	;Schalter für Schlangen
	mov	cl,CTRL_Tilde
	jmp	SetPlusMinus
endp
proc SetTunnel	;Schalter für Tunneleffekt
	mov	cl,CTRL_Tunnel
	jmp	SetPlusMinus
endp
proc SetInDOS	;Schalter für InDOS-Flag-Benutzung
	mov	cl,CTRL_InDOS
	call	SetPlusMinus
	test	[es:ctrl0],CTRL_InDOS
	setnz	al
	mov	[es:InDosFlagInc],al	;Code patchen
	neg	al
	mov	[es:InDosFlagDec],al	;Code patchen
	ret
endp
proc SetRoBit	;Schalter für ReadOnly-Attribut bei CDFS
	mov	cl,CTRL_RoBit
	;jmp	SetPlusMinus
endp
proc SetPlusMinus
	lodsb
	cmp	al,'+'
	je	@@set
	cmp	al,'-'
	jne	help		;sonst Hilfeseite
	not	cl
	and	[es:ctrl0],cl
	jmp	@@e
@@set:	or	[es:ctrl0],cl
@@e:	BSET	ch,bit 6	;irgendein Schalter
	ret
endp

proc SetLang
	lodsb
	call	Upcase
	mov	[es:language],al
SetStringResourcePointer:
	cmp	al,'D'
	mov	ax,ofs Texte_deutsch
	je	@@de
	mov	ax,ofs Texte_englisch
@@de:	mov	[String_Table],ax
	ret
endp
	P8086

proc help	;Hilfe Option "H" oder "?", kein Return
	mov	ax,ofs Downl$
	push	ax		;8086!
	mov	ax,ofs Email$
	mov	bl,10
	jmp	TXTO1
endp

;********************
;** Deinstallation **
;********************

proc UnInst	;Deinstallation(sversuch) Option "U", kein Return
	mov	bl,7		;"noch nicht installiert"
	test	ch,bit 7
	jnz	TXTO1		;Wenn nicht nötig!
	test	ch,3
	jz	Raus
	mov	bl,5		;"deaktiviert"
	call	AusgabeStringNr
	inc	bl		;"Interrupt gestohlen"
disab:	and	[es:Ctrl0],not 80h	;löschen
	jmp	TXTOut
DisActiv:;Deaktivieren Option "D" oder Dirs ein/aus mit D+/D-
	test	ch,bit 7	;Schon installiert?
	mov	bl,7
	jnz	TXTO1
	mov	bl,5		;"deaktiviert"
	jr	disab
	;Deinstallation
Raus:	push	ds
	 lds	dx,[es:OldInt21]
	 DOS	2521h
	 lds	dx,[es:OldInt2F]
	 mov	al,2Fh
	 DOS
	pop	ds
	DOS	49h		;den Speicher ab es freigeben
	mov	bl,13		;"removed..."
TXTO1:	jmp	TXTOut
endp

;***********************
;** Statistik-Ausgabe **
;***********************

proc ShowStatus	;Status-Anzeige, kein Return
	test	ch,bit 7
	mov	bl,7		;"Noch nicht installiert"
	jnz	TXTOut
	P386
	mov	cl,[es:ctrl0]
	test	cl,80h
	mov	bl,5		;"deaktiviert"
	jz	TXTOut
	mov	bl,11
	call	AusgabeStringNr
	call	AusgabeNL
	call	AusgabeSchalter
	mov	bl,14
	mov	si,ofs counter_read
	seges	lodsw
	call	AusgabeZaehler
	inc	bl
	seges	lodsw
	call	AusgabeZaehler
	inc	bl
	seges	lodsw
	call	AusgabeZaehler
	call	AusgabeHeap
	call	PrintLastError
	DOS	4C00h
endp

proc AusgabeSchalter
;Alle Schalter ausgeben
	mov	cl,[es:ctrl0]
	mov	ch,40h
	mov	bl,17
@@l:	push	bx
	 call	AusgabeSch
	pop	bx
	ror	ch,1
	inc	bl
	cmp	bl,23
	jne	@@l
	ret
endp

proc AusgabeZaehler
;PE: BL=String-Nummer
;    AX=Zählerstand
	push	bx
	 push	ax
	 call	AusgabeStringNr
	 pop	ax
	 call	AusgabeNL
	pop	bx
	ret
endp

proc GetOnOffPtr
;PE: Z=0=EIN, Z=1=AUS
;PA: SI=String-Zeiger
;VR: AX,CX,SI
	push	bx
	 mov	bl,24		;"EIN"
	 jnz	@@1
	 inc	bl		;"AUS"
@@1:	 call	LoadString
	pop	bx
	ret
endp

proc AusgabeSch
	push	bx cx
	 test	cl,ch
	 call	GetOnOffPtr
	 push	si
	 call	LoadString
	 push	si
	 mov	bl,26		;"%xxs %s\n"
	 call	AusgabeStringNr
	 pop	cx
	 pop	cx
	pop	cx bx
	ret
endp

proc AusgabeHeap
;Einfacher HeapWalker, geht nur den freien Bereich durch!
	mov	si,es
	dec	si
	mov	ds,si		;MCB-Zeiger
	mov	ax,[3]		;Paragrafen
	shl	ax,4		;Bytes
	inc	si
	mov	ds,si
	mov	si,[LocalHeap]
	sub	ax,si
	sub	ax,4		;Zwangsbytes nicht mitzählen
	xchg	bx,ax		;SIZE
	xor	cx,cx		;FREE
	xor	dx,dx		;MAXAVAIL
@@l:	lodsw
	xchg	di,ax
	lodsw
	add	cx,ax
	cmp	dx,ax
	jnc	@@1
	xchg	dx,ax		;Maximum
@@1:	xchg	ax,di
	or	ax,ax
	xchg	si,ax
	jnz	@@l
	LD	ds,cs
	push	dx
	push	cx
	push	bx
	mov	bl,34
	call	AusgabeStringNr
	add	sp,6
	ret
endp
	P8086

;********************************
;** Installations-Vorbereitung **
;********************************

proc GetLocalHeapSize
;Berechnet erforderliche(?) Heap-Größe anhand der größten .JLT-Datei,
;die im Arbeitsverzeichnis von DOSLFN liegt
;PA: AX=Heap-Größe
	PUSHSTATE
	P386
	mov	ax,[LocalHeapSize]
	or	ax,ax
	jnz	@@e		;angegebene Größe (root weiß, was sie tut)
	lea	dx,[StdDTA]
	DOS	1Ah		;DTA nach hinten setzen
	lea	di,[fname_buffer]
	push	di
	 call	CopyWorkDir
	 mov	dx,[UserUniFile]
	 or	dx,dx
	 jnz	@@userload
	 DOS	6601h		;Codeseite holen
	 jc	@@noload
	 push	di
	  call	MakeTblFileName
	 pop	di
	 lea	dx,[fname_buffer]
@@userload:
	 mov	cx,7
	 DOS	4Eh		;FindFirst
	 jc	@@noload
	 mov	eax,[StdDta.fsize]
	 cmp	eax,50000	;Zu groß?
	 jna	@@load
@@noload:
	 xor	ax,ax
@@load:
	 add	ax,DEFHEAPSIZE	;"Pflichtteil" dazu
	pop	dx		;fname_buffer
	BTST	[ctrl0],CTRL_CDROM
	jz	@@e		;nur mit .TBL berechnet
	push	ax
	 lea	si,[jltfilter$]	;mit größter .JLT dazu
	 call	strcpy
	 mov	di,DEFHEAPSIZE_CD-DEFHEAPSIZE	;zusätzlicher "Pflichtteil"
	 mov	cx,7		;Versteckte Dateien gehen auch!
	 mov	ah,4Eh		;FindFirst
	 jmp	@@f
@@l:	 mov	eax,[StdDTA.fsize]
	 cmp	eax,32000	;Irreales Maß!
	 ja	@@1		;Ignorieren
	 cmp	ax,di
	 jc	@@1
	 xchg	di,ax		;neues Maß
@@1:	 mov	ah,4Fh		;FindNext
@@f:	 DOS
	 jnc	@@l
	pop	ax
	add	ax,di
@@e:	ret
	POPSTATE
endp

proc fstrlen pascal
arg @s:dword
uses es,di
	les	di,[@s]
	xor	ax,ax
	mov	cx,-1
	repne	scasb
	mov	ax,-2
	sub	ax,cx
	ret
endp

proc CheckWinVer
	MUX	160Ah		;Windows-Versionsnummer
	or	ax,ax
	jnz	@@e
	cmp	bh,4		;Zu hoch?
	mov	bl,33
	jc	@@e
_out:	jmp	TXTOut
@@e:	ret
endp

proc Install	;Installation oder Aktivierung, kein Return
	test	ch,bit 7
	jnz	@@test
	call	CheckWinVer
	mov	bl,12		;"reaktiviert"
	test	ch,bit 6	;irgendein Schalter auf Kommandozeile gewesen?
	jz	@@setab		;ohne, "reaktiviert"
	mov	bl,9		;mit,  "Schalter angenommen"
@@setab:jmp	Activate
@@test:				;hier: ES=DS
;Auf Mindest-Prozessor und -Betriebssystem testen
	mov	bp,cx		;retten
	IS386
	mov	bl,8		;"Test386 versagt"
	jc	_out
	P386
	DOS	30h		;DOS-Versionsnummer
	cmp	al,4		;wegen Int21/AH=6Ch
	mov	bl,32
	jc	_out
	call	CheckWinVer
;Zeiger auf InDOS-Flag und Gerätetreiber-Kette beschaffen
	push	es		;Brauch ich's?
	 DOS	34h		;InDOS-Flag-Adresse beschaffen
	 mov	[InDosFlagOfs],bx
;	 mov	[InDosFlagOfs2],bx
	 mov	[InDosFlagSeg],es
	 DOS	52h		;get NUL device driver address
	 add	bx,22h
	 cmp	[dword es:bx+10],' LUN'	;Is there actually a NUL header?
	 je	@@okdev
	 mov	bx,0FFFFh	;let scan for devices auto-terminate
@@okdev: SES	[DriverChain],bx
	pop	es
;Hand-Relokation im residenten Bereich
	mov	[wo high rwrec.addr],cs
	mov	[wo DPB_Drive],0FFh	;kein Laufwerk beim Start
	;mov	[DriveType],0	;Um Gottes Willen keinen Flush machen!
;Zeiger auf "filename uppercase table" beschaffen
	mov	bx,0FFFFh
	mov	cx,5
	mov	dx,bx
	mov	di,5Dh
	DOS	6504h
	mov	eax,[5Eh]
	sub	ax,7Eh
	mov	[uppercase_table],eax
;Zeiger auf "DBCS lead byte table" beschaffen
	DOS	6507h
	mov	eax,[5Eh]
	add	ax,2		;Länge übergehen
	mov	[lead_byte_table],eax
;5 kritische Strings umsetzen (in Sektor-Bereich)
	mov	bx,ofs String_Table
	mov	si,[bx]
	mov	di,ofs Alt_String_Table
	mov	[bx],di		;String_Table neu setzen
	mov	cx,5
@@l:	call	strcpy
	loop	@@l
;Initialisierung des Lokalen Heap vorbereiten
	call	GetLocalHeapSize
			;sollte auf Paragrafengrenze aufgerundet werden!
	mov	di,ofs DefLocalHeap
	BTST	[ctrl0],CTRL_CDROM
	jnz	@@a1
	mov	di,ofs DefLocalHeap_FATONLY
@@a1:	jmp	CriticalInit
endp

jltfilter$:	dz	"*.JLT"

;***********************
;** String-Ressourcen **
;***********************
;Die einzelnen Strings sind einfach durch \0-Zeichen voneinander getrennt
;und dicht an dicht hintereinander.
;Die ersten 5 Strings werden in einen sicheren Bereich kopiert,
;bevor der Heap (über die Strings hinweg) initialisiert wird.

FIRSTERRORSTRING = 35

Email$:	dz	"henrik.haftmann@e-technik.tu-chemnitz.de"
Downl$:	dz	"http://www.tu-chemnitz.de/~heha/hs_freeware/doslfn.zip"

Text0	db	"DOSLFN 0.32n (haftmann#software 01/03): $"

Texte_deutsch:
 dz	10							;0
 dz    "hoch"							;1
 dz    "geladen, verbraucht %u Bytes."				;2
 dz 10,"Kann Unicode-Datei %s nicht finden/öffnen!"		;3
 dz 10,"Falscher Inhalt der Datei %s oder Lese-Fehler!"		;4
 dz    "deaktiviert."						;5
 dz 10,"(Andere TSR stahl Int21 und/oder Int2F)"		;6
 dz    "Noch nicht installiert!"				;7
 dz    "Benötigt mindestens einen 386er Prozessor!"		;8
 dz    "Schalter angenommen."					;9
 db    "	(386+)	++ FREEWARE ++",10			;10
  db   "Programm für lange Dateinamen unter nacktem DOS.",10
  db   "Aktionen:	- (nichts)	TSR laden oder aktivieren",10
  db   "		- h oder ?	diese Hilfe",10
  db   "		- d		DOSLFN deaktivieren",10
  db   "		- s		Status und Einstellungen",10
  db   "		- u		TSR entfernen",10
  db   "Schalter:	- w{+|-}	* Schreibzugriffe",10
  db   "		- ~{+|-}	* Tilde (ich hasse Schlangen)",10
  db   "		- t{+|-}	* Tunneleffekt (für Editoren)",10
  db   "		- c{+|-}	* CDROM-Unterstützung",10
  db   "		- i{+|-}	* InDOS-Flag-Wiederaufrufsperre für TSRs",10
  db   "		- r{+|-}	* Schreibschutz-Attribut für CDROM-Dateien",10
  db   "		- z[:|=]table	Unicode-Tabelle (.TBL-Volkov-Format) laden",10
  db   "		- m[:|=]bytes	Größe des internen Heaps festlegen, 600..50000",10
  db   "		- p[:|=]path	Arbeitsverzeichnis (.TBL/.JLT/.386) festlegen",10
  db   "		- l{d|e}	Sprache setzen (deutsch|englisch)",10
  db   "Umgebung: 	TZ=xxxNyyy	Zeitzone N für Zeitumrechnung, ohne DST",10
  db   "				(nicht implementiert)",10
  db   "Email:	  %s",10
  dz   "Download: %s"

 dz    "aktiv"							;11
 dz    "reaktiviert."						;12
 dz    "vom Speicher entfernt."					;13
 dz    "%5u Lesezugriffe"					;14
 dz    "%5u Schreibzugriffe"					;15
 dz    "%5u Int21/AH=71-Aufrufe"				;16
 dz    "Schreibzugriffe"					;17
 dz    "Schlangen"						;18
 dz    "Tunneleffekt"						;19
 dz    "CDROM-Unterstützung"					;20
 dz    "InDOS-Flag-Verriegelung"				;21
 dz    "Schreibschutz-Attribut für CD-Dateien"			;22
 dz    "Ungültige Heap-Größe"					;23
 dz    "EIN"							;24
 dz    "AUS"							;25
 dz    "%37s %s",10						;26
 dz    "Verzeichnis existiert nicht!"				;27
 dz    "Kann Verzeichnis nicht setzen."				;28
 dz    "Kann Heap-Größe nicht verändern."			;29
 dz    "Kann Schalter nicht annehmen."				;30
 dz 10,"Dazu vorher TSR entfernen."				;31
 dz    "DOS4+ erforderlich!"					;32
 dz 10,"In einem DOS-Fenster dieser Windows-Version ist DOSLFN sinnlos!";33
 dz    "Heap: gesamt=%u, frei=%u, größter Block=%u Bytes",10	;34
 dz    "Letzter Fehler: %u - "					;35  =   0
 dz			"Verbotener Schreibzugriff"			;1
 dz			"Konnte Verzeichnis nicht expandieren"		;2
 dz			"Konnte Joliet-Link-Tabelle nicht finden"	;3
 dz			"Nicht genug Speicher - bitte vergrößern"	;4
 dz			"Konnte Unicode-Datei nicht laden"		;5

texte_englisch:
 dz	10							;0
 dz    "high "							;1
 dz    "loaded consuming %u bytes."				;2
 dz 10,"Cannot find/open Unicode table file %s!"		;3
 dz 10,"Wrong content of file %s or cannot read!"		;4
 dz    "disabled."						;5
 dz 10,"(Another TSR grabbed Int21 and/or Int2F)"		;6
 dz    "Not yet installed!"					;7
 dz    "Requires at least a 386 processor!"			;8
 dz    "switch(es) taken"					;9
 db    "	(386+)	++ FREEWARE ++",10			;10
  db   "Program that supports long filenames in pure DOS.",10
  db   "USE THIS PROGRAM AT YOUR OWN RISK, DATA LOSS MAY BE POSSIBLE",10
  db   "Actions:	- (nothing)	load and/or enable TSR",10
  db   "		- h or ?	this help",10
  db   "		- d		disable DOSLFN",10
  db   "		- s		show status and settings",10
  db   "		- u		unload TSR",10
  db   "Switches:	- w{+|-}	* write access",10
  db   "		- ~{+|-}	* NameNumericTail - tilde usage (I hate snakes)",10
  db   "		- t{+|-}	* PreserveLongNames - tunnel effect",10
  db   "		- c{+|-}	* CDROM support",10
  db   "		- i{+|-}	* reenter lock via InDOS flag",10
  db   "		- r{+|-}	* read-only bit for CDROM files",10
  db   "		- z[:|=]table	load Unicode table (format Volkov .TBL)",10
  db   "		- m[:|=]bytes	declare size of internal heap, 600..50000",10
  db   "		- p[:|=]path	declare working directory for .TBL/.JLT/.386",10
  db   "		- l{d|e}	set language (german|english)",10
  db   "Environment: 	TZ=xxxNyyy	time zone N for time conversion, no DST usage",10
  db   "				(not implemented)",10
  db   "email:	  %s",10
  dz   "Download: %s"

 dz    "active"							;11
 dz    "enabled."						;12
 dz    "removed from memory."					;13
 dz    "%5u read accesses"					;14
 dz    "%5u write accesses"					;15
 dz    "%5u Int21/AH=71 calls"					;16
 dz    "write access"						;17
 dz    "tilde usage"						;18
 dz    "tunnel effect"						;19
 dz    "CDROM support"						;20
 dz    "InDOS flag usage"					;21
 dz    "Read-Only bit set on CD files"				;22
 dz    "invalid heap size"					;23
 dz    "ON"							;24
 dz    "OFF"							;25
 dz    "%35s %s",10						;26
 dz    "directory doesn't exist!"				;27
 dz    "cannot set workdir"					;28
 dz    "cannot resize heap"					;29
 dz    "switch rejected"					;30
 dz			 " - unload TSR first"			;31
 dz    "requires at least DOS version 4!"			;32
 dz 10,"This program is useless in a DOS box of this Windows version!";33
 dz    "Heap: size=%u, free=%u, max-avail=%u Bytes",10		;34
 dz    "Last error: %u - "					;35  =   0
 dz			"user had denied write access"			;1
 dz			"couldn't expand FAT directory"			;2
 dz			"couldn't find a Joliet Link Table"		;3
 dz			"not enough memory - increase heap"		;4
 dz			"couldn't auto-load Unicode table"		;5

Texte_franzoesisch:
;	include	"francais.inc"

Texte_japanisch:
;	include	"nihongo.inc"


StdDTA		TSearchRec <>		;im transienten Teil
fname_buffer	db	80 dup (?)	;für Heapgrößenbestimmung
truename_buf	db	64 dup (?)	;für WorkDir

	endc
;**************************************************************************

Detected encoding: OEM (CP437)1
Wrong umlauts? - Assume file is ANSI (CP1252) encoded