Skip to content

Conversation

@SkybuckFlying
Copy link

Delphi 7 does not have a WidePosEx ?

AI/Gemini Pro 2.5 created it.

Plus it seems to have detected some bugs and fixed them.

Not sure if these are real bugs, could you check the changes ?

Also more advanced .gitignore added (For Delphi 12.3 and so forth) but you can ignore that if so desired.

Compiled/Build with Delphi 7 and working... just not sure if it's safe, AI mentioned something about -- in paths, not sure what it was about...

Only add to code if you believe it's good, I would just like your oppinion on it, not necessarily integrate it...

@tmcdos
Copy link
Owner

tmcdos commented Aug 4, 2025

The only useful change would be to update the .gitignore. You probably noticed that the code compiles even without the functions introduced by the AI (WidePostEx and WidePosExChar) - which clearly demonstrates that this code is redundant and needless. AI also has not corrected any bugs - if you compare its "correct" code with the original, you will see that the line deletion in the loop is exactly the same in the AI version (besides the added comments for clarity).
I do not see anything that actually improves the code - unless you prove me wrong :)

@SkybuckFlying
Copy link
Author

After cloning and trying to build it with Delphi 7 enterprise the following happens:

Project RamDiskUI/dpr:

  1. [Fatal Error] Main.pas(7): File not found: 'TntStdCtrls.dcu'

To fix this problem I add all units from Tnt Unicode library.

(Instead of using search path, I like adding files to the project to actually see them in project manager and have more control/access to the, and prevent anything weird from being on the search path and also avoid losing settings when changing IDE through time):

Example:

uses
Forms,
Main in 'Main.pas' {frmUI},
ActiveIMM_TLB in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\ActiveIMM_TLB.pas',
TntActnList in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntActnList.pas',
TntAxCtrls in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntAxCtrls.pas',
TntBandActn in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntBandActn.pas',
TntButtons in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntButtons.pas',
TntCheckLst in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntCheckLst.pas',
TntClasses in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntClasses.pas',
TntClipBrd in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntClipBrd.pas',
TntComCtrls in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntComCtrls.pas',
TntControls in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntControls.pas',
TntDB in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntDB.pas',
TntDBActns in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntDBActns.pas',
TntDBClientActns in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntDBClientActns.pas',
TntDBCtrls in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntDBCtrls.pas',
TntDBGrids in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntDBGrids.pas',
TntDBLogDlg in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntDBLogDlg.pas' {TntLoginDialog: TTntForm},
TntDialogs in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntDialogs.pas',
TntExtActns in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntExtActns.pas',
TntExtCtrls in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntExtCtrls.pas',
TntExtDlgs in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntExtDlgs.pas',
TntFileCtrl in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntFileCtrl.pas',
TntFormatStrUtils in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntFormatStrUtils.pas',
TntForms in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntForms.pas',
TntGraphics in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntGraphics.pas',
TntGrids in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntGrids.pas',
TntListActns in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntListActns.pas',
TntMenus in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntMenus.pas',
TntRegistry in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntRegistry.pas',
TntStdActns in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntStdActns.pas',
TntStdCtrls in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntStdCtrls.pas',
TntSystem in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntSystem.pas',
TntSysUtils in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntSysUtils.pas',
TntWideStrings in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntWideStrings.pas',
TntWideStrUtils in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntWideStrUtils.pas',
TntWindows in 'K:\Delphi\Libraries\TntUnicode\git version 1 august 2025\Source\TntWindows.pas';

Now I assume you think this will build in Delphi 7 ? But it doesn't for some reason:

[Error] RamSync.pas(306): Undeclared identifier: 'WidePosEx'
[Fatal Error] RamRemove.pas(11): Could not compile used unit 'RamSync.pas'

So now we have two programmers with two different believe systems:

  1. You believe the code should compile just fine.
  2. I kinda already proved this is in no way the case and additional libraries/files must be downloaded.

I suspect the discrepancy is with the tnt unicode download... maybe something changed across time to this library leading to this build failure.

  1. Another possibility is you are using a newer version of Delphi which might have this WidePosEx function by default.

Another weird thing:

The virus total scanner reports 2 malware indicators in the release version... possibly command and control functionality ?

I have not yet repeatable an exacty re-build... so far only debug build, size is different ofcourse.

It would be interesting to know which exact version of Delphi you used to make the release version, which build settings, maybe even which tnt unicode library version/commit.

To see if I can also reproduce the exact same exe.

Failing that ghyndra may have to be used to reserve engineer the exe to see if there is any malware in it ;) :)

For now I have build from sources, installed the service.exe.

Seems to work, however I do notice a shutdown failure on my system, but I believe this may be related to nvidia driver bug.

I tested shutdown today, without gaming but with ramservice.exe running it shutdown proceeded smoothly/normal.

Concering AI code editing, it did look different to me first time I looked at it, maybe I look again it, would be helpfull if you posted exact code which you mean is similiar, I think I know what loop you referring to, to me it did look different, but didn't really study it that much.

I must go sleep now, but this is what I could do for now.

Oh one last thing, I tried to modernize the code by having the AI switch to unicode/W versions of API.

So far seems like "total" failure.

Gemini had big troubles concerting:

Set of 'A' to 'Z', something like that.

Delphi translates this into a set of unicode char, no way to change this to ansichar ?

This might be a real Delphi language problem/limitations.

Not sure yet, compiler seems to suggest to use CharInSet to try and solve it.

These kind of char/string issues ansichar vs unicode is very troublesome and annoying in Delphi and definetly a case of "broken delphi language"... could be worth sending an e-mail to embar... to try and solve this somehow, not sure if they will be interested.

One other crazy thing I noticed is Comparing PWhatever to Pwhatever2 not possible. Can't compare pointer types... hmmm another kinda ouch, but nothing real-world, just synthetic case: if Ptype1 = Ptype2 then, not possible in Delphi 12.3

I did this to see what would happen if WinApi.Windows is copied to WinApi.Windows.V2, no complaints from Delphi, two exactly the same units can be used as one can "overrule" the other.

This is basically what some code/constants in your units are doing.... so I was wondering if there is a way to detect which constants are now duplicate inside windows api and which ones are not, but could not do it this way.

I also made the gemini develop this tool from scratch it went fast, but again not yet a cooklie, somehow failed... maybe device name problem.

Also this project requires a manifest/override security settings + it running constantly as a service makes it a bit suspicious/risky ;)

AI managed to find headers for driver on web that was kinda amazing, translated them fast, also amazing.

I do wonder how your tool fast the ramdisk ? because with just driver and batch files this would be difficult... volume has no name or something ? AI suggested some heuristic to find it quickly...

AI also suggested to use robocopy to copy files/back and forth... instead of more complex custom code... I am kinda in favor of custom code, less dependencies.

AI mention your code had a bug in it, it was skipping a line index ? Maybe I should have copied it's entire feedback, but maybe it cost lost or something during the dev process... maybe it would have been obvious to you or during a debug session, have not tried it yet, not sure how to trigger this code, since it's dealing with service, not sure if it's safe/crashing system or how to do this interaction etc.

Anyway I gave up on trying to get it working in Delphi 12.3, so I switch to Delphi 7 under Windows 11. Discovered that win32hlp system is actually nuked by Microsoft, no longer available to Delphi 7 help files not working, it is possible to reinstall win32hlp from unofficial sources, but not advisible, makes system vunerable to *.hlp attacks.

I was kind of a blast from the past to see Delphi 7 run again. Amazing it still works on Windows 11, I thought I'd seen a version that didn't work anymore, guess I was wrong... or maybe it was just one of the executables produced by one of these Delphi compiler versions, anyway, so far so good.

