Skip to content

fix: absolute paths are never translated for wide-char APIs - #149

Open
x87 wants to merge 1 commit into
thelink2012:masterfrom
x87:fix_abs_wide_path
Open

x87 wants to merge 1 commit into
thelink2012:masterfrom
x87:fix_abs_wide_path

Conversation

@x87

@x87 x87 commented Sep 13, 2026

Copy link
Copy Markdown

TL;DR: modloader can translate absolute paths coming from narrow (A) Win32 functions, but not the same path when it arrives through a wide (W) function.


CLEO's stream_custom_script uses std::filesystem / std::ifstream, which call the wide functions
(GetFileAttributesExW, CreateFileW). If the path to the script is absolute, modloader can't match it with the file that exist only inside the mod directory. While it can be argued as intended behavior (after all, it is absolute path to a file which does not exist at said location), to user's surprise does_file_exist works fine, because it uses narrow GetFileAttributesA API.

if
  file_exists A // true, OK
then
  stream_custom_script A // false, what?
end

Repro

modloader_abs_repro.zip

Drop the folder into <SA>\modloader\, put your game folder in abs_path.ini, run the game, then read
<game>\abs_repro.txt. Tested with both CLEO 4.4.4 and CLEO 5.4.0 and ModLoader 0.3.11:

CLEO std.asi.dll abs_repro.txt
5.4.0 stock abs: exists=1 load=1 [^1] cleo: exists=1 load=0 ← bug (load from (absolute) cleo: fails)
5.4.0 patched abs: exists=1 load=1 cleo: exists=1 load=1
4.4.4 stock abs: exists=1 load=0 ← bug (load from absolute path fails)
4.4.4 patched abs: exists=1 load=1

^1 note that absolute paths used in scripts work in CLEO 5 because of CLEO's own modloader compatibility fix. But this fix is not applied to virtual paths like cleo:\script.cs and also not available in CLEO 4.4.4.

Why it happens

  1. CLEO hands over to ModLoader a full path: C:\GTA San Andreas\cleo\script.cs.
  2. ModLoader only translates a path when it does not exist yet (AR_PATH_INE).
  3. For an absolute path it must first strip the game folder to get the relative form it can probe mod folders with. That helper was called with the wide string reinterpreted as narrow: GetCurrentDir((const char*) arg, …).
  4. The game-path comparison therefore fails on the second byte, the helper returns nullptr, bCheckModules stays false, and no mod folder is ever probed.
  5. ModLoader passes the original absolute path on. The file exists only inside the modloader directory, not in the root CLEO folder, so the open fails.

For a relative path the other branch runs (GetCurrentDir(buffer, MAX_PATH) = the process CWD,
narrow) and the probe works. cleo\script.cs loads fine.

The Fix

Convert the game path to the same type as the argument for comparison to work.

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.

1 participant