This is a CON module of EuroTool program EuroDirs for Windows.
EUROASM CPU=x64, SIMD=yes, Unicode=no
dirswinc PROGRAM Format=COFF,Width=64
%DROPMACRO *
INCLUDEHEAD1 argument.htm
INCLUDE1 winabi.htm, cpuext64.htm, wins.htm, winscon.htm, winf64.htm, winsfile.htm
INCLUDE1 memory64.htm, sort64.htm, string64.htm, status32.htm, time.htm,
SCAN_INDEX STRUC ; A structure which describes one scaned directory.
.Ptr D DWORD ; Relative pointer to the directory name in UTF-8. Related to [NamesInMemory].
.Size D DWORD ; Size of the directory name in bytes.
.Volume D QWORD ; Total amount of bytes in all files in this directory.
ENDSTRUC SCAN_INDEX
CP_UTF8 EQU 65001
[.rodata] ; Text constants.
Ext_names DU ".names",0 ; The extension of temporary names file.
Ext_index DU ".index",0 ; The extension of temporary index file.
Ext_scan DU ".scan",0 ; The extension of scan file.
DateMaskW DU "????.??.??_??.??",0 ; File date mask in unichars.
Root DU "C:\",0 ; Initialization of Dir.
EuroDirs:: DB "EuroDirs",0
Version:: DB " version %^Date",0
Help:: DB 0xEF,0xBB,0xBF ; BOM UTF-8.
DB "; EuroDirs default configuration:",13,10
DB "/ScanNow=false ; Do not scan if /SN=false, compare scans instead.",13,10
DB "/ScanDir=""."" ; Directory for scan files.",13,10
DB "/TimeZone=0 ; Time zone of scan files in hours. /TZ=0 for UTC.",13,10
DB "/LeaveTemporary=no ; Do not erase *.name and *.index files (for debugging).",13,10
DB "/MaxDays=35 ; Older scan files will be deleted when /SN=true.",13,10
DB "/InputFile= ; Old scan file to compare. Default is the oldest in ScanDir.",13,10
DB "/OutputFile= ; New scan file to compare. Default is the newest in ScanDir.",13,10
DB "/ExcludeDirs= ; Do not scan dirs beginning with it. Default is empty.",13,10
DB "/IncludeDirs= ; Scan those dirs only. Default is scan all.",13,10
DB "/ViewDirs=Inc ; Ins|Inc|Unch|Dec|Del - select one of 5 views.",13,10
DB 0
Comparing DB 13,10,"Comparing directories from two scan files:",13,10,0
Report1:: DB " new created directories,",0
Report2:: DB " increased directories,",0
Report3:: DB " directories which did not change,",0
Report4:: DB " decreased directories,",0
Report5:: DB " deleted directories.",0
[.data] ; Text variables.
TodayW DU "yyyy.mm.dd_hh.mm",0 ; Today's date during the scan in unichars.
MaxDaysAgoW DU "yyyy.mm.dd_hh.mm",0 ; Today - ArgMaxDays in unichars.
TodayA DB "yyyy.mm.dd_hh.mm",0 ; Today's date during the scan in bytes.
MaxDaysAgoA DB "yyyy.mm.dd_hh.mm",0 ; Today - ArgMaxDays in bytes.
Disk DB "B" ; Disk letters when ArgIncludeDirs is not specified.
[.bss] ; Working memory variables.
SystemTime DS SYSTEMTIME ; A time structure.
FileTime DS FILETIME ; A time structure.
ScanIndex DS SCAN_INDEX ; A directory index.
File_ini DS FILE64 ; Configuration FILE64.
File_names DS FILE64 ; Temporary FILE64 for directory names (UTF-8).
File_index DS FILE64 ; Temporary FILE64 for directory indexes.
File_scan DS FILE64 ; Scan FILE64 (UTF-8), alphabetically sorted.
File_scanOlder:: DS FILE64 ; Older scan FILE64 to compare.
File_scanNewer:: DS FILE64 ; Newer scan FILE64 to compare.
Dir:: D 1K * UNICHAR ; The working directory which is scanned. UTF-16.
DirUTF8:: D 4K * BYTE ; The working directory translated to UTF-8.
InputFileNameUTF16:: D 264 * UNICHAR ; Input arguments translated to UTF-16.
OutputFileNameUTF16:: D 264 * UNICHAR
ScanDirUTF16:: D 264 * UNICHAR
IncludeDirsUTF16:: D 2K * UNICHAR
ExcludeDirsUTF16:: D 2K * UNICHAR
Total D QWORD ; Total size of all files and subdirectories.
NamesInMemory D QWORD ; Pointer to the contents of File_names mapped in memory.
NamesSize D QWORD ; Size of the contents of File_names mapped in memory.
IndexInMemory D QWORD ; Pointer to the contents of File_index mapped in memory.
IndexEndInMemory D QWORD ; Size of the contents of File_index mapped in memory.
IndexNumber D 24*B ; Number of subdirectories.
IncludedDirsBuffer D QWORD ; BUFFER for the parsed directories of ArgIncludedDirs.
IncludedDirsPtr D QWORD ; Pointer to IncludedDirsBuffer contents.
IncludedDirsEnd D QWORD ; Pointer to the end of Includ edDirsBuffer contents.
ExcludedDirsBuffer D QWORD ; BUFFER for the parsed directories of ArgExcludedDirs.
ExcludedDirsPtr D QWORD ; Pointer to ExcludedDirsBuffer contents.
ExcludedDirsEnd D QWORD ; Pointer to the end of ExcludedDirsBuffer contents.
ScanDirPtr D QWORD ; Pointer to ScanDirBuffer contents.
ScanDirEnd D QWORD ; Pointer to the end of ScanDirBuffer contents.
ScanDirNr D QWORD ; Number of scans in ScanDir.
OlderPtr D QWORD ; Pointer to the old scan mapped in memory, variable.
OlderInMemory D QWORD ; Pointer to the beginning of old scan mapped in memory.
OlderEndInMemory D QWORD ; Pointer to the end of old scan mapped in memory.
NewerPtr D QWORD ; Pointer to the new scan mapped in memory, variable.
NewerInMemory D QWORD ; Pointer to the beginning of new scan mapped in memory.
NewerEndInMemory D QWORD ; Pointer to the end of new scan mapped in memory.
ScanDirBuffer D QWORD ; BUFFER for the dates of old scans
Buffers:: ; Array of buffers with SCAN_INDEXes.
Buffer1 D QWORD ; Pointer to BUFFER with SCAN_INDEXes of inserted dirs.
Buffer2 D QWORD ; Pointer to BUFFER with SCAN_INDEXes of increased dirs.
Buffer3 D QWORD ; Pointer to BUFFER with SCAN_INDEXes of unchanged dirs.
Buffer4 D QWORD ; Pointer to BUFFER with SCAN_INDEXes of decreased dirs.
Buffer5 D QWORD ; Pointer to BUFFER with SCAN_INDEXes of deleted dirs.
BufferSelected:: D QWORD ; One of Buffer1..Buffer5 selected by a button or /ViewDirs.
MaxLineNr:: D DWORD ; Number of rows in the selected buffer.
ArgNr D DWORD ; Binary number of the current cmd-line argument.
IndexPtr D DWORD ; On the fly pointer in File_index.
FatDate D WORD ; Used in SetMaxDaysAgo.
FatTime D WORD ; Used in SetMaxDaysAgo.
Number1W:: D 40*BYTE ; Decimal number (UTF-16) of inserted dirs in Unichars.
Number2W:: D 40*BYTE ; Decimal number (UTF-16) of increased dirs in Unichars.
Number3W:: D 40*BYTE ; Decimal number (UTF-16) of unchanged dirs in Unichars.
Number4W:: D 40*BYTE ; Decimal number (UTF-16) of decreased dirs in Unichars.
Number5W:: D 40*BYTE ; Decimal number (UTF-16) of deleted dirs in Unichars.
Number1A: D 20*BYTE ; Decimal number (ANSI) of inserted dirs in bytes.
Number2A: D 20*BYTE ; Decimal number (ANSI) of increased dirs in bytes.
Number3A: D 20*BYTE ; Decimal number (ANSI) of unchanged dirs in bytes.
Number4A: D 20*BYTE ; Decimal number (ANSI) of decreased dirs in bytes.
Number5A: D 20*BYTE ; Decimal number (ANSI) of deleted dirs in bytes.
NumberA D 20*BYTE ; A working space for numbers. Zero terminated, e. g. "1234567",0.
Number_A D 26*BYTE ; Number same as NumberA but with separators '_', e. g. "1_234_567",0.
[.text]
MainCon:: PROC
StdOutput EuroDirs, Version, Eol=yes, Unicode=off
; Try to load arguments from the configuration file "%AppData%\eurotool\eurodirs.ini".
WinABI GetEnvironmentVariableW,=U'AppData',Dir,SIZE# Dir,Unicode=yes
TEST RAX
JZ .10:
FileAssign File_ini,Dir,=U'\eurotool\eurodirs.ini',Unicode=yes
FileExists? File_ini
JNC .13:
FileMkDir File_ini
JC .10:
LEA RDI,[Help] ; Online help and default configuration.
GetLength$ RDI, Unicode=no
FileStore File_ini, RDI,RCX
JNC .13:
.10:StdOutput =U'Configuration "',File_ini.Name,=U'" could not be saved.',Eol=yes, Console=yes, Unicode=yes
JMP .20:
.13:CALL ReadFile_ini
.20:; And finally read arguments from the command-line.
INC [ArgNr] ; The next argument.
GetArg [ArgNr],Unicode=yes ; RSI,RCX is the line with one argument in UTF-16, e. g. /ScanDir="C:\Eurodirs"
JC .30: ; When they are no more arguments.
SHR RCX,1
LEA RDI,[DirUTF8]
WinABI WideCharToMultiByte,CP_UTF8,0,RSI,RCX,RDI,SIZE#DirUTF8/2,0,0
MOV RSI,RDI
GetLengthUTF8 RDI
CALL ArgParse:: ; Use ArgParse to translate UTF-8 string RSI,RCX to a public symbol Arg***.
JNC .20:
LEA RSI,[Help+3]
StdOutput ErrorMessage::,RSI,Unicode=no ; On error write help and exit.
TerminateProgram 8
.30:; Create buffers for SCAN_INDEX objects.
BufferCreate Size=16K
JC ErrorBuffer
MOV [ScanDirBuffer],RAX
BufferCreate Size=4M
JC ErrorBuffer
MOV [Buffer1],RAX
BufferCreate Size=4M
JC ErrorBuffer
MOV [Buffer2],RAX
BufferCreate Size=8M
JC ErrorBuffer
MOV [Buffer3],RAX
BufferCreate Size=4M
JC ErrorBuffer
MOV [Buffer4],RAX
BufferCreate Size=4M
JC ErrorBuffer
MOV [Buffer5],RAX
CALL Arg2Gui
JSt [Status::],ArgScanNow,.80: ; Should the computer be scanned now?
CALL LoadAndCompareScans: ; Compare older and newer scans, fill the five buffers.
JMP MainGui::
.80:CALL Scan: ; Compute and save the Scan and then terminate.
.Terminate::
FileClose File_scanOlder, File_scanNewer
BufferDestroy [ScanDirBuffer],[Buffer1],[Buffer2],[Buffer3],[Buffer4],[Buffer5]
TerminateProgram 0
ENDP MainCon::
Procedure Scan is called when an argument /ScanNow=yes was detected. EuroDirs is usually called with this argument from TaskScheduler daily or weekly and it creates a scan file "eurodirs_YYYY.MM.DD_HH.MM.scan" in the directory specified by /ScanDir=
Scan: PROC ; Scan now.
CALL SetToday
CALL SetMaxDaysAgo
FileAssign File_scan,ScanDirUTF16::,DateMaskW,Ext_scan,Unicode=yes
FileEach File_scan,EraseOlderFile ; Erase files older than ArgMaxDays.
; Open temporary files with extensions .names, .index, .scan
.
LEA RBX,[File_names]
FileAssign RBX,ScanDirUTF16,TodayW,Ext_names,Unicode=yes
FileCreate RBX
JC ErrorFile:
LEA RBX,[File_index]
FileAssign RBX,ScanDirUTF16,TodayW,Ext_index,Unicode=yes
FileCreate RBX
JC ErrorFile:
LEA RBX,[File_scan]
FileAssign RBX,ScanDirUTF16,TodayW,Ext_scan,Unicode=yes
FileCreate RBX
JC ErrorFile:
; Prepare excluded directories.
BufferCreate Size=4K
MOV [ExcludedDirsBuffer],RAX
LEA RSI,[ExcludeDirsUTF16]
CALL ParseToBuffer
BufferRetrieve [ExcludedDirsBuffer]
LEA RDX,[RSI+RCX]
MOV [ExcludedDirsPtr],RSI
MOV [ExcludedDirsEnd],RDX
; Scan included dirs (if not empty) or all directories otherwise. Omit excluded dirs.
LEA RSI,[IncludeDirsUTF16]
CMPW [RSI],0
JZ .20: ; Included directories are not specified; jump to scan all.
BufferCreate Size=4K
MOV [IncludedDirsBuffer],RAX
CALL ParseToBuffer
BufferRetrieve [IncludedDirsBuffer]
LEA RDX,[RSI+RCX]
MOV [IncludedDirsPtr],RSI
MOV [IncludedDirsEnd],RDX
.10:MOV RSI,[IncludedDirsPtr]; Scan the next directory from IncludedDirs.
CMP RSI,[IncludedDirsEnd]
JNB .40:
LODSQ ; 1st QWORD is a size, 2nd QWORD is a pointer to a directory name.
MOV ECX,EAX
LODSQ
MOV [IncludedDirsPtr],RSI
MOV RSI,RAX
LEA RDI,[Dir]
JRCXZ .10:
REP MOVSW ; Included directory will be put to Dir.
XOR EAX,EAX
STOSD
CALL ExcludeDir ; Skip if Dir is one of the excluded directories.
JZ .10:
LEA RDI,[Dir]
StdOutput =U"Scanning directory """,RDI,=U""", please wait...",Eol=yes,Console=yes,Unicode=yes
CALL DirVolume ; The main recursive procedure will scan Dir and all its subdirectories.
JZ .10: ; ZF=1 when the directory is excluded.
ADD [Total],RAX ; Otherwise RAX is the total volume of Dir.
CALL StoreIndex ; Write Dir to File_names and File_index.
JMP .10:
.20:; IncludedDirs are empty, scan directories from all disks.
MOV RAX,[Root] ; DU "C:\",0
LEA RDI,[Dir]
MOV [RDI],RAX
.30:MOV AL,[Disk] ; DB "B"
INC AL
CMP AL,'Z'
JA .40:
LEA RDI,[Dir]
MOV [Disk],AL ; Rewrite "C:\" to "D:\" etc.
MOV [RDI],AL
WinABI GetDriveTypeW,RDI
CMP AL,DRIVE_FIXED
JNE .30:
CALL ExcludeDir
JZ .30:
LEA RDI,[Dir]
StdOutput =U"Scanning directory """,RDI,=U"""",Eol=yes,Console=yes,Unicode=yes
CALL DirVolume ; The main recursive procedure will scan Dir and all its subdirectories.
JZ .30: ; ZF if Dir does not exist or is excluded.
ADD [Total],RAX ; Volume of the Dir.
CALL StoreIndex
JMP .30: ; Go to the next disk.
.40:MOV RBX,[IncludedDirsBuffer]
BufferDestroy RBX
MOV RBX,[ExcludedDirsBuffer]
BufferDestroy RBX
FileClose File_index,File_names ; DirVolume was now scanned and the result put into File_names and File_index.
LEA RBX,[File_names]
FileMapOpen RBX
JC ErrorMap:
MOV [NamesInMemory],RSI
MOV [NamesSize],RAX
LEA RBX,[File_index]
FileMapCreate RBX
JC ErrorMap:
MOV [IndexInMemory],RDI
ADD RDI,RAX
MOV [IndexEndInMemory],RDI
SHR RAX,4 ; SIZE# SCAN_INDEX = 16.
MOV [IndexNumber],RAX ; Number of scanned directories.
LEA RDI,[NumberA]
MOV RBX,RDI
StoD RDI
MOVB [RDI],0
StdOutput =B'Sorting ',RBX,=B' directories...',Unicode=off,Eol=yes
LEA RDX,[ByName:]
MOV RSI,[IndexInMemory]
MOV RAX,[IndexNumber]
ShellSort RSI,RAX,SIZE#SCAN_INDEX,RDX
StdOutput =B'Writing scan "', Unicode=no
StdOutput File_scan.Name, =U'"', Unicode=yes, Console=yes, Eol=yes
.60:CMP RSI,[IndexEndInMemory]
JAE .80:
MOV RAX,[RSI+SCAN_INDEX.Volume]
LEA RDI,[NumberA]
MOV RBX,RDI
StoD RDI
SUB RDI,RBX
MOV EDX,[RSI+SCAN_INDEX.Ptr]
MOV ECX,[RSI+SCAN_INDEX.Size]
ADD RDX,[NamesInMemory]
FileWrite File_scan,RDX,RCX,=B(9),1,RBX,RDI,=B(10),1 ; DirNameInUTF8, HT, DirVolume, LF.
LEA RBX,[File_scan]
JC ErrorWrite
ADD RSI,SIZE# SCAN_INDEX
JMP .60:
.80:FileClose File_names, File_index, File_scan
JSt [Status::],ArgLeaveTemporary,.90:
FileDelete File_names, File_index
.90:StdOutput =B"Scan terminated.", Unicode=off, Eol=yes
RET
ENDP Scan:
DirVolume: PROC ; Recursively called procedure which scans one directory.
XOR EAX,EAX
PUSH RBX,R12,RBP,RAX
SUB RSP,SIZE# WIN32_FIND_DATAW
MOV RBP,RSP ; RBP: ^WIN32_FIND_DATAW.
LEA RDI,[Dir]
MOV ECX,SIZE# Dir
XOR EAX,EAX
REPNE SCASW ; Search for the terminating zero.
SUB RDI,2
MOV AX,'\'
CMP [RDI-2],AX
JE .10:
STOSW ; Make the folder name always backslash terminated.
.10: MOV R12,RDI ; R12: behind the last \ in Dir.
MOV EAX,'*'
STOSD
LEA RCX,[Dir]
WinABI FindFirstFileW,RCX,RBP
MOV RBX,RAX ; RBX=FindHandle.
CMP RAX,INVALID_HANDLE_VALUE
JE .90:
MOVW [R12],0 ; Erase the asterix.
.20: JSt [RBP+WIN32_FIND_DATAW.FileAttributes],FILE_ATTRIBUTE_DIRECTORY,.40:
; The name is a file.
MOV EAX,[RBP+WIN32_FIND_DATAW.FileSizeLow]
MOV EDX,[RBP+WIN32_FIND_DATAW.FileSizeHigh]
SAL RDX,32
ADD RAX,RDX
ADD [RBP+SIZE# WIN32_FIND_DATAW],RAX
.30: WinABI FindNextFileW,RBX,RBP
TEST RAX,RAX
JNZ .20:
JMP .90:
.40: ; The name is a directory.
LEA RSI,[RBP+WIN32_FIND_DATAW.FileName] ; ReturnRAX (Total) on machine stack.
MOV EAX,'.'
CMP [RSI],EAX
JE .30: ; Ignore the directory "."
SHL EAX,16
MOV AL,'.'
CMP [RSI],EAX
JNE .50:
CMPW [RSI+4],0
JE .30: ; Ignore the directory ".."
.50: GetLength$ RSI, Unicode=yes
MOV RDI,R12
REP MOVSB
SUB RAX,RAX
STOSW ; Let Dir be the new directory.
CALL ExcludeDir:
JNZ .60:
MOVW [R12],0
JMP .30:
.60: CALL DirVolume ; Recursive call itself.
ADD [RBP+SIZE# WIN32_FIND_DATAW],RAX ; Returned RAX (Total) on machine stack.
CALL StoreIndex:
JMP .30:
.90: MOVW [R12],0
WinABI FindClose,RBX,Fastmode=enabled
ADD RSP,SIZE# WIN32_FIND_DATAW
CMP RBX,INVALID_HANDLE_VALUE
POP RAX,RBP,R12,RBX
RET
ENDP DirVolume:
StoreIndex: PROC
MOV [ScanIndex.Volume],RAX
LEA RSI,[Dir]
LEA RDI,[DirUTF8]
XOR EAX,EAX
.20:LODSW
CMP AX,0
JE .80:
CMP AX,0xD800
JB .50:
CMP AX,0xE000
JAE .50:
SUB AX,0xD800 ; High surrogate character.
SHL EAX,10
MOV EDX,EAX
LODSW
CMP AX,0
JE .80:
SUB AX,0xDC00
JNA .40:
CMP AX,0x03FF
JA .40:
ADD EAX,EDX
EncodeUTF8 ; Use macro EncodeUTF8.
XOR EAX,EAX
JMP .20:
.40:MOV EAX,'?'
.50:EncodeUTF8
JMP .20:
.80:LEA RDX,[DirUTF8]
MOV R8,RDI
SUB R8,RDX
MOV [ScanIndex.Size],R8D
MOV EAX,[IndexPtr]
MOV [ScanIndex.Ptr],EAX
ADD [IndexPtr],R8D
MOV R11,RBX
LEA RBX,[File_names]
FileWrite RBX,RDX,R8 ; Write the directory name (UTF8) to the File_names.
JC ErrorWrite:
LEA RBX,[File_index]
FileWrite RBX,ScanIndex,SIZE#SCAN_INDEX ; Write Index to the nxdFile.
JC ErrorWrite:
MOV RBX,R11
RET
ENDP StoreIndex:
Global procedure LoadAndCompareScans checks arguments.
If /ScanDir is empty it fills it with .
.
If /InputFile is empty (default), it assigns it with the oldest scan from /ScanDir.
If /OutputFile is empty (default), it assigns it with the newest scan from /ScanDir.
If the name of oldest scan is greater then the name of newest scan, it will swap them.
If less than two scans are in /ScanDir, LoadAndCompareScans ends with CF=1.
By default it selects the oldest and the newest scan in ScanDir.
The procedure fills five buffers of ScanFiles indexes with differences between those two scans.
LoadAndCompareScans:: PROC
CALL Arg2Gui
; Clear buffers from previous run of LoadAndCompareScans procedure.
BufferClear [ScanDirBuffer],[Buffer1],[Buffer2],[Buffer3],[Buffer4],[Buffer5]
; Load dates of all scans which are in ScanDirUTF16 into a ScanDirBuffer.
FileAssign File_scan,ScanDirUTF16,DateMaskW,Ext_scan,Unicode=yes
FileEach File_scan,StoreFileName ; Store the date from file names to ScanDirBuffer.
FileClose File_scan
BufferRetrieve [ScanDirBuffer]
CMP ECX,2*16*2 ; At least two scans are required.
JAE .20:
StdOutput =B"At least two scan files are required in /ScanDir=""",Unicode=no
StdOutput ScanDirUTF16,=U"""",Unicode=yes,Console=yes,Eol=yes
STC
JMP .90:
.20:MOV RAX,RCX
ADD RCX,RSI
SHR RAX,4+1 ; Each item in ScanDirBuffer has 16*2 bytes.
MOV [ScanDirPtr],RSI
MOV [ScanDirEnd],RCX
MOV [ScanDirNr],RAX
ShellSort RSI, RAX, 2*16, ByDate
FileClose File_scanOlder,File_scanNewer ; Close files, if opened by previous comparison.
; If InputFileNameUTF16 is specified, it will be used as File_scanOlder.
LEA RBX,[File_scanOlder]
LEA RDI,[InputFileNameUTF16]
MOV RDX,RDI
CMPW [RDI],0
JNE .25:
MOV RSI,[ScanDirPtr]
MOV ECX,16*2
REP MOVSB
SUB EAX,EAX
STOSW
FileAssign RBX,ScanDirUTF16::,RDX,Ext_scan,Unicode=yes ; The first (oldest) scan.
JMP .30:
.25:FileAssign RBX,RDX,Unicode=yes
.30:FileMapOpen RBX
JC ErrorMap:
TEST RAX
JZ ErrorZero:
MOV [OlderPtr],RSI
MOV [OlderInMemory],RSI
ADD RAX,RSI
MOV [OlderEndInMemory],RAX
; If OutputFileNameUTF16 is specified, it will be used as File_scanNewer.
LEA RBX,[File_scanNewer]
LEA RDI,[OutputFileNameUTF16]
MOV RDX,RDI
CMPW [RDI],0
JNE .35:
MOV RSI,[ScanDirEnd]
SUB RSI,16*2
MOV ECX,16*2
REP MOVSB
SUB EAX,EAX
STOSW
FileAssign RBX,ScanDirUTF16::,RDX,Ext_scan,Unicode=yes ; The last (newest) scan.
JMP .40:
.35:FileAssign RBX,RDX,Unicode=yes
.40:FileMapOpen RBX
JC ErrorMap:
TEST RAX
JZ ErrorZero:
MOV [NewerPtr],RSI
MOV [NewerInMemory],RSI
ADD RAX,RSI
MOV [NewerEndInMemory],RAX
; Check if newer > older.
RstSt [Status::],ArgSwappedDates
LEA RSI,[File_scanNewer.Name]
LEA RDI,[File_scanOlder.Name]
GetLength$ RDI,Unicode=yes
MOV EDX,ECX
GetLength$ RSI,Unicode=yes
CMP ECX,EDX
JNE .45:
SHR ECX,1
REPE CMPSW
JGE .45:
SetSt [Status::],ArgSwappedDates
.45:; Comparing two scan files starts.
StdOutput Comparing,Unicode=no
StdOutput =U"Old file: ",File_scanOlder.Name,Eol=yes,Console=yes, Unicode=yes
StdOutput =U"New file: ",File_scanNewer.Name,Eol=yes,Console=yes, Unicode=yes
JNSt [Status::],ArgSwappedDates,.50:
StdOutput =B"Warning: old scan is younger than the new scan.",Eol=yes,Unicode=no
.50:; Start to compare File_scanOlder and File_scanNewer.
.LoadBoth:CALL LoadNewer: ; Output: R8=Volume,RBP=RSI=Pointer to a dir-name,RCX=R12=Size or -1 when at EOF.
.LoadOld: CALL LoadOlder: ; Output: R9=Volume,RBX=RDI=Pointer to a dir-name,RDX=Size or -1 when at EOF.
.Compare: CMP RCX,-1
JNE .55:
; File_scanNewer is over or higher. Keep loading File_scanOlder, store it to Buffer5.
CMP RDX,-1
JE .EndCompare: ; Both file are at end.
JMP .NewIsHigher:
.55:CMP RDX,-1
JE .OldIsHigher: ; File_scanOlder is over or higher. Keep loading File_scanNewer, store it to Buffer1.
CMP ECX,EDX
JBE .60:
MOV ECX,EDX
.60:REPE CMPSB ; Compare at the matching size.
MOV RDI,RBX
JB .OldIsHigher:
JA .NewIsHigher:
CMP R12,RDX ; When names match, look at their sizes.
JB .OldIsHigher:
JA .NewIsHigher:
; NoneIsHigher; Directory names are equal.
; ScanIndex goes to Buffer2,3 or 4. SCAN_INDEX.Ptr is relative to [NewerInMemory].
SUB RBP,[NewerInMemory]
MOV [ScanIndex.Ptr],EBP
MOV [ScanIndex.Size],EDX
SUB R8,R9 ; Volume difference is in R8.
MOV [ScanIndex.Volume],R8
JNE .75:
MOV RBX,[Buffer3]
MOV [ScanIndex.Volume],R9
JMP .80:
.75:MOV RBX,[Buffer4]
JB .80:
MOV RBX,[Buffer2]
.80:BufferStore RBX,ScanIndex,SIZE# ScanIndex
JC ErrorBuffer
JMP .LoadBoth:
.NewIsHigher: ; Line retrieved from File_scanNewer is higher or it already ended.
; Keep the line (in R8,RBP,R12) and store the index of line retrieved from File_scanOlder (R9,RBX=RDI,RDX).
; ScanIndex goes to Buffer5, SCAN_INDEX.Ptr is relative to [OlderInMemory].
SUB RBX,[OlderInMemory]
MOV [ScanIndex.Ptr],EBX
MOV [ScanIndex.Size],EDX
NEG R9
MOV [ScanIndex.Volume],R9
MOV RBX,[Buffer5]
BufferStore RBX,ScanIndex,SIZE# ScanIndex
JC ErrorBuffer
MOV RSI,RBP
MOV RCX,R12
JMP .LoadOld:
.OldIsHigher:; Line retrieved from File_scanOlder is higher or it already ended.
; Keep the line (in R9,RBX=RDI,RDX) and store the index of line retrieved from File_scanNewer (R8,RBP=RSI,R12=RCX).
; ScanIndex goes to Buffer1, SCAN_INDEX.Ptr is relative to [NewerInMemory],
SUB RBP,[NewerInMemory]
MOV [ScanIndex.Ptr],EBP
MOV [ScanIndex.Size],R12D
MOV [ScanIndex.Volume],R8
MOV R10,RBX
MOV RBX,[Buffer1]
BufferStore RBX,ScanIndex,SIZE# ScanIndex
JC ErrorBuffer
MOV RBX,R10
CALL LoadNewer:
JMP .Compare:
.EndCompare:
Nr %FOR 1..5 ; Set the lengths of five buffers to NumberxA and NumberxW.
BufferRetrieve [Buffer%Nr]
MOV EAX,ECX
SHR EAX,4
LEA RDI,[Number%Nr{}A]
MOV RSI,RDI
StoD RDI
XOR EAX,EAX
STOSB
LEA RDI,[Number%Nr{}W]
.8%Nr: LODSB
STOSW
CMP AL,0
JNE .8%Nr:
%ENDFOR Nr
SetSt [Status::],ArgDescending
MOV R8,[NewerInMemory]
BufferRetrieve [Buffer1]
SHR ECX,4 ; Let RCX=number of SCAN_INDEX records (16 bytes each).
ShellSort RSI,RCX,16,ByVolume ; ArgDescending, R8=NewerInMemory
BufferRetrieve [Buffer2]
SHR ECX,4 ; Let RCX=number of SCAN_INDEX records (16 bytes each).
ShellSort RSI,RCX,16,ByVolume ; ArgDescending, R8=NewerInMemory
BufferRetrieve [Buffer3]
SHR ECX,4 ; Let RCX=number of SCAN_INDEX records (16 bytes each).
ShellSort RSI,RCX,16,ByVolume ; ArgDescending, R8=NewerInMemory
RstSt [Status::],ArgDescending
BufferRetrieve [Buffer4]
SHR ECX,4 ; Let RCX=number of SCAN_INDEX records (16 bytes each).
ShellSort RSI,RCX,16,ByVolume ; ArgAscending, R8=NewerInMemory
MOV R8,[OlderInMemory]
BufferRetrieve [Buffer5]
SHR ECX,4 ; Let RCX=number of SCAN_INDEX records (16 bytes each).
ShellSort RSI,RCX,16,ByVolume ; ArgAscending, R8=OlderInMemory
StdOutput EuroDirs, =B" has found", Eol=yes, Unicode=no
Nr %FOR 1..5
StdOutput Number%Nr{}A, Report%Nr, Eol=yes, Unicode=no
%ENDFOR Nr
CLC
.90:RET
ENDP LoadAndCompareScans
Arg2Gui PROC ; Convert string arguments Arg*** from UTF-8 to ***UTF16.
LEA RSI,[ArgScanDir::]
LEA RDI,[ScanDirUTF16]
WinABI MultiByteToWideChar,CP_UTF8,0,RSI,-1,RDI,SIZE#ScanDirUTF16/2
LEA RSI,[ArgInputFile::]
LEA RDI,[InputFileNameUTF16]
WinABI MultiByteToWideChar,CP_UTF8,0,RSI,-1,RDI,SIZE#InputFileNameUTF16/2
LEA RSI,[ArgOutputFile::]
LEA RDI,[OutputFileNameUTF16]
WinABI MultiByteToWideChar,CP_UTF8,0,RSI,-1,RDI,SIZE#OutputFileNameUTF16/2
LEA RSI,[ArgIncludeDirs::]
LEA RDI,[IncludeDirsUTF16]
WinABI MultiByteToWideChar,CP_UTF8,0,RSI,-1,RDI,SIZE#IncludeDirsUTF16/2
LEA RSI,[ArgExcludeDirs::]
LEA RDI,[ExcludeDirsUTF16]
WinABI MultiByteToWideChar,CP_UTF8,0,RSI,-1,RDI,SIZE#ExcludeDirsUTF16/2
; Check /ScanDir=
LEA RDI,[ScanDirUTF16]
CMPW [RDI],0
JNE .10:
MOV EAX,'.' ; /ScanDir= was not specified, use ".".
MOV [RDI],EAX
.10:XOR ECX,ECX
XOR EAX,EAX
DEC RCX
REPNE SCASW
MOV AX,'\'
SUB RDI,2
CMP [RDI-2],AX
JE .90:
STOSD ; Terminate the scan directory with backslash.
.90:RET
ENDP Arg2Gui
eurodirs_2025.09.06_09.59.scan.
StoreFileName PROC LEA RSI,[RDI+WIN32_FIND_DATAW.FileName] ; Let RSI point to unichar "2024.09.06_09.59". BufferStore [ScanDirBuffer],RSI,16*2 JC ErrorBuffer RET ENDP StoreFileName
Construct a line from the [BufferSelected] into Dir. The line looks like
dir_size directory\name\in\UTF16, it is encoded in UTF-16 and zero terminated.
When the input line number RBX is 0, an empty line is returned (CF=1. RCX=0).
When the input line number RBX is 1, first line is returned (CF=0. RCX>0).
When the input line number RBX is 2, second line is returned etc.
When the input line number RBX is bigger than the [MaxLineNr], an empty line is returned (CF=1. RCX=0).
OneLine2Dir:: PROC
PUSH RBX,RDI
MOV EAX,[ArgViewDirs::]
MOV RDX,[Buffers+8*RAX-8]
BufferRetrieve RDX ; Load the buffer contents to RSI,RCX.
ADD RCX,RSI ; The end of buffer contents.
SAL EBX,4 ; Each SCAN_INDEX in the buffer contents has size=16.
JZ .10:
LEA RBX,[RBX+RSI-SIZE# SCAN_INDEX] ; Let RBX point at the selected SCAN_INDEX.
CMP RBX,RCX ; Is it out of contents?
JB .20:
.10: XOR ECX,ECX
LEA RSI,[Dir]
STC
JMP .90: ; Abort when the requested line number exceeded the buffer and return CF=1 and empty line.
.20: MOV RDX,[NewerInMemory]
CMP AL,ArgViewDirsDel
JNE .30:
MOV RDX,[OlderInMemory] ; Select the mapping of File_scanOlder or File_scanNewer.
.30: MOV RAX,[RBX+SCAN_INDEX.Volume]
LEA RSI,[NumberA]
StoD RSI,Size=20 ; Store the directory volume as a decimal ANSI
CALL AddUnderscores ; and complete it with separators.
LEA RSI,[Number_A]
CMPB [ArgViewDirs::],ArgViewDirsDel
JNE .50:
CMPB [RSI],'0'
JE .50:
INC RSI ; Skip the minus sign when viewing deleted direcotires.
.50: LEA RDI,[Dir]
CMPB [ArgViewDirs::],ArgViewDirsInc
JNE .60:
MOV AL,'+' ; Add a plus sign when viewing incremented directories.
JMP .70:
.60: LODSB ; Convert the number from ANSI to WIDE.
CMP AL,0
JE .80:
.70: STOSW
JMP .60:
.80: MOV ECX,[RBX+SCAN_INDEX.Size]
MOV EAX,[RBX+SCAN_INDEX.Ptr]
ADD RDX,RAX ; Let RDX point to the directory name.
DecodeUTF8 RDX,StoreWideChar,Size=RCX,Width=16
MOV RCX,RDI ; At the end of directory name.
MOVW [RDI],0 ; Zero terminate.
LEA RSI,[Dir]
SUB RCX,RSI ; CF=0, RSI,RCX is the target line.
.90:POP RDI,RBX
RET
ENDP OneLine2Dir
Complete an ASCII decadic number with underscores _ per each three digits.
AddUnderscores PROC
PUSH RDX
SUB RDI,RSI
MOV ECX,3
MOV EAX,EDI
XOR EDX,EDX
LEA RDI,[Number_A]
DIV RCX
CMP EDX,0
JNE .20:
REP MOVSB
DEC EAX
JMP .40:
.20:CMP EDX,1
JNE .30:
MOVSB
CMPB [RSI-1],'-'
JNE .40:
XCHG EAX,EDX
DEC EDX
JS .80:
JMP .60:
.30:MOVSW
.40:XCHG EAX,EDX
.50:DEC RDX
JS .80:
MOV AL,'_'
STOSB
.60:MOV CL,3
REP MOVSB
JMP .50:
.80:MOV AX,' '
STOSW
POP RDX
RET
ENDP AddUnderscores
StoreWideChar:: PROC STOSW RET ENDP StoreWideChar:
/dir_name 0x09 dir_volume_decimal 0x0A
LoadOlder PROC
MOV R10,RCX ; Save preserved RCX.
MOV RDI,[OlderPtr]
MOV RCX,[OlderEndInMemory]
MOV RBX,RDI
MOV RDX,RDI
SUB RCX,RDI
JNA .90:
MOV AL,9
REPNE SCASB
MOV R11,RSI
MOV RSI,RDI
DEC RDI ; Go at HT.
SUB RDI,RDX
XCHG RDI,RDX
LodD RSI
MOV R9,RAX
INC RSI ; Skip LF.
MOV [OlderPtr],RSI
MOV RSI,R11
MOV RCX,R10 ; Restore preserved RCX.
RET ; Normal end.
.90:MOV RDX,-1
MOV RCX,R10 ; Restore preserved RCX.
RET ; End at EOF.
ENDP LoadOlder
/dir_name 0x09 dir_volume_decimal 0x0A
LoadNewer PROC
MOV R10,RDI ; Save preserved RDI.
MOV RDI,[NewerPtr]
MOV RCX,[NewerEndInMemory]
MOV RBP,RDI
SUB RCX,RDI
JNA .90:
MOV AL,9
REPNE SCASB
MOV RSI,RDI
DEC RDI
SUB RDI,RBP
MOV ECX,EDI
LodD RSI
MOV R8,RAX
INC RSI ; Skip LF.
MOV [NewerPtr],RSI
MOV RDI,R10 ; Restore preserved RDI.
MOV RSI,RBP ; Normal end.
MOV R12,RCX
RET ; Normal end.
.90:MOV RDI,R10 ; Restore preserved RDI.
MOV RCX,-1
MOV R12,RCX
RET ; End at EOF.
ENDP LoadNewer
ExcludeDir PROC
MOV RSI,[ExcludedDirsPtr]
.10:CMP RSI,[ExcludedDirsEnd]
JAE .90:
LODSQ
MOV RCX,RAX
LODSQ
JRCXZ .10:
XCHG RSI,RAX
LEA RDI,[Dir]
REPE CMPSW
XCHG RAX,RSI
JNE .10:
RET ; ZF=1.
.90:CMP ESI,1
RET ; ZF=0.
ENDP ExcludeDir
ParseToBuffer PROC
MOV RBX,RAX
.10:MOV RDX,RSI
.20:LODSW
CMP AX,0
JE .50:
CMP AX,';' ; ; is the folder names separator.
JNE .20:
CALL .50: ; Store to buffer and continue.
JMP .10:
.50: ; Store size RSI-2-RDX and pointer RDX to the buffer RBX.
LEA RAX,[RSI-2]
SUB RAX,RDX
SHR RAX,1 ; Convert bytes to unichars.
JZ .90:
PUSH RAX ; Save folder name size on stack.
MOV RAX,RSP
BufferStore RBX,RAX,8 ; Dir name size in unichars.
POP RAX
JC ErrorBuffer
PUSH RDX ; Save pointer to folder name on stack.
MOV RAX,RSP
BufferStore RBX,RAX,8 ; Dir ptr.
POP RAX
JC ErrorBuffer
.90:RET
ENDP ParseToBuffer
ReadFile_ini PROC
SetSt [Status::],ArgFromFile ; Tell ArgParse that arguments may not begin with / or -.
StdOutput =B"Configuration """,Unicode=no
StdOutput File_ini.Name,Console=yes,Unicode=yes
FileStreamOpen File_ini,BufSize=4K
JNC .10:
StdOutput =B""" was not found.",Eol=yes,Unicode=no
JMP .90:
.10:FileStreamReadLn File_ini
JBE .80:
MOV ECX,EAX
; The first line may begin with BOM.
MOV AX,[RSI]
CMPW AX,0xBBEF ; UTF-8 BOM?
JNE .50:
ADD RSI,3 ; Skip the BOM.
SUB ECX,3
JB .80:
JMP .50:
.30:FileStreamReadLn File_ini
JBE .80:
MOV ECX,EAX
.50:CALL ArgParse::
JNC .30:
LEA RSI,[Help+3]
StdOutput RSI,Unicode=no
TerminateProgram 8
.80:FileClose File_ini
StdOutput =B""" was accepted.",Eol=yes,Unicode=off
.90:RstSt [Status::],ArgFromFile
RET
ENDP ReadFile_ini
ByName: PROC
MOV R8,RCX ; Save RCX,RSI,RDI.
MOV R9,RSI
MOV R10,RDI
MOV EDX,[RDI+SCAN_INDEX.Size]
MOV ECX,[RSI+SCAN_INDEX.Size]
CMP ECX,EDX
JBE .10:
MOV ECX,EDX ; Compare dirnames by the shorter of both sizes.
.10:MOV ESI,[RSI+SCAN_INDEX.Ptr]
MOV EDI,[RDI+SCAN_INDEX.Ptr]
ADD RSI,[NamesInMemory]
ADD RDI,[NamesInMemory]
DEC ECX
XCHG RSI,RDI ; Ascending.
REPE CMPSB
JA .90:
JB .50:
CMP EDX,[R9+SCAN_INDEX.Size] ; When the dirnames are the same, compare by dirname size.
JAE .90
.50:MOV RAX,[R9+SCAN_INDEX.Ptr] ; Records are in wrong order. Swap them.
MOV RDX,[R9+SCAN_INDEX.Volume]
XCHG RAX,[R10+SCAN_INDEX.Ptr]
XCHG RDX,[R10+SCAN_INDEX.Volume]
MOV [R9+SCAN_INDEX.Ptr],RAX
MOV [R9+SCAN_INDEX.Volume],RDX
.90:MOV RCX,R8 ; Restore RCX.
RET
ENDP ByName:
ByDate: PROC
XCHG RSI,RDI
PUSH RCX,RSI,RDI
MOV ECX,SIZE# DateMaskW /2
REPE CMPSW
POP RDI,RSI,RCX
JAE .90:
MOV RAX,[RSI+00]
MOV RDX,[RSI+08]
XCHG [RDI+00],RAX
XCHG [RDI+08],RDX
MOV [RSI+00],RAX
MOV [RSI+08],RDX
MOV RAX,[RSI+16]
MOV RDX,[RSI+24]
XCHG [RDI+16],RAX
XCHG [RDI+24],RDX
MOV [RSI+16],RAX
MOV [RSI+24],RDX
.90:RET
ENDP ByDate
ByVolume:PROC
JSt [Status::],ArgDescending,.10:
XCHG RSI,RDI
.10:MOV RAX,[RSI+SCAN_INDEX.Volume]
MOV RDX,[RDI+SCAN_INDEX.Volume]
CMP RDX,RAX
CLC
JG .90:
MOV R9,RCX
MOV R10,RSI
MOV R11,RDI
XCHG RSI,RDI
JL .60:
MOV ECX,[RSI+SCAN_INDEX.Size]
MOV ESI,[RSI+SCAN_INDEX.Ptr]
MOV EDX,[RDI+SCAN_INDEX.Size]
MOV EDI,[RDI+SCAN_INDEX.Ptr]
ADD RSI,R8
ADD RDI,R8
CMP ECX,EDX
JBE .20:
MOV ECX,EDX
.20:REPE CMPSB
JA .80:
JB .50:
CMP EDX,[R10+SCAN_INDEX.Size]
JAE .80:
.50:MOV RAX,[R10+SCAN_INDEX.Volume]
MOV RDX,[R11+SCAN_INDEX.Volume]
.60:MOV [R11+SCAN_INDEX.Volume],RAX
MOV [R10+SCAN_INDEX.Volume],RDX
MOV RAX,[R10+SCAN_INDEX.Ptr]
MOV RDX,[R11+SCAN_INDEX.Ptr]
MOV [R11+SCAN_INDEX.Ptr],RAX
MOV [R10+SCAN_INDEX.Ptr],RDX
STC
.80:MOV RCX,R9
.90:RET
ENDP ByVolume:
SetToday PROC
LEA RBX,[SystemTime]
WinABI GetLocalTime,RBX
LEA RDI,[TodayA]
MOVZXW EAX,[RBX+SYSTEMTIME.wYear]
StoD RDI,Size=4,Signed=no,Align=right,LeadingZeroes=yes
MOV AL,'.'
STOSB
MOVZXW EAX,[RBX+SYSTEMTIME.wMonth]
StoD RDI,Size=2,Signed=no,Align=right,LeadingZeroes=yes
MOV AL,'.'
STOSB
MOVZXW EAX,[RBX+SYSTEMTIME.wDay]
StoD RDI,Size=2,Signed=no,Align=right,LeadingZeroes=yes
MOV AL,'_'
STOSB
MOVZXW EAX,[RBX+SYSTEMTIME.wHour]
StoD RDI,Size=2,Signed=no,Align=right,LeadingZeroes=yes
MOV AL,'.'
STOSB
MOVZXW EAX,[RBX+SYSTEMTIME.wMinute]
StoD RDI,Size=2,Signed=no,Align=right,LeadingZeroes=yes
XOR EAX,EAX
STOSB
LEA RSI,[TodayA]
LEA RDI,[TodayW]
MOV ECX,SIZE# TodayA
.50:LODSB
STOSW
LOOP .50:
RET
ENDP SetToday
SetMaxDaysAgo PROC
LEA RSI,[TodayA]
LEA RDI,[MaxDaysAgoA]
MOV ECX,SIZE# MaxDaysAgoA
REP MOVSB
LEA RBX,[FileTime]
WinABI GetSystemTimeAsFileTime,RBX
MOV RBX,[RBX] ; RBX is the number of 100-nanosecond intervals since January 1, 1601.
MOV RAX,24*60*60*10_000_000
MOV ECX,[ArgMaxDays::]
MUL RCX
SUB RBX,RAX
LEA RDI,[FileTime]
MOV [RDI],RBX
WinABI FileTimeToDosDateTime,RDI,FatDate,FatTime
MOV AX,[FatDate]
SHR AX,9
ADD AX,1980
LEA RDI,[MaxDaysAgoA]
StoD RDI,Size=4,Signed=no,Align=right,LeadingZeroes=yes
MOV AX,[FatDate]
AND AX,01E0h
SHR AX,5
INC RDI
StoD RDI,Size=2,Signed=no,Align=right,LeadingZeroes=yes
MOV AX,[FatDate]
AND AX,001Fh
INC RDI
StoD RDI,Size=2,Signed=no,Align=right,LeadingZeroes=yes
LEA RSI,[MaxDaysAgoA]
LEA RDI,[MaxDaysAgoW]
MOV ECX,SIZE# MaxDaysAgoA
.50:LODSB
STOSW
LOOP .50:
RET
ENDP SetMaxDaysAgo
EraseOlderFile: PROC
LEA RSI,[RDI+WIN32_FIND_DATAW.FileName+18]
LEA RDI,[MaxDaysAgoW]
MOV ECX,16
REPE CMPSW ; Compare datetime encoded in the file name.
JAE .90:
LEA RAX,[RBX+FILE64.Name]
StdOutput =U"Old scan """,RAX,=U""" was deleted.",Eol=yes,Console=yes,Unicode=yes
FileDelete RBX
.90:RET
ENDP EraseOlderFile:
ErrorZero: ; RBX=^FILE64
StdOutput =B'Error: zero size of the file "',Unicode=no
LEA RAX,[RBX+FILE64.Name]
JMP Write
ErrorMap: ; RBX=^FILE64
StdOutput =B'Error: memory-mapping the file "',Unicode=no
LEA RAX,[RBX+FILE64.Name]
JMP Write
ErrorFile: ; RBX=^FILE64
StdOutput =B'Error: could not open the file "',Unicode=no
LEA RAX,[RBX+FILE64.Name]
JMP Write
ErrorWrite: ; RBX=^FILE64
StdOutput =B'Error writing to the file "',Unicode=no
LEA RAX,[RBX+FILE64.Name]
Write:StdOutput RAX,Eol=no,Console=yes,Unicode=yes
StdOutput =B'".', Unicode=no, Eol=yes
JMPS .Terminate:
ErrorBuffer:
StdOutput =B"Error allocating memory.",Eol=yes,Unicode=no
.Terminate:
FileClose File_scan, File_index, File_names
BufferDestroy [ScanDirBuffer],[Buffer1],[Buffer2],[Buffer3],[Buffer4],[Buffer5]
TerminateProgram 8
TerminateProgram 8
ENDPROGRAM dirswinc