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 extensionThe 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.
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.