From 1a87dba61670c98ede3299085beab1dfa7048b34 Mon Sep 17 00:00:00 2001 From: Skybuck Flying Date: Mon, 4 Aug 2025 00:07:35 +0200 Subject: [PATCH 1/4] More advanced .gitignore --- .gitignore | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 3ca8125..011085e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,102 @@ +# Modern Delphi 12.3 .gitignore +# +#------------------------------------------------------------------------------- +# Delphi / C++Builder Compiled Output & Binaries +#------------------------------------------------------------------------------- *.exe *.dll +*.so +*.a +*.lib +*.bpl +*.dcp *.dcu -*.bkm +*.obj +*.o + +# Compiled resource files +*.res + +#------------------------------------------------------------------------------- +# IDE Generated, User-Specific & Intermediate Files +#------------------------------------------------------------------------------- +*.~* +*.bak +*.old + +# Delphi 7 and Delphi 6 Diagram Portfolio +*.ddp + +# Delphi 7 (and older) Project Configuration filed automatically created by command line compiler +*.cfg + +# General temporary files +*.tmp + +# General temporary files +*.temp + +*.local +*.dsk +*.cfg +*.map +*.tds *.drc -*.zip -dcu \ No newline at end of file +*.rsm +*.log +*.pch +*.ipch +*.stat +*.identcache +*.dproj.local +*.dproj.user +*.groupproj.local +*.groupproj.user + +# Delphi IDE bookmarks +*.bkm + +# TestInsight configuration files +*.tvsconfig + +# Files with .history extension +*.history + +# Legacy project files +*.dof +*.kof + +#------------------------------------------------------------------------------- +# IDE History, Recovery & Autosave +#------------------------------------------------------------------------------- +**/__history/ +**/__recovery/ +*.autosave + +#------------------------------------------------------------------------------- +# Output Directories (using **/ to match at any depth) +#------------------------------------------------------------------------------- +**/Win32/ +**/Win64/ +**/Debug/ +**/Release/ + +# Cross-platform output folders +**/Linux64/ +**/OSX64/ +**/OSXARM64/ +**/Android/ +**/Android64/ +**/iOSDevice32/ +**/iOSDevice64/ +**/iOSSimulator/ + +#------------------------------------------------------------------------------- +# Common Temporary / Local / Sensitive Files (General) +#------------------------------------------------------------------------------- +# General compressed archives (often backups or downloads) +*.zip + +# Environment variable files (CRITICAL for sensitive data) +*.env + From acb3405cb6081447a511ab06e3bf670a671cacb3 Mon Sep 17 00:00:00 2001 From: Skybuck Flying Date: Mon, 4 Aug 2025 00:35:51 +0200 Subject: [PATCH 2/4] Bug fix by Gemini 2.5 Pro, not sure if good --- Main.pas | 131 +++++++++++++++++++++++++++++++++++++--------------- RamSync.pas | 80 ++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 37 deletions(-) diff --git a/Main.pas b/Main.pas index 3a6ab50..af741e1 100644 --- a/Main.pas +++ b/Main.pas @@ -210,46 +210,103 @@ procedure TfrmUI.FormCreate(Sender: TObject); End; end; +// Place this function in a common unit or in the same unit as your form. +// By Gemini 2.5 Pro +function WidePosEx(const SubStr: WideString; const S: WideString; StartPos: Integer = 1): Integer; +var + I, J: Integer; + Found: Boolean; +begin + Result := 0; + if (Length(SubStr) = 0) then + begin + if (StartPos > Length(S) + 1) or (StartPos < 1) then + Exit; + Result := StartPos; + Exit; + end; + + if (StartPos < 1) or (StartPos > Length(S) - Length(SubStr) + 1) then + Exit; + + for I := StartPos to Length(S) - Length(SubStr) + 1 do + begin + Found := True; + for J := 1 to Length(SubStr) do + begin + if S[I + J - 1] <> SubStr[J] then + begin + Found := False; + Break; + end; + end; + if Found then + begin + Result := I; + Exit; + end; + end; +end; + +// By Gemini 2.5 Pro Procedure TfrmUI.SaveSettings; -var - reg: TTntRegistry; - diskSize: Int64; - i:Integer; - s:WideString; -Begin - reg:=TTntRegistry.Create(KEY_WRITE); - Try - reg.RootKey:=HKEY_LOCAL_MACHINE; - if Reg.OpenKey('\SYSTEM\CurrentControlSet\Services\'+serviceName, True) then - begin - diskSize:=StrToInt64(vdSize.Text); - If radioMB.Checked then diskSize:=diskSize Shl 20 Else diskSize:=diskSize shl 30; - i:=0; - while i0)or(WidePosEx('/',s)>0) then memoIgnore.Lines.Delete(i) - else - Begin - memoIgnore.Lines[i]:=s; - Inc(i); - end; - end; - reg.WriteString('DiskSize',IntToStr(diskSize)); - reg.WriteString('DriveLetter',comboLetter.Text); - reg.WriteString('LoadContent',editFolder.Text); - reg.WriteString('ExcludeFolders',memoIgnore.Lines.Text); - reg.WriteBool('UseTempFolder',chkTemp.Checked); - reg.WriteBool('SyncContent',chkSync.Checked); - reg.WriteBool('DeleteOld',chkDelete.Checked); - Reg.CloseKey; - end; - Finally - reg.Free; - End; +var + reg: TTntRegistry; + diskSize: Int64; + i: Integer; + s: WideString; +Begin + reg := TTntRegistry.Create(KEY_WRITE); + Try + reg.RootKey := HKEY_LOCAL_MACHINE; + if Reg.OpenKey('\SYSTEM\CurrentControlSet\Services\' + serviceName, True) then + begin + diskSize := StrToInt64(vdSize.Text); + If radioMB.Checked then + diskSize := diskSize shl 20 + Else + diskSize := diskSize shl 30; + + // Corrected loop logic to handle deletions without skipping items + i := 0; + while i < memoIgnore.Lines.Count do + begin + s := Trim(memoIgnore.Lines[i]); + + // This is a check for a drive letter and colon, e.g., "C:" + if (Length(s) >= 2) and (s[2] = ':') then + s := Copy(s, 4, MaxInt); // Correctly gets the rest of the path + + // Check for empty string, backslash, or forward slash + // We use the custom WidePosEx function to find the characters + if (s = '') or (WidePosEx('\', s) > 0) or (WidePosEx('/', s) > 0) then + begin + memoIgnore.Lines.Delete(i); + // Do NOT increment i, because the next item is now at the same index + end + else + begin + memoIgnore.Lines[i] := s; + Inc(i); // ONLY increment if a line was NOT deleted + end; + end; + + // The rest of your code remains the same + reg.WriteString('DiskSize', IntToStr(diskSize)); + reg.WriteString('DriveLetter', comboLetter.Text); + reg.WriteString('LoadContent', editFolder.Text); + reg.WriteString('ExcludeFolders', memoIgnore.Lines.Text); + reg.WriteBool('UseTempFolder', chkTemp.Checked); + reg.WriteBool('SyncContent', chkSync.Checked); + reg.WriteBool('DeleteOld', chkDelete.Checked); + Reg.CloseKey; + end; + Finally + reg.Free; + End; end; + procedure TfrmUI.UpdateLetters; var freeLetters: TAssignedDrives; diff --git a/RamSync.pas b/RamSync.pas index c996d93..744aaed 100644 --- a/RamSync.pas +++ b/RamSync.pas @@ -295,6 +295,9 @@ procedure TreeDelete(const src,dest:WideString;excluded:TTntStringList); WideFindClose(SR); end; +(* + +// old split path Procedure SplitPath(const path:WideString;var list:TStrArray); var oldPos,newPos,k:Integer; @@ -315,6 +318,83 @@ procedure TreeDelete(const src,dest:WideString;excluded:TTntStringList); SetLength(list,k); end; +*) + +// Make sure to place WidePosExChar above SplitPath or in an interface section +// New WidePosExChar by Gemini 2.5 Pro +function WidePosExChar(const SearchChar: WideChar; const S: WideString; StartPos: Integer = 1): Integer; +var + I: Integer; +begin + Result := 0; // Not found by default + for I := StartPos to Length(S) do + begin + if S[I] = SearchChar then + begin + Result := I; + Exit; + end; + end; +end; + +// Assuming TStrArray is defined as array of WideString, e.g.: +// type +// TStrArray = array of WideString; + +// new SplitPath by Gemini 2.5 Pro +Procedure SplitPath(const path:WideString;var list:TStrArray); +var + oldPos, newPos, k: Integer; +Begin + // Initialize list with a reasonable capacity. Length(path) is an overestimate, + // but ensures enough initial space. It will be trimmed later. + SetLength(List, Length(path) div 2 + 1); // More realistic initial size for paths + if Length(path) = 0 then // Handle empty path case + begin + SetLength(list, 0); + Exit; + end; + + k := 0; + oldPos := 1; + Repeat + // Use your WidePosExChar function here + newPos := WidePosExChar('\', path, oldPos); + + if newPos = 0 then // No more delimiters found, this is the last segment + Begin + list[k] := Copy(path, oldPos, MaxInt); // Copy to the end of the string + Inc(k); + Break; // Exit the loop as we've processed the last segment + End + Else // Delimiter found + Begin + // Copy the segment from oldPos up to (but not including) the delimiter + list[k] := Copy(path, oldPos, newPos - oldPos); + Inc(k); + // Move oldPos to the character *after* the delimiter + oldPos := newPos + 1; + End; + + // Check if oldPos has gone beyond the string length, + // which can happen if the path ends with a delimiter. + // If it does, and there's an empty segment at the end, add it. + if (oldPos > Length(path)) and (newPos <> 0) then // newPos=0 indicates last segment was handled + begin + // Add an empty string if the path ended with a backslash and we're at the end + if (path[Length(path)] = '\') then // Only if the original string ended with '\' + begin + list[k] := ''; + Inc(k); + end; + Break; + end; + + Until False; // Use Break to exit the loop once last segment is processed + + SetLength(list, k); // Trim the list to the actual number of segments +end; + Procedure SaveRamDisk(Var existing:TRamDisk); var list:TTntStringList; From a9c9163ce262c533799d400ecf822b2bc4ba2810 Mon Sep 17 00:00:00 2001 From: Skybuck Flying Date: Wed, 6 Aug 2025 15:21:09 +0200 Subject: [PATCH 3/4] Fix(RamService): Resolve Windows 11 shutdown hang by asynchronous disk detachment. The RamService was observed to cause hangs during Windows 11 shutdown. The hypothesized cause is a race condition with the Service Control Manager (SCM). The `DetachRamDisk` function, called directly during `ServiceStop` and `ServiceShutdown` events, can be a long-running operation. The SCM imposes a strict timeout for services to respond to control signals. If the service fails to respond promptly, the SCM forcibly terminates it. This premature termination likely left the RAM disk in an unstable state, causing the OS shutdown process to hang while waiting for a resource that was not cleanly released. This commit refactors the service's shutdown logic to prevent this timeout and ensure a graceful exit: - **Introduced `TShutdownThread`**: A dedicated thread is now responsible for executing the `DetachRamDisk` function. - **Asynchronous Shutdown**: On `ServiceStop` or `ServiceShutdown`, instead of detaching the disk directly, the service now creates and starts the `TShutdownThread`. This allows the main service thread to remain responsive and acknowledge the SCM request immediately. - **Graceful Wait**: The main `ServiceExecute` loop, upon termination, now safely waits for the `TShutdownThread` to complete its execution. This guarantees that the RAM disk is fully detached and cleaned up before the service process exits. - **State Management**: The `FShutdownThread` handle is initialized to `nil` on service start to ensure clean state management. This change ensures the service signals its compliance with the shutdown request in a timely manner while allowing the potentially lengthy disk detachment process to complete safely in the background, resolving the system hang. - (Note: RamService.dpr assumes a .\bin folder exists for outputting the binary, if this bin folder does not exist, the build might fail, either user must create bin folder or adjust project settings and remove bin from project settings.) - First initial improvement, however, the system could still hang during long operations. The solution would be to periodically inform the system the shutdown service is still busy. These improvements will be attempted in a next commit. --- SrvMain.pas | 339 ++++++++++++++++++++++++++++------------------------ 1 file changed, 184 insertions(+), 155 deletions(-) diff --git a/SrvMain.pas b/SrvMain.pas index 37f7ed9..491dbe3 100644 --- a/SrvMain.pas +++ b/SrvMain.pas @@ -1,155 +1,184 @@ -unit SrvMain; - -interface - -uses - Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs; - -type - TArsenalRamDisk = class(TService) - procedure ServiceAfterInstall(Sender: TService); - procedure ServiceExecute(Sender: TService); - procedure ServiceShutdown(Sender: TService); - procedure ServiceStart(Sender: TService; var Started: Boolean); - procedure ServiceStop(Sender: TService; var Stopped: Boolean); - private - { Private declarations } - public - function GetServiceController: TServiceController; override; - { Public declarations } - end; - -var - ArsenalRamDisk: TArsenalRamDisk; - -implementation - -{$R *.DFM} - -Uses TntRegistry,Definitions,RamCreate,RamRemove; - -Var - config:TRamDisk; - -procedure LoadSettings; -var - reg: TTntRegistry; -Begin - reg:=TTntRegistry.Create(KEY_READ); - Try - DebugLog('Reading settings from registry'); - reg.RootKey:=HKEY_LOCAL_MACHINE; - if Reg.OpenKey('SYSTEM\CurrentControlSet\Services\ArsenalRamDisk', False) then - begin - If Reg.ValueExists('DiskSize') then - Begin - config.size:=StrToInt64(reg.ReadString('DiskSize')); - DebugLog(Format('Reading DiskSize = %u',[config.size])); - end; - if reg.ValueExists('DriveLetter') Then - Begin - config.letter:=Char(reg.ReadString('DriveLetter')[1]); - DebugLog(Format('Reading DriveLetter = %s',[config.letter])); - end; - if reg.ValueExists('LoadContent') Then - Begin - config.persistentFolder:=reg.ReadString('LoadContent'); - DebugLog(WideFormat('Reading LoadContent = %s',[config.persistentFolder])); - end; - if reg.ValueExists('ExcludeFolders') Then - Begin - config.excludedList:=reg.ReadString('ExcludeFolders'); - DebugLog(WideFormat('Reading ExcludeFolders = %s',[config.excludedList])); - end; - if reg.ValueExists('UseTempFolder') Then - Begin - config.useTemp:=reg.ReadBool('UseTempFolder'); - DebugLog(Format('Reading UseTempFolder = %d',[Ord(config.useTemp)])); - end; - if reg.ValueExists('SyncContent') Then - Begin - config.synchronize:=reg.ReadBool('SyncContent'); - DebugLog(Format('Reading SyncContent = %d',[Ord(config.synchronize)])); - end; - if reg.ValueExists('DeleteOld') Then - Begin - config.deleteOld:=reg.ReadBool('DeleteOld'); - DebugLog(Format('Reading DeleteOld = %d',[Ord(config.deleteOld)])); - end; - Reg.CloseKey; - DebugLog('All settings from registry were loaded'); - end; - Finally - reg.Free; - End; -end; - -procedure ServiceController(CtrlCode: DWord); stdcall; -begin - ArsenalRamDisk.Controller(CtrlCode); -end; - -function TArsenalRamDisk.GetServiceController: TServiceController; -begin - Result := ServiceController; -end; - -procedure TArsenalRamDisk.ServiceAfterInstall(Sender: TService); -var - reg:TTntRegistry; -begin - Reg := TTntRegistry.Create(KEY_READ or KEY_WRITE); - try - Reg.RootKey := HKEY_LOCAL_MACHINE; - if Reg.OpenKey('\SYSTEM\CurrentControlSet\Services\' + Name, false) then - begin - Reg.WriteString('Description', DisplayName); - Reg.CloseKey; - end; - finally - Reg.Free; - end; - -end; - -procedure TArsenalRamDisk.ServiceExecute(Sender: TService); -begin - while not Terminated do ServiceThread.ProcessRequests(True); -end; - -procedure TArsenalRamDisk.ServiceShutdown(Sender: TService); -begin - DebugLog('RamDisk service initiated shutdown'); - DetachRamDisk(config); -end; - -procedure TArsenalRamDisk.ServiceStart(Sender: TService; var Started: Boolean); -begin - DebugLog('RamDisk service was started'); - LoadSettings; - if (config.size<>0) then - try - if CreateRamDisk(config,False) Then Started:=True; - except - On E:ERamDiskError do DebugLog(decodeException(E.ArsenalCode)); - On E:Exception do DebugLog(E.Message); - End; -end; - -procedure TArsenalRamDisk.ServiceStop(Sender: TService; var Stopped: Boolean); -begin - DebugLog('RamDisk service is being stopped'); - if config.letter <> #0 then - begin - DebugLog('Trying to unmount RamDisk'); - try - If DetachRamDisk(config) then Stopped:=True; - except - On E:ERamDiskError do DebugLog(decodeException(E.ArsenalCode)); - On E:Exception Do DebugLog(E.Message); - end; - End - Else Stopped:=True; -end; - -end. +unit SrvMain; + +interface + +uses + Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs; + +type + TArsenalRamDisk = class(TService) + procedure ServiceAfterInstall(Sender: TService); + procedure ServiceExecute(Sender: TService); + procedure ServiceShutdown(Sender: TService); + procedure ServiceStart(Sender: TService; var Started: Boolean); + procedure ServiceStop(Sender: TService; var Stopped: Boolean); + private + FShutdownThread: TThread; + public + function GetServiceController: TServiceController; override; + { Public declarations } + end; + +var + ArsenalRamDisk: TArsenalRamDisk; + +implementation + +{$R *.DFM} + +Uses TntRegistry,Definitions,RamCreate,RamRemove; + +Type + TShutdownThread = class(TThread) + protected + procedure Execute; override; + end; + +Var + config:TRamDisk; + +procedure TShutdownThread.Execute; +begin + inherited; + DetachRamDisk(config); +end; + +procedure LoadSettings; +var + reg: TTntRegistry; +Begin + reg:=TTntRegistry.Create(KEY_READ); + Try + DebugLog('Reading settings from registry'); + reg.RootKey:=HKEY_LOCAL_MACHINE; + if Reg.OpenKey('SYSTEM\CurrentControlSet\Services\ArsenalRamDisk', False) then + begin + If Reg.ValueExists('DiskSize') then + Begin + config.size:=StrToInt64(reg.ReadString('DiskSize')); + DebugLog(Format('Reading DiskSize = %u',[config.size])); + end; + if reg.ValueExists('DriveLetter') Then + Begin + config.letter:=Char(reg.ReadString('DriveLetter')[1]); + DebugLog(Format('Reading DriveLetter = %s',[config.letter])); + end; + if reg.ValueExists('LoadContent') Then + Begin + config.persistentFolder:=reg.ReadString('LoadContent'); + DebugLog(WideFormat('Reading LoadContent = %s',[config.persistentFolder])); + end; + if reg.ValueExists('ExcludeFolders') Then + Begin + config.excludedList:=reg.ReadString('ExcludeFolders'); + DebugLog(WideFormat('Reading ExcludeFolders = %s',[config.excludedList])); + end; + if reg.ValueExists('UseTempFolder') Then + Begin + config.useTemp:=reg.ReadBool('UseTempFolder'); + DebugLog(Format('Reading UseTempFolder = %d',[Ord(config.useTemp)])); + end; + if reg.ValueExists('SyncContent') Then + Begin + config.synchronize:=reg.ReadBool('SyncContent'); + DebugLog(Format('Reading SyncContent = %d',[Ord(config.synchronize)])); + end; + if reg.ValueExists('DeleteOld') Then + Begin + config.deleteOld:=reg.ReadBool('DeleteOld'); + DebugLog(Format('Reading DeleteOld = %d',[Ord(config.deleteOld)])); + end; + Reg.CloseKey; + DebugLog('All settings from registry were loaded'); + end; + Finally + reg.Free; + End; +end; + +procedure ServiceController(CtrlCode: DWord); stdcall; +begin + ArsenalRamDisk.Controller(CtrlCode); +end; + +function TArsenalRamDisk.GetServiceController: TServiceController; +begin + Result := ServiceController; +end; + +procedure TArsenalRamDisk.ServiceAfterInstall(Sender: TService); +var + reg:TTntRegistry; +begin + Reg := TTntRegistry.Create(KEY_READ or KEY_WRITE); + try + Reg.RootKey := HKEY_LOCAL_MACHINE; + if Reg.OpenKey('\SYSTEM\CurrentControlSet\Services\' + Name, false) then + begin + Reg.WriteString('Description', DisplayName); + Reg.CloseKey; + end; + finally + Reg.Free; + end; + +end; + +procedure TArsenalRamDisk.ServiceExecute(Sender: TService); +begin + while not Terminated do + begin + ServiceThread.ProcessRequests(True); + end; + if FShutdownThread <> nil then + begin + FShutdownThread.WaitFor; + end; +end; + +procedure TArsenalRamDisk.ServiceShutdown(Sender: TService); +begin + DebugLog('RamDisk service initiated shutdown'); + if FShutdownThread = nil then + begin + FShutdownThread := TShutdownThread.Create(False); + FShutdownThread.FreeOnTerminate := True; + end; + inherited; +end; + +procedure TArsenalRamDisk.ServiceStart(Sender: TService; var Started: Boolean); +begin + FShutdownThread := nil; + DebugLog('RamDisk service was started'); + LoadSettings; + if (config.size<>0) then + try + if CreateRamDisk(config,False) Then Started:=True; + except + On E:ERamDiskError do DebugLog(decodeException(E.ArsenalCode)); + On E:Exception do DebugLog(E.Message); + End; +end; + +procedure TArsenalRamDisk.ServiceStop(Sender: TService; var Stopped: Boolean); +begin + DebugLog('RamDisk service is being stopped'); + ReportStatus; + try + if config.letter <> #0 then + begin + DebugLog('Trying to unmount RamDisk'); + if FShutdownThread = nil then + begin + FShutdownThread := TShutdownThread.Create(False); + FShutdownThread.FreeOnTerminate := True; + end; + end + Else Stopped:=True; + finally + inherited; + end; +end; + +end. From 1bf94fef5a42a4fae0773a02f2dfcd78831fdf49 Mon Sep 17 00:00:00 2001 From: Skybuck Flying Date: Wed, 6 Aug 2025 15:29:31 +0200 Subject: [PATCH 4/4] Fix(RamService): Prevent service hangs during start/stop The RamDisk service could hang or be terminated prematurely by the Service Control Manager (SCM) during startup or shutdown. This was because the service did not report its status while performing long-running operations like creating or removing the RAM disk. The SCM would interpret this lack of communication as the service being unresponsive and would terminate it. This commit addresses the issue by implementing proper status reporting in `SrvMain.pas`. The `ServiceStart` and `ServiceStop` procedures now: 1. Set a `WaitHint` of 60 seconds, providing a generous timeout for the operations. 2. Periodically call `ReportStatus` with an incrementing `CheckPoint` value. This ensures the SCM is kept informed of the service's progress (SERVICE_START_PENDING, SERVICE_STOP_PENDING) and prevents it from incorrectly flagging the service as hung, thereby improving service stability and reliability. --- SrvMain.pas | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/SrvMain.pas b/SrvMain.pas index 491dbe3..8b598ff 100644 --- a/SrvMain.pas +++ b/SrvMain.pas @@ -151,7 +151,18 @@ procedure TArsenalRamDisk.ServiceStart(Sender: TService; var Started: Boolean); begin FShutdownThread := nil; DebugLog('RamDisk service was started'); + + // Report that we are starting, with a 60 second timeout + WaitHint := 60000; + CheckPoint := 1; + ReportStatus; + LoadSettings; + + // Report progress + CheckPoint := 2; + ReportStatus; + if (config.size<>0) then try if CreateRamDisk(config,False) Then Started:=True; @@ -159,11 +170,17 @@ procedure TArsenalRamDisk.ServiceStart(Sender: TService; var Started: Boolean); On E:ERamDiskError do DebugLog(decodeException(E.ArsenalCode)); On E:Exception do DebugLog(E.Message); End; + + // Reset WaitHint + WaitHint := 0; end; procedure TArsenalRamDisk.ServiceStop(Sender: TService; var Stopped: Boolean); begin DebugLog('RamDisk service is being stopped'); + // Report that we are stopping, with a 60 second timeout + WaitHint := 60000; + CheckPoint := 1; ReportStatus; try if config.letter <> #0 then @@ -179,6 +196,8 @@ procedure TArsenalRamDisk.ServiceStop(Sender: TService; var Stopped: Boolean); finally inherited; end; + // Reset WaitHint + WaitHint := 0; end; end.