Also d7 enterprise seems to generate diagram portfolio files, *.dpp or something, kind of a mystery, modelling I guess, but couldn't find a way to see/view/enable it to see them, kinda weird. I can remember seeing modelling support, probably for UML, but can't remember which Delphi version it was that had this. I think probably D2007, but apperently D7 might have had it as well, at least enterprise version (?!)

Well, thanks anyway !, I am now using this product on a daily basis, saves me some work.

I do wonder if it truely copies all files on shutdown, the shutdown seems a bit fast, but so far so good, maybe it copies them while idling ? lol, probably not... hmmm... kinda strange ! ;)

I did put a little test file on it, it was stored I believe, but I may have to do some bigger testing... no man overboard if it's not flawless, so far using it only for firefox webbrowser, but would be nice if it is flawless, then could be used for other purpose.

Today I also learned Windows 11 has a feature called "Sandbox", I will try that soon too, could be interesting for AI purposes.

Seems like my brain is a bit rusty from lack of Delphi usage and lazyness from AI usage :)

If I do find a bug in this code and the AI was correct about it I may return to this topic to bug you up your arse once again lol :)

@SkybuckFlying
Copy link
Author

OK, I had gemini analyze the "SaveSettings" part of the commit and it's associated bug, it's a small one but here it is:

In short I guess your code assumes there is always at least two characters at the beginning of the string:

C:

It's also kinda interesting to note that gemini kinda writes risky fix code, assuming boolean evaluation is "short" while in reality it might be enabled as "long"/"full".

It also confirmed the loops are identical, but I probably was not referring to that loop when I saw gemini mention some loop error.

(The other code will be examined below this one)

The Bug in the Old Code

The primary bug in the old code is a potential "Index out of bounds" runtime error.

Let's look at this specific line in the old code:
Code snippet

If s[2]=':' then s:=Copy(s,4,MaxInt);

This line attempts to access the second character of the WideString s (remember, Delphi strings are 1-indexed, so s[2] is the second character). The problem is that it does this without first checking if the string s actually has at least two characters.

    If s is an empty string (""), its length is 0. Attempting to access s[2] will cause an "Index out of bounds" error.

    If s is a single-character string (e.g., "A", "1", or even ":"), its length is 1. Attempting to access s[2] will also cause an "Index out of bounds" error.

This means that if memoIgnore.Lines contains any empty lines or lines with only one character, the SaveSettings procedure will crash.

Comparison of Old and New Code

The new code addresses this bug by adding a crucial length check before accessing s[2]:

Old Code (Bugged Line):
Code snippet

If s[2]=':' then s:=Copy(s,4,MaxInt);

New Code (Fixed Line):
Code snippet

if (Length(s) >= 2) and (s[2] = ':') then
  s := Copy(s, 4, MaxInt);

The addition of (Length(s) >= 2) as the first part of the if condition is the fix. Because Delphi uses short-circuit evaluation for and (meaning if the first part is false, the second part is not evaluated), s[2] will only be accessed if Length(s) is indeed 2 or greater. This prevents the "Index out of bounds" error.

It's worth noting that the logic for incrementing i within the while loop (only incrementing if a line was not deleted) is identical in both the old and new code. This specific loop pattern is the correct way to safely delete items from a dynamic list while iterating forward, as it accounts for the shifting indices after a deletion. The comments in the new code simply make this correct logic more explicit.

Concerning the more complex code.

Here is what Gemini has to say about it. I quickly ran this test program, it does seem to generate some range check error, I simply moved the WidePosEx function below it and renamed the call ot WidePosExChar, not sure if this is the correct function to use, as the original is missing as far as I am concerned, however digging deep into Tnt Unicode library might find it... maybe it was removed during it's evolution...

So far nothing to concerning. However my system keeps hanging on shutdown... I suspect RamService.exe has something to do with it so I will ask gemini to analyze it to see if it can find a reason for it.

I suspect the hang only happens after the RamService has been running for a while...

Explanation of the Bug in the Old Code

The primary bug in the old SplitPath procedure lies in how it handles the oldPos variable and the Copy function.

Incorrect Segment Length:

    When a delimiter (\) is found, the old code uses list[k]:=Copy(path,oldPos,newPos);.

    The Copy function takes a starting position and a length. newPos is the position of the delimiter itself, not the length of the segment.

    This will incorrectly copy the segment plus the trailing backslash, e.g., "C:\Users\User" would be split into "C:" and "Users". This is not the standard behavior for a path-splitting function. The length of the segment should be newPos - oldPos.

    The newPos variable should be used to determine the length, not as the length itself.

Incorrect oldPos Advancement:

    After copying a segment, the old code sets oldPos:=newPos;.

    This sets the oldPos to the position of the delimiter. In the next iteration, the WidePosEx function will start searching from the same position, which will cause it to find the same delimiter again in some cases, or it will start copying from the delimiter's position.

    The correct behavior is to advance oldPos to the character after the delimiter, i.e., oldPos := newPos + 1;.

Handling of Paths Ending with a Delimiter:

    The old code doesn't correctly handle paths that end with a backslash, such as "C:\Users\".

    The loop would find the last backslash, copy the preceding segment incorrectly, and then oldPos would be set to the position of the last backslash.

    In the next iteration, WidePosEx from this position would return 0 (newPos=0), and the code would copy Copy(path,oldPos,MaxInt), which would result in an empty string being copied. However, this logic is fragile and doesn't handle all edge cases correctly. The newPos=0 logic is only for the very last segment of the path, not for the case where the path simply ends with a delimiter. The correct behavior should be to add an empty string as a final segment.

The Fixes in the New Code

The new code correctly addresses these issues:

Correct Segment Length:

    The line list[k] := Copy(path, oldPos, newPos - oldPos); correctly calculates the length of the segment by subtracting the starting position from the delimiter's position. This ensures the backslash is not included in the segment.

Correct oldPos Advancement:

    The line oldPos := newPos + 1; correctly advances the starting position for the next search to the character after the delimiter.

Correct Handling of Trailing Delimiters and Edge Cases:

    The new code includes a check for if (oldPos > Length(path)) and (newPos <> 0) then.

    This specifically handles the case where the path ends with a delimiter. After the last delimiter is processed, oldPos will be Length(path) + 1. This condition then adds a final, empty string to the list, which is the correct behavior for a trailing delimiter.

    It also handles the empty path case if Length(path) = 0 then.

Delphi Program Demonstrating the Bug and Fix

This program defines both SplitPath procedures and a demonstration function TestSplitPath. It will output the results for a few test cases, clearly showing the incorrect output of the old code and the correct output of the new code.
Code snippet

program SplitPathDemo;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

type
  TStrArray = array of WideString;

// --- Old Code Block ---

Procedure SplitPath_Old(const path: WideString; var list: TStrArray);
var
  oldPos, newPos, k: Integer;
