Skip to content

Broken compatiblity with legacy LVL files in ANSI format #78

@Flower35

Description

@Flower35

Hello! I would like to report an issue regarding the loading of legacy episodes made in the "SMBX 1.3" engine.

In short, it is expected that old file formats (".wld", ".lvl") retain the ANSI encoding, while the newer formats (".lvlx", ".lua") use the UTF-8 codepage. The encoding of ".lvl" and ".lvlx" should not be mixed (LVL misintepreted as UTF-8).


The handling of WLD files is done via the original Public Sub OpenWorld(FilePath As String) subroutine, thus the file contents are read in ANSI encoding (specific to current Windows CodePage) and translated to UTF16-LE encoding. This potentially affects any referenced LVL and LVLX filenames with diacritics and other Unicode symbols.

PATCH(0x8DF5B0).JMP(runtimeHookLoadWorld).NOP_PAD_TO_SIZE<6>().Apply();

This case is partially checked with the GetNonANSICharsFromWStr() function (the SMBX installation path and the WLD file path, but NONE of the world map contents).

std::wstring nonAnsiCharsEpisode = GetNonANSICharsFromWStr(wldPath);
if (!nonAnsiCharsEpisode.empty())
{
std::wstring path = L"The episode path has characters which are not compatible with the system default Windows ANSI code page. This is not currently supported. Please rename or move your episode folder.\n\nUnsupported characters: " + nonAnsiCharsEpisode + L"\n\nPath:\n" + wldPath;
LunaMsgBox::ShowW(0, path.c_str(), L"SMBX does not support episode path", MB_ICONERROR);
_exit(1);
}


A problem appears with the custom Public Sub OpenLevel (FilePath As String) hook, which was written to handle both the old LVL and the new LVLX formats:

PATCH(0x8D8F40).JMP(runtimeHookLoadLevel).NOP_PAD_TO_SIZE<6>().Apply();

base.ReadFile(static_cast<std::wstring>(*filename), getCurrentLevelData());

if (m_isValid && !FileFormats::OpenLevelFile(utf8_encode(filePath), outData))


The "PGE File Library" correctly detects the file format and uses the correct encoding to read level data:
https://github.com/WohlSoft/PGE-File-Library-STL/blob/f2b83a89ce04ad5a40f1249d1c125b53de6f1750/src/file_rwopen.cpp#L85

However, the discrepancy between the ANSI and UTF-8 encoding was not taken into account in the LunaLua_loadLevelFile() function.

void LunaLua_loadLevelFile(LevelData &outData, std::wstring fullPath, bool isValid)

Specifically, the encoding of the following strings could be affected:
https://github.com/WohlSoft/PGE-File-Library-STL/blob/f2b83a89ce04ad5a40f1249d1c125b53de6f1750/src/smbx64/file_rw_lvl.cpp#L179

// "PGE-File-Library-STL/src/smbx64/file_rw_lvl.cpp"

// Internal level name

SMBX64::ReadStr(&FileData.LevelName, line);

// Custom music filepath

SMBX64::ReadStr(&section.music_file, line);

// Various layer names

SMBX64::ReadStr(&blocks.layer, line);
SMBX64::ReadStr(&blocks.event_destroy, line);
SMBX64::ReadStr(&blocks.event_hit, line);
SMBX64::ReadStr(&blocks.event_emptylayer, line);

SMBX64::ReadStr(&bgodata.layer, line);

// NPC message boxes (even though only the ASCII font is supported)

SMBX64::ReadStr(&npcdata.msg, line);

SMBX64::ReadStr(&npcdata.layer, line);
SMBX64::ReadStr(&npcdata.event_activate, line);
SMBX64::ReadStr(&npcdata.event_die, line);
SMBX64::ReadStr(&npcdata.event_talk, line);
SMBX64::ReadStr(&npcdata.event_emptylayer, line);
SMBX64::ReadStr(&npcdata.attach_layer, line);

// The level filename of a warp target (pipe or door)

SMBX64::ReadStr(&doors.lname, line);
SMBX64::ReadStr(&doors.layer, line);

SMBX64::ReadStr(&waters.layer, line);

SMBX64::ReadStr(&layers.name, line);

// Various event names

SMBX64::ReadStr(&events.name, line);
SMBX64::ReadStr(&events.msg, line);

SMBX64::ReadStr(&events_layers.hide, line);
SMBX64::ReadStr(&events_layers.show, line)
SMBX64::ReadStr(&events_layers.toggle, line);

SMBX64::ReadStr(&events.trigger, line);
SMBX64::ReadStr(&events.movelayer, line);

When entering a warp to another level (NOT from the world map), this error message appears on the screen:

std::string msg = "Can't load this level, "
"because the file does not "
"exist: ";

I solved this issue by manually converting old files to remove any unicode characters from the world map, custom music filenames, and LVL filenames.

However, the LunaLua_loadLevelFile() function could be updated to properly handle cases where StrA2WStr() should be called instead of Str2WStr(), for example:

const LevelDoor& nextDataLevelDoor = outData.doors[i];

if (outData.meta.RecentFormat == LevelData::SMBX64)
{
    // ANSI (legacy format)
    nextDoor->warpToLevelFileName = StrA2WStr(nextDataLevelDoor.lname);
}
else
{
    // UTF-8
    nextDoor->warpToLevelFileName = nextDataLevelDoor.lname;
}

(similar change for all the layer names, event names, and custom music filepaths)


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions