Skip to main content
Unofficial Guild Wars Reforged App — a fan project, not affiliated with ArenaNet or NCSoft

UR-05: Path::RemoveExtension drops the last character

The remove-extension helper passes a filename length that is one too short. Every name that has an extension loses its last character.

Path::RemoveExtension removes the extension and also the last character of the file name. The defect only occurs when the path has an extension.

Build used for the measurements below: 38797, 3229678d3fd7d2f0e309530086a614d97f02e7eeb3ca12650ababfd2eb360817. We first found the defect on build b0319704f3072d6948a66026a35af5eb0af12b48d70986783c293e7c77e98483; it's present on both, and function 457 is the helper on both.

Function 457, Path::RemoveExtension(dst, src, chars), in Base/Rtl/Path.cpp.

Fix: low effort, medium blast radius — the change is one length value, but the template scan and MdlTex.cpp both call this helper, so anything that depends on the current short result changes behaviour. We found no such code, but we haven't read every caller.

The function splits the path with _wsplitpath (function 411), then rebuilds it with _wmakepath (function 412) and a null extension. The filename length it passes to the split is one too short.

What the client does now

We called the shipped function directly with six inputs:

Path::RemoveExtension("\Healer.txt")        = "\Heale"         expected "\Healer"
Path::RemoveExtension("\Warrior.txt")       = "\Warrio"        expected "\Warrior"
Path::RemoveExtension("\Builds\Healer.txt") = "\Builds\Heale"  expected "\Builds\Healer"
Path::RemoveExtension("Healer.txt")         = "Heale"          expected "Healer"
Path::RemoveExtension("\My.Build.txt")      = "\My.Buil"       expected "\My.Build"
Path::RemoveExtension("\Healer")            = "\Healer"        correct, no extension

The last line is correct: with no extension there's nothing to remove, so the length error never fires. The fifth line shows the defect also applies to names containing a dot — the function removes the last extension plus one more character.

Why this matters

Function 9747 calls this helper for every template it enumerates. Once UR-02 and UR-03 are fixed and the template list works, every listed name will be one character short — the load path builds a file path from that name and can't find the file.

The same helper serves MdlTex.cpp.

This defect hides behind UR-02
You can't see this defect today, because the template list is always empty. Fix UR-02 and UR-03 and it becomes visible at once — plan all three together.

Expected behaviour

Return the directory and the file name without the extension, keeping the file name complete.

Acceptance criteria

  • Path::RemoveExtension("\Healer.txt") returns "\Healer".
  • Path::RemoveExtension("\Builds\Healer.txt") returns "\Builds\Healer".
  • Path::RemoveExtension("\Healer") returns "\Healer", as it does today.
  • Path::RemoveExtension("\My.Build.txt") returns "\My.Build".
  • The template list shows the exact name that the player typed.
  • The player can load a template directly from the list.

How to reproduce

You can confirm this one on its own; it doesn't depend on the other defects.

Add an export

Append an export entry for function 457 to the module.

Call it

Instantiate the module in Node with stub imports. Call the function with "\Healer.txt".

Read the result

The function returns "\Heale". The expected result is "\Healer".

The path helpers are pure — they need no game state and no initialised UI.

Our workaround today

Our bridge returns entry names without an extension, so the client calls Path::RemoveExtension on a name that has nothing to remove and the bug never fires.

This stays correct after a fix — removing an absent extension does nothing — so nothing on our side has to change on the same day.

GWonMac

Play Guild Wars on your Mac. GWonMac runs ArenaNet's official client natively on Apple Silicon — free, open source, signed and notarized.

© GWonMac. All rights reserved.