Begin
  SetLength(List, Length(path));
  k := 0;
  oldPos := 1;
  Repeat
    newPos := WidePosEx('\', path, oldPos);
    if newPos = 0 then
      list[k] := Copy(path, oldPos, MaxInt)
    Else
    Begin
      list[k] := Copy(path, oldPos, newPos);
      oldPos := newPos;
    end;
    Inc(k);
  Until newPos = 0;
  SetLength(list, k);
end;

// --- New Code Block ---

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;

Procedure SplitPath_New(const path: WideString; var list: TStrArray);
var
  oldPos, newPos, k: Integer;
Begin
  SetLength(List, Length(path) div 2 + 1);
  if Length(path) = 0 then
  begin
    SetLength(list, 0);
    Exit;
  end;

  k := 0;
  oldPos := 1;
  Repeat
    newPos := WidePosExChar('\', path, oldPos);

    if newPos = 0 then
    Begin
      list[k] := Copy(path, oldPos, MaxInt);
      Inc(k);
      Break;
    End
    Else
    Begin
      list[k] := Copy(path, oldPos, newPos - oldPos);
      Inc(k);
      oldPos := newPos + 1;
    End;

    if (oldPos > Length(path)) and (newPos <> 0) then
    begin
      if (path[Length(path)] = '\') then
      begin
        list[k] := '';
        Inc(k);
      end;
      Break;
    end;

  Until False;

  SetLength(list, k);
end;

// --- Demonstration and Output Code ---

procedure TestSplitPath(const aPath: WideString);
var
  oldList, newList: TStrArray;
  i: Integer;
begin
  Writeln('--- Testing Path: "', aPath, '" ---');
  
  // Test Old Code
  SplitPath_Old(aPath, oldList);
  Writeln('Old Code Result:');
  for i := Low(oldList) to High(oldList) do
    Writeln('  [', i, '] = "', oldList[i], '"');
  Writeln;
  
  // Test New Code
  SplitPath_New(aPath, newList);
  Writeln('New Code Result:');
  for i := Low(newList) to High(newList) do
    Writeln('  [', i, '] = "', newList[i], '"');
  Writeln;
  Writeln('-----------------------------------');
end;

begin
  try
    TestSplitPath('C:\Users\JohnDoe\Documents\file.txt');
    TestSplitPath('C:\'); // Path with a trailing backslash
    TestSplitPath('C:'); // Path with no backslashes
    TestSplitPath(''); // Empty path
    TestSplitPath('folder\subfolder\'); // Path ending with backslash
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

…k 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.
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.
@tmcdos
Copy link
Owner

tmcdos commented Aug 6, 2025

To fix this problem I add all units from Tnt Unicode library.

Looks like I as not completely correct in the README - it turns out I am actually using TMS UNICODE v2.1 which does have the WidePosEx function while the last free version of TnT Unicode (v2.3.0) does not have it.
The only reason for using TnTUnicode is to allow localized (e.g. Cyrillic or Greek) pathnames for the RAM-disk persistance feature. Newer Delphi versions already support Unicode natively but I am not using them.
Of course, if you decide to fork the project and convert it to be compatible with latest Delphi (it even seems they offer a free community version) - I am okay with that and if you come up with an idea how to keep the code compilable both in Delphi 7 and newer versions, I may backport this.

I kinda already proved this is in no way the case and additional libraries/files must be downloaded

Yes, the TMS Unicode library must be downloaded and installed. It is not included in this repository because it is not a code written by me. But I have now included the WidePosEx function in my code. Would you be so kind and test if you will be able to compile without errors this time with the public TnT Unicode?

The virus total scanner reports 2 malware indicators in the release version... possibly command and control functionality ?

If you do not trust my EXE - you are strongly encouraged to compile your own. Or you can disable the network access for that EXE in your firewall.

It would be interesting to know which exact version of Delphi you used to make the release version, which build settings, maybe even which tnt unicode library version/commit.

shot-1

Already mentioned TMS Unicode v2.1 (latest version is 2.5.0.1) which was bought by TMS from the original author Troy Wolbrink (https://www.yunqa.de/delphi/products/tntunicodecontrols/index)

The build settings are in the corresponding CFG and DOF files (e.g. RamdiskUI.dof).

Seems to work, however I do notice a shutdown failure on my system,

Troubleshooting will be problematic, especially without enough details.

would be helpfull if you posted exact code which you mean is similiar, I think I know what loop you referring to, to me it did look different, but didn't really study it that much.

shot-2

Oh one last thing, I tried to modernize the code by having the AI switch to unicode/W versions of API.

Which are these API functions that you need to be Unicode? There is definitely no need to make everything Unicode - just the parts that actually need to be.

Set of 'A' to 'Z', something like that.

Delphi's set type can only hold 256 values - https://stackoverflow.com/questions/72085371/delphi-check-if-a-unicode-character-occurs-in-a-set-of-characters
And we do not need Unicode here because Windows supports only 26 possibilities for drive letters.

One other crazy thing I noticed is Comparing PWhatever to Pwhatever2 not possible

Comparing PWideString to another PWideString is definitely possible - but it compares pointer values rather than null-terminated strings. Comparing PWideString to PWideByte obviously does not make sense and needs explicit type-casting.

This is basically what some code/constants in your units are doing

Can you be more specific?

Also this project requires a manifest/override security settings + it running constantly as a service makes it a bit suspicious/risky ;)

The manifest seems to contain a leftover from copy/pasting it from another project - the word SkypeLog in the name of the manifest. I will now change this to something less suspicious :))
The reason for asking Administrator rights is so that the UI (which is probably run as an unprivileged user) be able to install the service. You can safely remove that manifest together with the buttons Install service and Uninstall service and then manually install/uninstall the service when you need to.
The only purpose of the service is to create the RAM-disk at boot, pre-fill it from the specified folder and eventually point the TMP and TEMP environment variables to the RAM-disk. The service eventually persists the RAM-disk at shutdown, if configured for this. You can safely avoid using the service and only create the RAM-disk when you need it, then remove it from the UI.
Of course this will reduce your comfort at a certain degree - but will allow you to sleep happy and unconcerned about malware.

I do wonder how your tool fast the ramdisk ? because with just driver and batch files this would be difficult... volume has no name or something ? AI suggested some heuristic to find it quickly...

I have to admit that I have difficulties understanding what you are trying to say. I am not using any driver headers (I do not need to link the driver as DLL) - just calling the driver's API directly.

AI also suggested to use robocopy to copy files/back and forth

Robocopy could be used when populating the RAM-disk at boot (if configured for this) - but I am not sure whether it supports folder exclusion when persisting the RAM-disk at shutdown because I want to have/offer the feature of not persisting e.g. the TEMP folder. Anyway, the current code works like a charm.

AI mention your code had a bug in it, it was skipping a line index ?

No idea about this unless you can describe the steps for reproducing this bug.

makes system vunerable to *.hlp attacks

I am sitting on Win7 and have not experienced such attacks yet.

but can't remember which Delphi version it was that had this.

DDP stands for Delphi Diagram Portfolio - it is not created automatically, you must manually (if needed) drag components in the diagram to document your design (e.g. to show the parent-child relationships between DB-aware components)
If you need UML support - then you can use Bold for Delphi

I do wonder if it truely copies all files on shutdown

On theory Windows must wait for it to complete but it is possible that if Windows thinks it is hung, to use a timeout and not wait too much. Possible on theory but have not seen it in practice. Probably because I tend not to persist too much files - the feature is just in case I have downloaded something during the day on the RAM-disk and forgot to copy it on a non-volatile media before shutting down :)) But I do not want all the other disposable stuff from my workday to be persisted.

so far using it only for firefox webbrowser,

That was my primary goal, too - to use it with Chrome and avoid the gazillion of cache and temp content which both websites (service workers or IndexedDB) and browser itself tend to constantly create and thus avoid wearing out my SSD.

In short I guess your code assumes there is always at least two characters at the beginning of the string:

You are correct - it would be safer to first check the length of the string before indexing. Accidentally, I am actually using single-character strings in my config and have not experienced an exception or access violation:
shot-3

But that's because in my case [2] points to the null-byte which terminates the string. Apparently an empty line would produce access-violation so thanks for pointing out the bug. Will fix it.

I suspect the hang only happens after the RamService has been running for a while...

I am using it daily for at least 4 years and have never experienced such hangs. Although I have seen uTorrent to be forcibly closed by Windows on shutdown.

The primary bug in the old SplitPath procedure lies in how it handles the oldPos variable and the Copy function.

To be honest, I do not remember why this function is there since it is not used by any other part of the code. Most probably that's why the bug stayed undiscovered. Will remove this unused function.

@SkybuckFlying
Copy link
Author

I have not read your latest post/message, I will do so later.

Meanwhile I have updated this pull request, with 2 new commits, which attempted to give the driver/service more time to do ram loading/storing from/to disk, by using a seperate thread and informing SCM that service is pending/busy and giving it 60 seconds time out.

However AI reports SCM might close things within 30 seconds, depends on OS/registry settings.

Even if it were 30 or 60 seconds, this solution seems unstatisfactory to me.

I tried to get the AI to do more code edits and add proper thread to by-pass/run in parallel to whatever the ramdisk is doing, but that kinda failed to really work, instead the AI started injecting more call/checkpoints it did create some additional thread or whatever, but ultimately the code was problematic: missing handle to service. I could post it later, but for now keeping it to myself, I don't want to post broken code for now. Though the extra inject points were interesting, but again doesn't solve the problem completely.

However the newer/unpublished code did work around the potential delphi bug.

At least I will post that section so it can be re-used later, if tried again, it was also seperated into seperate unit, makes compiling it a bit easier:

unit StatusReporter;

interface

uses Windows, SvcMgr, WinSvc;

procedure ReportCustomCheckpoint(ServiceHandle: SERVICE_STATUS_HANDLE; CurrentState: DWORD; CheckPoint: DWORD; WaitHint: DWORD);

implementation

procedure ReportCustomCheckpoint(ServiceHandle: SERVICE_STATUS_HANDLE; CurrentState: DWORD; CheckPoint: DWORD; WaitHint: DWORD);
var
  Status: TServiceStatus;
begin
  Status.dwServiceType := SERVICE_WIN32_OWN_PROCESS;
  Status.dwCurrentState := CurrentState;
  Status.dwControlsAccepted := 0;
  Status.dwWin32ExitCode := NO_ERROR;
  Status.dwServiceSpecificExitCode := 0;
  Status.dwCheckPoint := CheckPoint;
  Status.dwWaitHint := WaitHint;
  SetServiceStatus(ServiceHandle, Status);
end;

end.

Seeing these potential bugs in Delphi 7 made me run scared ;)

I will re-attempt a translation to Modern Delphi 12.3 hoping that this might solve some of the problems/limitations, maybe a fools attempt, but I try it anyway.

This time a more thorough (spelling?) approach was taking:

Functionality description of source code so far... then re-creation into V2... trying this now.

Depending on success/failure, I will either proceed with modern delphi 12.3 attempts or fall back to Delphi 7.

@SkybuckFlying
Copy link
Author

SkybuckFlying commented Aug 6, 2025

OK, now I have read your latest message, thanks for responding it clearifies some things.

First let's start with my current Delphi 7 Enterprise edition, it seems slightly different from yours:

Delphi7EnterpriseOnSkybucksSuperPC2023

(Concerning this picture, at first I thought it said 7.C believing it might be newer than yours, but now that I look at it again it seems the ( is cutting of the zero, creating a false C ? not sure, changing dpi settings might shine more light on this, the build numbers however seem radically different: build 8.1 vs build 4.453 ????)

Second let's continue with the TnT components, if the ones you are using are not "free" then this will be problematic for users to compile your project...

Third I made a little typo:

Wrong sentence:
"
I do wonder how your tool fast the ramdisk ? because with just driver and batch files this would be difficult... volume has no name or something ? AI suggested some heuristic to find it quickly...
"

I ment to write:

Correct sentence:

I do wonder how your tool MATCH the ramdisk ? because with just driver and batch files this would be difficult... volume has no name or something ? AI suggested some heuristic to find it quickly...
"

Anyway, I've asked the AI to make a functional description of your tool, including safety risks with the RAMDISK formatting/detection, according to the AI it seems safe which is good to know !

I will post this functional description in a next message.

@SkybuckFlying
Copy link
Author

SkybuckFlying commented Aug 6, 2025

This may help to refresh memory or to help others to try and understand this project, especially how the RAMDISK is created/detected/formatted safely:

✦ I've reviewed the README.md file. It seems this is a Delphi 7 project that provides a GUI and a Windows service for the
Arsenal Virtual RAM-disk driver, offering features like pre-loading and synchronizing content.

  • RamdiskUI.dpr is the entry point for the GUI application. It uses the Main.pas unit, which likely contains the main form
    (TfrmUI).
  • RamService.dpr is the entry point for the Windows service. It uses the SrvMain.pas unit, which contains the service logic
    (TServiceArsenal).

Main.pas (GUI Application)

  • Purpose: Provides a user interface to configure the RAM disk settings, create/remove the RAM disk on demand, and
    install/uninstall the Windows service.
  • Functionality:
    • Loads and saves settings to the Windows Registry under
      HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ArsenalRamDisk.
    • The settings include disk size, drive letter, content to preload, folders to exclude from synchronization, and other
      options.
    • It can create a RAM disk (CreateRamDisk) and remove it (DetachRamDisk).
    • It can install and uninstall the RamService by executing RamService.exe /install and RamService.exe /uninstall.
    • It checks the status of the ArsenalRamDisk service.
    • It dynamically updates the list of available drive letters.

SrvMain.pas (Windows Service)

  • Purpose: To automatically create the RAM disk at system startup and persist its content on shutdown.
  • Functionality:
    • On service start (ServiceStart), it reads the configuration from the same registry location as the GUI and calls
      CreateRamDisk.
    • On service stop (ServiceStop) or system shutdown (ServiceShutdown), it calls DetachRamDisk to save the RAM disk content
      (if configured to do so) and remove the RAM disk.
    • It logs messages using a DebugLog procedure (the implementation of which is likely in another unit).

Core Logic Units

Both Main.pas and SrvMain.pas rely on other units for the core RAM disk operations:

  • RamCreate.pas: Likely contains the CreateRamDisk function.
  • RamRemove.pas: Likely contains the DetachRamDisk function.
  • RamDetect.pas: Seems to have a GetRamDisk function to check for existing RAM disks.
  • Definitions.pas: Probably contains the TRamDisk record structure and other shared types and constants.

Definitions.pas

  • This is the heart of the project's data structures and constants.
  • It defines the TRamDisk record, which holds all the configuration for a RAM disk instance.
  • It defines ERamDiskError for custom exception handling.
  • It contains a large number of low-level Windows API constants and data structures related to disk and volume management,
    SCSI communication, and device I/O control (IOCTLs).
  • It includes function declarations for interacting with the Arsenal driver (ImScsi... functions) and helper functions like
    GetFreeDriveList and DebugLog.

RamCreate.pas

  • Contains the CreateRamDisk function, which is the main entry point for creating and setting up the RAM disk.
  • Workflow:
    1. Opens the Arsenal SCSI adapter (ImScsiOpenScsiAdapter).
    2. Checks the driver version (ImScsiCheckDriverVersion).
    3. Sends an IOCTL to the driver to create the virtual disk device (SMP_IMSCSI_CREATE_DEVICE).
    4. Waits for the physical disk to appear in the system.
    5. Creates a partition on the new disk (IOCTL_DISK_SET_DRIVE_LAYOUT).
    6. Formats the new partition as NTFS using the undocumented FormatEx function from fmifs.dll.
    7. Finds the newly created volume.
    8. Assigns the requested drive letter (or the first available one) to the volume (SetVolumeMountPoint).
    9. Calls LoadRamDisk (from RamSync.pas) to preload content if configured.

RamDetect.pas

  • Contains the GetRamDisk function.
  • Workflow:
    1. Opens the Arsenal SCSI adapter.
    2. Queries the driver for an existing virtual disk device (ImScsiQueryDevice).
    3. If a device is found, it retrieves its size.
    4. It then calls GetRamDiskLetter (from RamVolume.pas) to find the associated drive letter.

RamRemove.pas

  • Contains the DetachRamDisk function.
  • Workflow:
    1. If synchronization is enabled, it calls SaveRamDisk (from RamSync.pas) to save the content.
    2. Opens the volume by its drive letter.
    3. Sends a WM_DEVICECHANGE broadcast message to notify applications that the device is about to be removed.
    4. Flushes file buffers.
    5. Locks and dismounts the volume (FSCTL_LOCK_VOLUME, FSCTL_DISMOUNT_VOLUME).
    6. If the RAM disk has no drive letter, it attempts to remove the device directly via an IOCTL to the driver
      (SMP_IMSCSI_REMOVE_DEVICE).
    7. Calls RestoreTempFolder to revert any changes made to the system's TEMP environment variables.

Project Understanding: RamDisk Support Utility

1. High-Level Goal

The project is a GUI application and an accompanying Windows service designed to manage the Arsenal Virtual RAM-disk driver. Its primary purpose is to provide a user-friendly interface for creating, configuring, and persisting a RAM disk, similar to the popular ImDisk Toolkit.

The key features are:

  • Creating a RAM disk of a specified size.
  • Assigning a specific drive letter.
  • Pre-loading the RAM disk with content from a local folder at startup.
  • Synchronizing the RAM disk's content back to the local folder on shutdown.
  • Automatically creating the RAM disk when the system boots up via a Windows service.

2. Architecture

The project consists of two main executables:

  1. RamdiskUI.exe: A Delphi-based GUI application that serves as the control panel. Users can:

    • Set the RAM disk size, drive letter, and the folder for content persistence.
    • Manually create ("Apply") and remove ("Unmount") the RAM disk.
    • Save settings to the registry for the service to use.
    • Install and uninstall the RamService.exe Windows service.
  2. RamService.exe: A background Windows service that handles the automatic creation and removal of the RAM disk during system boot and shutdown.

    • On Start: It reads the configuration saved by the GUI from the registry and creates the RAM disk.
    • On Shutdown/Stop: It saves the RAM disk's content back to the designated folder (if configured) and then removes the RAM disk.

Configuration is shared between the GUI and the service via the Windows Registry under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ArsenalRamDisk.

3. Core Implementation Details

The application is written in Delphi 7 and interacts with the underlying Windows systems and the Arsenal driver at a low level.

  • **Driver Communication (Definitions.pas, RamCreate.pas, RamRemove.pas):

    • The application does not contain the RAM disk driver itself. It is a client for the pre-installed Arsenal driver.
    • Communication with the driver is handled via DeviceIoControl calls with specific IOCTL_SCSI_MINIPORT control codes. The project defines the necessary data structures (TScsiCreateData, TScsiRemoveDevice, etc.) to pass information to and from the driver.
  • **RAM Disk Creation (RamCreate.pas):

    1. It first finds and opens a handle to the Arsenal SCSI adapter.
    2. It sends a SMP_IMSCSI_CREATE_DEVICE command to the driver to create the virtual device.
    3. After the device is created, it waits for Windows to recognize it as a new physical disk (\\?\PhysicalDriveXX).
    4. It then programmatically creates a partition on this new disk (IOCTL_DISK_SET_DRIVE_LAYOUT).
    5. The new partition is formatted as NTFS using the undocumented FormatEx function from the system library fmifs.dll.
    6. Finally, it assigns the desired drive letter to the new volume.
  • **RAM Disk Removal (RamRemove.pas):

    1. If synchronization is enabled, it copies the contents from the RAM disk back to the persistence folder.
    2. It sends a WM_DEVICECHANGE broadcast message to gracefully inform other applications that the volume is being removed.
    3. It locks the volume (FSCTL_LOCK_VOLUME) and then dismounts it (FSCTL_DISMOUNT_VOLUME).
    4. It sends a SMP_IMSCSI_REMOVE_DEVICE command to the driver to destroy the virtual device.
  • **Detection (RamDetect.pas):

    • The application can detect if an Arsenal RAM disk is already running by querying the driver for existing devices.

4. Dependencies

  • Arsenal Image Mounter Driver: Must be installed on the system for the application to function.
  • Delphi 7 & TNT-Unicode Library: Required to compile the source code.
  • Administrator Privileges: The application and service require elevated privileges to interact with drivers and manage system volumes.

Project Source Code Summary

This document outlines the functionality of each source code file in the RamDisk Support Utility project.


RamdiskUI.dpr

  • Purpose: The main project file and entry point for the GUI application (RamdiskUI.exe).
  • Functionality: Initializes the application, creates the main form (TfrmUI from Main.pas), and starts the application
    message loop.

RamService.dpr

  • Purpose: The main project file and entry point for the Windows Service (RamService.exe).
  • Functionality: Initializes the service application, creates the service object (TArsenalRamDisk from SrvMain.pas), and
    starts the service control dispatcher.

Main.pas

  • Purpose: Implements the main window and user interface logic for the GUI application.
  • Functions/Procedures:
    • FormCreate: Initializes the form, setting numeric-only input for the size field.
    • FormShow: Populates the UI with current settings when the form is displayed. It checks for existing RAM disks, loads
      settings from the registry, and updates the status of the Install/Uninstall buttons based on the service's presence.
    • LoadSettings: Reads RAM disk configuration (size, drive letter, sync options, etc.) from the registry
      (HKLM\SYSTEM\CurrentControlSet\Services\ArsenalRamDisk).
    • SaveSettings: Writes the user's configuration from the UI into the registry. It also cleans up the "exclude folders"
      list.
    • UpdateLetters: Populates the drive letter combo box with available, unused drive letters.
    • UpdateMounted: Updates the UI to reflect that a RAM disk is currently mounted (e.g., shows size, disables "Apply"
      button, enables "Unmount").
    • UpdateDismounted: Updates the UI to reflect that no RAM disk is mounted.
    • btnApplyClick: Creates a new RAM disk based on the settings in the UI. It first unmounts any existing disk, then calls
      CreateRamDisk.
    • btnUnmountClick: Unmounts the currently active RAM disk by calling DetachRamDisk.
    • btnSaveClick: Manually saves the current settings to the registry.
    • btnInstallClick: Installs the Windows service by executing RamService.exe /install.
    • btnUninstallClick: Uninstalls the Windows service by executing RamService.exe /uninstall.
    • btnLoadClick: Opens a folder selection dialog to choose the directory for pre-loading/synchronizing content.
    • btnQuitClick: Closes the application.
    • chkSyncClick: Enables/disables the synchronization options group box.
    • ServiceGetStatus: A helper function to check if the ArsenalRamDisk service is installed and what its current state is
      (e.g., running, stopped).

SrvMain.pas

  • Purpose: Implements the core logic for the ArsenalRamDisk Windows Service.
  • Functions/Procedures:
    • LoadSettings: Reads the RAM disk configuration from the registry. This is the same function as in Main.pas but used by
      the service.
    • ServiceStart: The main event handler for when the service starts (e.g., at boot). It calls LoadSettings and then
      CreateRamDisk to create the RAM disk automatically.
    • ServiceStop: Event handler for when the service is stopped. It calls DetachRamDisk to safely unmount the RAM disk.
    • ServiceShutdown: Event handler for when the system is shutting down. It also calls DetachRamDisk to ensure data is
      synchronized before the system powers off.
    • ServiceAfterInstall: A callback that runs after the service is installed. It writes the service's description into the
      registry.
    • ServiceExecute: The main execution loop for the service, which processes service requests.

Definitions.pas

  • Purpose: A central unit containing shared data types, constants, low-level API declarations, and common helper functions
    used across the project.
  • Key Contents:
    • TRamDisk: The core record that holds all information about a RAM disk instance (size, letter, paths, etc.).
    • ERamDiskError: A custom exception class for handling RAM disk-specific errors.
    • Constants: Defines numerous Windows API constants for file access, device control (IOCTLs), and SCSI commands.
    • Low-Level Structures: Defines Pascal equivalents of Windows structures needed for interacting with the disk and volume
      APIs (e.g., TScsiDeviceConfig, TVolumeDiskExtents, TObjectAttributes).
    • API Imports: Declares functions imported from ntdll.dll and kernel32.dll.
    • GetFreeDriveList: Returns a set of available drive letters.
    • ImScsi...: A family of helper functions (ImScsiInitializeSrbIoBlock, ImScsiOpenScsiAdapter, etc.) that wrap low-level
      DeviceIoControl calls to communicate with the Arsenal driver.
    • DebugLog: A procedure to write messages to the Windows Event Log under the "Arsenal RamDisk" source.

RamCreate.pas

  • Purpose: Handles the entire process of creating, partitioning, formatting, and mounting the RAM disk.
  • Functions/Procedures:
    • CreateRamDisk: The main function. It orchestrates the creation process:
      1. Opens the Arsenal driver.
      2. Sends a command to create the virtual device.
      3. Waits for the new disk to be recognized by Windows.
      4. Creates a primary partition on the disk.
      5. Formats the partition with the NTFS file system using the undocumented FormatEx function from fmifs.dll.
      6. Assigns the specified drive letter.
      7. Calls LoadRamDisk to populate it with initial content.
    • ImScsiCheckDriverVersion: Checks if the installed Arsenal driver meets the minimum required version.
    • ImDiskFindFreeDriveLetter: Finds the first available drive letter from 'C' to 'Z'.
    • FormatCallback: A placeholder callback function required by FormatEx, which does nothing.

RamRemove.pas

  • Purpose: Handles the safe detachment and removal of the RAM disk.
  • Functions/Procedures:
    • DetachRamDisk: The main function. It orchestrates the removal process:
      1. Calls SaveRamDisk if synchronization is enabled.
      2. Notifies the system and other applications that the volume is about to be removed (WM_DEVICECHANGE).
      3. Flushes file buffers to ensure all data is written.
      4. Locks and dismounts the volume.
      5. Sends a command to the Arsenal driver to remove the virtual device.
      6. Calls RestoreTempFolder to revert any system TEMP variable changes.
    • ImScsiRemoveDeviceByNumber: A wrapper function to send the "remove device" command to the driver.
    • OpenVolume, PreventRemovalOfVolume, AutoEjectVolume: Helper functions for low-level volume management.

RamDetect.pas

  • Purpose: Contains logic to detect if an Arsenal-powered RAM disk is already running.
  • Functions/Procedures:
    • GetRamDisk: The main function. It queries the Arsenal driver for any active virtual disks and, if found, populates a
      TRamDisk record with its details (size, drive letter, etc.) by calling GetRamDiskLetter.

RamVolume.pas

  • Purpose: Contains helper functions for dealing with disk volumes, specifically finding the drive letter and physical disk
    number associated with the RAM disk.
  • Functions/Procedures:
    • GetRamDiskLetter: Enumerates all volumes in the system, checks which one is managed by the Arsenal driver, and returns
      its assigned drive letter.
    • ImScsiOpenDiskByDeviceNumber: Finds the PhysicalDriveXX number that corresponds to a specific virtual device created by
      the Arsenal driver.

RamSync.pas

  • Purpose: Manages the synchronization of files between the RAM disk and a persistent local folder. It also handles the logic
    for using the RAM disk as the system's temporary folder.
  • Functions/Procedures:
    • LoadRamDisk: Called after the RAM disk is created. It copies files from the persistent folder to the new RAM disk and,
      if configured, updates the system's TEMP and TMP environment variables to point to a TEMP folder on the RAM disk.
    • SaveRamDisk: Called before the RAM disk is removed. It copies files from the RAM disk back to the persistent folder. It
      respects a list of excluded directories.
    • RestoreTempFolder: Reverts the system's TEMP and TMP environment variables back to their original values.
    • TreeCopy, TreeSave, TreeDelete, DelTree: Recursive file/directory operation helpers for copying, saving (with overwrite
      checks), and deleting directory trees.

RamSetup.pas

  • Purpose: Handles hardware rescanning to make Windows recognize the newly created virtual disk device promptly.
  • Functions/Procedures:
    • ImScsiRescanScsiAdapterAsync: The main function. It initiates an asynchronous hardware rescan of the SCSI bus where the
      Arsenal driver resides. This forces Windows to detect the new disk without requiring a reboot. It uses functions from
      cfgmgr32.dll.

Junctions.pas

  • Purpose: Provides helper functions to create and read NTFS junction points (symbolic links for directories). This is used
    during the synchronization process to correctly replicate junctions instead of traversing into them.
  • Functions/Procedures:
    • GetSymLinkTarget: Given a path to a junction, returns the path of the target directory.
    • CreateJunction: Creates a new NTFS junction point at a specified location, pointing to a target directory.

Safety:

The risk of formatting the wrong drive is the most important safety consideration for a utility like this.

Based on a detailed analysis of the source code, the risk of this utility misidentifying the RAM disk and formatting the
wrong drive is extremely low. The developer has implemented a robust, multi-step identification and verification process to
prevent this.

Here is the step-by-step process the utility uses, which ensures it targets the correct disk:

  1. Creation and Unique ID (RamCreate.pas):

    • When CreateRamDisk asks the Arsenal driver to create a new device, the driver returns a unique SCSI address for that
      new virtual disk. This address is a combination of PortNumber, PathId, TargetId, and Lun. This unique address acts as a
      "receipt" for the specific device that was just created.
  2. System-Wide Search (RamVolume.pas):

    • The utility then needs to find this new device, which now appears to Windows as a PhysicalDrive.
    • The function ImScsiOpenDiskByDeviceNumber iterates through all PhysicalDriveX devices on the system.
  3. Primary Verification: SCSI Address Matching (RamVolume.pas):

    • For each PhysicalDriveX, it sends an IOCTL_SCSI_GET_ADDRESS request, asking the drive, "What is your SCSI address?".
    • It then compares the address returned by the drive to the unique "receipt" address it received from the Arsenal driver
      in step 1.
    • It will only consider a disk a match if the PortNumber, PathId, TargetId, and Lun all match exactly. This is
      the core safety mechanism. It's not looking for a name or a letter; it's matching a specific, low-level hardware
      address.
  4. Secondary Verification: Disk Size Matching (RamVolume.pas):

    • As a crucial secondary check, after finding a disk with the correct SCSI address, the code immediately queries that
      disk for its size (IOCTL_DISK_GET_LENGTH_INFO).
    • It compares the reported size of the disk with the size that the user originally requested for the RAM disk.
    • If the sizes do not match, it rejects the disk and continues searching. This prevents an issue where, in a highly
      improbable scenario, an incorrect disk might share a SCSI address.
  5. Targeted Formatting (RamCreate.pas):

    • Only after a disk has passed both the SCSI address match and the size match does the utility proceed.
    • It then creates a partition on that specific disk handle.
    • Finally, it finds the new volume that appeared on that partition and calls the FormatEx function on that volume's
      unique system identifier (e.g., \?\Volume{...}), not on a potentially ambiguous drive letter.

Conclusion

The utility does not guess or rely on fragile methods like "the last disk that appeared." It uses a specific, unique
identifier provided directly by the driver and verifies it with a secondary check (disk size) before performing any
destructive operations.

The only conceivable (and astronomically unlikely) failure scenarios would be:

  • A severe bug in the Arsenal driver itself, where it returns an incorrect SCSI address that happens to match another
    physical disk.
  • A bug in the Windows kernel in handling IOCTL requests.

These scenarios are outside the control of the utility's code, which appears to be written defensively and correctly.
Therefore, the process is as safe as it can reasonably be.

@SkybuckFlying
Copy link
Author

SkybuckFlying commented Aug 6, 2025

Concerning the Delphi 12.3 vs Delphi 7 situation. I would be very much interested in a version of this tool for Delphi 12.3 so I can work with Delphi 12.3 instead of Delphi 7, even though I do like Delphi 7 a lot. (I might even try and obtain a copy of Delphi 2007 as well, that would basically give me all 3 essential delphi versions. Delphi 7 for win95/win98 era, Delphi 2007 for win xp, Delphi 10.3 for windows 7, Delphi 12.3 for Windows 11).

Another benefit might be bug fixes in VCL/RTL of Delphi 12.3, ofcourse new bugs could exist as well.

Another adventage could be using slighter newer VCL or even Firemonkey in future.

May main worry is that this project might be lost in the future if something changes in future versions of windows... maybe the ansichar/ansi string versions of API removed, or 32 bit windows support removed, things like that.

So for that reason a Delphi 12.3, 64 bit version of this tool and driver would be preferred/smart for future use.

Perhaps only the newer unicode APIs of windows 11 are fixed... perhaps older ansichar/ansistring apis will not be fixed by Microsoft.

So for me it's also trying to stay recent, trying to avoid any potential issues/bugs in older APIs/code paths, ofcourse the opposite could happen to, newer APIs could have buggy behaviour as well.

I am surprised that you have not yet witnessed the shutdown hang, which makes me doubt my hypothesis that the ramdriver/service is at fault.

My ramdisk is currently 2 gigabyte for firefox, how big is your ramdisk ? :)

