Fix Malformed Preset Filenames (#6712)

Sanitize preset names for filesystem safety, log errors without crashing

Co-authored-by: Unreference <87878910+unreference@users.noreply.github.com>
This commit is contained in:
Philip Dubé
2026-06-09 19:51:15 +00:00
committed by GitHub
parent 8649085862
commit 5875ed880a
+48 -11
View File
@@ -14,11 +14,37 @@
#include "soh/Enhancements/randomizer/randomizer_check_tracker.h"
#include "soh/Enhancements/randomizer/randomizer_entrance_tracker.h"
#include "soh/Enhancements/randomizer/randomizer_item_tracker.h"
#include "soh/Enhancements/randomizer/SeedContext.h"
#include "soh/Enhancements/randomizer/settings.h"
namespace fs = std::filesystem;
/**
* Replace characters to prevent crashes from invalid paths (e.g, "test :)" creating an NTFS Alternate Data Stream
* instead of a regular file).
*/
static std::string SanitizeFilename(const std::string& name) {
std::string result;
result.reserve(name.size());
for (const char c : name) {
if (c == '<' || c == '>' || c == ':' || c == '"' || c == '/' || c == '\\' || c == '|' || c == '?' || c == '*' ||
c < 32) {
result += '_';
} else {
result += c;
}
}
while (!result.empty() && (result.back() == '.' || result.back() == ' ')) {
result.pop_back();
}
if (result.empty()) {
result = "Unnamed";
}
return result;
}
namespace SohGui {
extern std::shared_ptr<SohMenu> mSohMenu;
} // namespace SohGui
@@ -75,7 +101,7 @@ static BlockInfo blockInfo[PRESET_SECTION_MAX] = {
};
std::string FormatPresetPath(std::string name) {
return fmt::format("{}/{}.json", presetFolder, name);
return fmt::format("{}/{}.json", presetFolder, SanitizeFilename(name));
}
void applyPreset(std::string presetName, std::vector<PresetSection> includeSections) {
@@ -220,16 +246,19 @@ void LoadPresets() {
}
if (fs::exists(presetFolder)) {
for (auto const& preset : fs::directory_iterator(presetFolder)) {
std::ifstream ifs(preset.path());
try {
std::ifstream ifs(preset.path());
if (auto json = nlohmann::json::parse(ifs); !json.contains("presetName")) {
spdlog::error(fmt::format("Attempted to load file {} as a preset, but was not a preset file.",
preset.path().filename().string()));
} else {
ParsePreset(json, preset.path().filename().stem().string());
}
auto json = nlohmann::json::parse(ifs);
if (!json.contains("presetName")) {
spdlog::error(fmt::format("Attempted to load file {} as a preset, but was not a preset file.",
preset.path().filename().string()));
} else {
ParsePreset(json, preset.path().filename().stem().string());
ifs.close();
} catch (const std::exception& e) {
spdlog::error("Failed to load preset {}: {}", preset.path().filename().string(), e.what());
}
ifs.close();
}
}
auto initData = std::make_shared<Ship::ResourceInitData>();
@@ -255,8 +284,16 @@ void SavePreset(std::string& presetName) {
}
presets[presetName].presetValues["presetName"] = presetName;
presets[presetName].presetValues["fileType"] = FILE_TYPE_PRESET;
std::string safeFilename = SanitizeFilename(presetName);
std::ofstream file(
fmt::format("{}/{}.json", Ship::Context::GetRawInstance()->LocateFileAcrossAppDirs("presets"), presetName));
fmt::format("{}/{}.json", Ship::Context::GetRawInstance()->LocateFileAcrossAppDirs("presets"), safeFilename));
if (!file.is_open()) {
spdlog::error("Failed to save preset '{}': Could not create file", presetName);
return;
}
file << presets[presetName].presetValues.dump(4);
file.close();
LoadPresets();