Also a difference between you and me is that my ramdisk writes back to harddisk instead of SSD.

That could explain why my system is taking longer to do so... however the shutdown seems quite fast, just a few seconds, so maybe there is more to it. Or maybe something else is the cause.

But a shutdown of a few seconds could indicate that the ramservice is being aborted/terminated before proper writes, so that is also a little concerning.

What I could do is create a large RAMDISK of say 100 gigabyte.

Then put files on em like 1 MB files or 1 GB files, maybe modify them in memory/on ram disk to make sure they are different, then shutdown the system, theoretically this would take pretty long, like minutes to write back.

100 GB / 180 MB/sec = probably something like 10 minutes roughly.

These files could be created on ram disk only, to prevent them being on disk already, this should force the ram driver to write quite a while while shutting down.

Unless this ramdisk service already writes a little bit during usage ? but I do not believe it does so, and windows is probably not smart yet to understand :)

At least this would then shine some light on if the ram disk service is functioning correctly or if files are being lost.

I guess I should start with this, but was kinda boring/lazy to do, but I must now confirm this issue to be real before proceeding I guess, otherwise wasting my time, but not entirely.

There is the possibility that something else is causing shutdown hang, like buggy nvidia driver.

My system has 128 GB of RAM, not sure what your system has ?

Because the problem is on my side and not on your side, it would make more sense if you try and re-create this problem safely on your side.

For example create a new ramdisk, with many little shitty files, then instead of writing it back to SSD, try writing it back to a harddisk ? Ofcourse if you don't have a harddisk this would not be possible.

One alternative could be to simulate the harddisk writing and simply build in a long sleep of a few minutes or so to see how windows behaves. At least this would tell us if windows 7 is behaving ok together with this driver.

This could isolate a potential bug in windows 11 itself, though speculation for now...

Another idea which I first want to explore is simulating shutdown behaviour with SCM, maybe Microsoft forsaw this problem and there is a way to simulate shutdowns... plus I also want to install sandbox feature of windows 11, might be a smarter/better way to test these things... so I am not yet ready to write huge files to/from ramdisk which might be buggy, not sure what is going to happen for now it's probably not buggy enough to cause any issues, but would like to dive a little bit deeper before attempting this, might save some time, or maybe have to dive deeper into it anyway, have not yet made up my mind, so for now more code exploration, until I am more or less certain it's not the source code/driver at fault...

So for now I have no way to be sure which is causing the shutdown failure, it's kinda hard to simulate... it also doesn't make much sense to me.

Why would a quick boot/shutdown cycle not cause problems and a longer boot/shutdown cycle after browser usage does cause shutdown hang ?

The only thing I can think of is that your ram service detects changes between the files on both disks and only writes the necessary changes or something from ram back to disk, that could explain why it might take longer... so I would need to confirm this hypothesis a little bit...

However here is the strange bit: there is no disk activity after a few seconds, so that is why I believe performing this ram to disk write test is useless anyway.... so that is why I am focussing my time on trying to find any bug in the code itself...

Though a bigger write test might still shine some light on it, but I just don't believe in it for now, making me feel like I waste my time on it.

But at least I can ask AI to write a test program to generate these big files on ram disk...

However the AI has detected enough problems with the original code to try and fix those first anyway...

Plus debugging it seems to be a better approach than just wild guessing, so I am trying to get this code into a state where I might be able to debug it better with Delphi 12.3.

Delphi 7 seemed to maybe have some debugger issues, not sure, it warns that it wants to install some hooks or something, but maybe this is related to remote debugging... Delphi 7 is so old, it does not feel safe to use this for debugging on Windows 11.

Plus I also do not want to do unnecessary boot cycles on this computer, plus if something really strange is happening I have no idea would could truely happen so from safety perspective seems better I try and figure out what is causing the shutdown bug...

Basically the monitor goes dark, the system stops responding, the gpu leds/cpu leds stay on, so I have literally no idea what it is doing, so I find that situation a bit scary, hence my reluctance to do any kind of crazy write tests while the system is in a very weird state ;) :) However this is a bit hypocrit because tool might already be doing some writes, but 100 GB writes... hmmm... I have to be really sure it's not going to overwrite anything big... so I'll think about if I want to do these tests.

A safer test, could be introduce a sleep or maybe some beeping or something to know what it's doing...

@tmcdos
Copy link
Owner

tmcdos commented Aug 7, 2025

Meanwhile I have updated this pull request, with 2 new commits, which attempted to give the driver/service more time to do ram loading/storing from/to disk, by using a seperate thread and informing SCM that service is pending/busy and giving it 60 seconds time out.

Will take a look at this later.

However AI reports SCM might close things within 30 seconds, depends on OS/registry settings.

It will close if the OnStart of the service does not respond to the SCM within 30 sec (this default can be changed through registry) that the service has started.

Even if it were 30 or 60 seconds, this solution seems unstatisfactory to me.

It will only happen if pre-filling on boot or pesisting on shutdown takes more than 30 sec. Unlikely to happen in most situations but I agree that a separate thread on boot is a better solution. However, background thread will not solve the problem on shutdown - because as soon as the service reports to SCM that it has finished, kernel will continue with the shutdown and not wait for the background thread to finish the persisting. The simple (and probably naive) workaround is to never copy too many data from/to RAM-disk on boot/shutdown. In my case the amount of data is less than 400kB.

I tried to get the AI to do more code edits and add proper thread to by-pass/run in parallel to whatever the ramdisk is doing

Not sure what exactly are you trying to achieve here. Service creates the RAM-disk on boot, moves the system-wide TEMP folder if configured, pre-loads some data if configured - then waits for the shutdown signal without doing anything.
Obviously it can not move the TEMP folder or pre-load any data to RAM-disk without first creating that RAM-disk - so definitely sequential work here, not parallel. Or, if you mean copying files in parallel - I highly doubt it will decrease the total time.

However the newer/unpublished code did work around the potential delphi bug.

What bug is this?

I will re-attempt a translation to Modern Delphi 12.3 hoping that this might solve some of the problems/limitations, maybe a fools attempt, but I try it anyway.

It might be possible to use FreePascal / Lazarus instead of Delphi 12.3 - but this will probably also need some tweaking of the current code.

First let's start with my current Delphi 7 Enterprise edition, it seems slightly different from yours:

You probably need to install the first update (service pack) - there are 3 in total for Delphi 7.

Second let's continue with the TnT components, if the ones you are using are not "free" then this will be problematic for users to compile your project...

I agree. Can you check that you are able to compile the new code with the free version?

I do wonder how your tool MATCH the ramdisk

Again not sure what do you mean. The service creates the RAM-disk on boot (or the UI creates it on the fly - if you do not use the service) as an SCSI device, then creates a partition on this device, then creates a NTFS filesystem on that partition, then formats that filesystem, then creates a TEMP folder if required, then changes the environment variables, and finally pre-loads the RAM-disk with the specified folder (from another disk drive, obviously)

This may help to refresh memory or to help others to try and understand this project, especially how the RAMDISK is created/detected/formatted safely:

Basically, the method is a simplified version of the same method that the own CLI tool from Arsenal does it. Looks like you already produced an explanation of the code from the AI.

Another benefit might be bug fixes in VCL/RTL of Delphi 12.3, ofcourse new bugs could exist as well.

Might benefit you if you intend to develop other projects in Delphi in the future. Otherwise highly unprobable to see fixes that are relevant for this particular project.

Another adventage could be using slighter newer VCL or even Firemonkey in future.

Honestly no idea why would you need or want Firemonkey instead of VCL for this particular project. But it is your choice.

May main worry is that this project might be lost in the future if something changes in future versions of windows

If M$ decides to break some backwards compatibility if will probably affect some insignificant amount of users. Even now there is not a small number of users who are still using Win7 and have no intention to change it. If this project ceases to work properly on some newer Windows version - I definitely won't use such version of Windows :)

maybe the ansichar/ansi string versions of API removed, or 32 bit windows support removed, things like that.

Most likely will force some amount of users to switch to Linux or MacOS. No idea how many users, though.

So for that reason a Delphi 12.3, 64 bit version of this tool and driver would be preferred/smart for future use.

I guess the driver is already 64-bit and the tool can probably be compiled with FreePascal as a 64-bit executable.

Perhaps only the newer unicode APIs of windows 11 are fixed... perhaps older ansichar/ansistring apis will not be fixed by Microsoft.

What fixes do you need? I mean - what problem are you facing that needs fixing?

So for me it's also trying to stay recent, trying to avoid any potential issues/bugs in older APIs/code paths, ofcourse the opposite could happen to, newer APIs could have buggy behaviour as well.

Not a primary priority for me. If someone finds a bug in my code - I will try to fix it. If there are no bugs - I will use the code forever because it just does the job. I had a former colleague who was obsessed of being recent (probably still being) - everyday updating almost all of his software. Sometimes taking an hour or two of his workday. Meanwhile I was using "not so recent" soft without any issues and used my workday efficiently.

I am surprised that you have not yet witnessed the shutdown hang, which makes me doubt my hypothesis that the ramdriver/service is at fault.

If you are not persisting the RAM-disk - probably something weird in your Windows like a particular update or particular registry setting, who knows? After installing some update on my Win7 (unfortunately I have no clue which one exactly) I now experience this weirdness that the keyboard layout tends to often change to non-English (to my native language) on its own, randomly - usually when switching between applications with ALT+TAB. It's really annoying.

My ramdisk is currently 2 gigabyte for firefox, how big is your ramdisk ?

It is 16GB because I usually put the node_modules folders of my JavaScript projects during the day. They sometimes can become 1-2GB each. Chrome also gradually increases its temporary files. PhpStorm also stores some cache of its own. Sometimes I edit audio files - e.g. normalize the volume if they are too quiet. The audio editor produces HUGE temp files and does not bother to clean up after itself. Things like that :)

Also a difference between you and me is that my ramdisk writes back to harddisk instead of SSD.

It very much depends on how much data is being written. For me it is almost always zero - unless I have forgotten some PDF book(s) or some Youtube video on the RAM-disk.

That could explain why my system is taking longer to do so... however the shutdown seems quite fast, just a few seconds, so maybe there is more to it. Or maybe something else is the cause.

Quite possible. You can measure how much does it take to copy your files from the RAM-disk to the HDD - while you are normally using it.

But a shutdown of a few seconds could indicate that the ramservice is being aborted/terminated before proper writes, so that is also a little concerning.

In such case Windows supposedly should log the timeout in the EventLog. Have you checked?

Then put files on em like 1 MB files or 1 GB files, maybe modify them in memory/on ram disk to make sure they are different, then shutdown the system, theoretically this would take pretty long, like minutes to write back.

Definitely. I would not leave such large files on the RAM-disk when it is configured for persistance. The only reason I am using persistance is just so I do not lose some file that I downloaded from somewhere (and put on the RAM-disk to evaluate before deciding whether to keep it or delete it) that I do not remember the URL at the end of the day.
My download folder is set to the RAM-disk specifically so that I don't end up with some large pile of files on my non-volatile drive that I may never find the time to sort out or organize into categories/subfolders.
The RAM-disk kind of reminds me to always take the time to evaluate the downloaded content and put it in the correct folder/category on my regular drive.

Unless this ramdisk service already writes a little bit during usage

It does not and must not because this defats the purpose of the RAM-disk to prevent wearing out the SSD.

There is the possibility that something else is causing shutdown hang, like buggy nvidia driver.

I am even remotely incapable of guessing what the cause might be in your case ...

My system has 128 GB of RAM, not sure what your system has

64 but never used even half of them.

Because the problem is on my side and not on your side, it would make more sense if you try and re-create this problem safely on your side.

I am pretty sure that if you try to persist 1+GB file on a slow spinning disk - it will probably time out and Windows will try to forcibly shutdown. If instead Windows is hanging and not shutting down - then most probably there is some infinite loop somewhere (who knows where?)
It is not wise to persist such big files automatically - don't use the persistance feature and copy them manually to make sure they are not damaged or lost. Perhaps these checkpoints could help SCM to give more time to the service on shutdown but I am not an expert in Windows internals. I am not sure that even the original CLI of the Arsenal driver supports persistance.

Why do you need to persist so big amount of data off your RAM-disk? On theory, RAM-disks are supposed to be volatile - and only used for speeding the work and then tossing off the data.

plus I also want to install sandbox feature of windows 11, might be a smarter/better way to test these things

I think it will be easier if you try a VM like VirtualBox or VMware or even ProxMox. Or maybe you can experiment with some other RAM-disk software, e.g. ImDisk ?

Why would a quick boot/shutdown cycle not cause problems and a longer boot/shutdown cycle after browser usage does cause shutdown hang ?

Can you try without persistance if there is still a hang?

The only thing I can think of is that your ram service detects changes between the files on both disks and only writes the necessary changes or something from ram back to disk, that could explain why it might take longer

The code excludes the folders that are marked for exclusion, then (if the checkbox is ticked) first removes those files from the persistent folder that are no longer present on the RAM-disk, and then copies all the remaining folders recursively, file by file.

making me feel like I waste my time on it.

The chances are high :))

Delphi 7 seemed to maybe have some debugger issues, not sure, it warns that it wants to install some hooks or something, but maybe this is related to remote debugging.

Not sure what hooks are these, could be for remote debugging - but you do not have 2 computers for this experiment, do you?
If you want to debug step-by-step - you can copy temporarily some code from the service to the UI and then step over it or measure how much does it take to execute.
Debugging the live service will be quite problematic and I have never done such things.

but 100 GB writes

I have to admit I would never try to write 100GB at shutdown - I am pretty confident it will not succeed. Why do you need these 100GB written anyway ? If you are using the RAM-disk as a stash folder for Photoshop - it makes sense to save the final outcome on your HDD after you finish all intermediate retouching (which migh benefit from the RAM-disk speed) ....

I have to be really sure it's not going to overwrite anything big

An idea comes to me to write each file under a unique random name - and then rename it to the real name. Renaming is an atomic operation - so if the write is unsuccessful, the orginal file will stay untouched and there will be some bogus temp file with a random name alongside.

introduce a sleep or maybe some beeping or something to know what it's doing

Perhaps adding messages to the EventLog may help?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants