From 8ee95f6ef4c5e80c01508f19640611820346061b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 22:22:54 +0000 Subject: [PATCH 1/2] Initial plan From 0a2fc21f85fe142ad2855776d5715093b2d70b80 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 22:25:57 +0000 Subject: [PATCH 2/2] fix: reject malformed customization item entries and document recovery Agent-Logs-Url: https://github.com/umin135/Polaris_TKDataModEditor/sessions/1eda21fa-487d-4ed9-9035-61a2c838abab Co-authored-by: dawc17 <100421659+dawc17@users.noreply.github.com> --- README.md | 10 +++++++++ src/fbsdata/io/TkmodIO.cpp | 45 ++++++++++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c63f543..be87bab 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@
# Polaris_TKDataModEditor wip.... + +## Recovering from malformed customization-item mods + +If a malformed `customize_item_*.bin` mod item was equipped in-game, the game may keep referencing that item from save data even after uninstalling the mod. + +Recommended recovery flow: +1. Re-enable the mod that contains the equipped item. +2. Enter customization and switch every equipped modded part back to a valid vanilla item. +3. Save, close the game, then remove the mod again. +4. If the error still persists, restore a backup save from before the malformed item was equipped (or delete the affected save/profile data and start from a clean save). diff --git a/src/fbsdata/io/TkmodIO.cpp b/src/fbsdata/io/TkmodIO.cpp index aaf00b2..4da17d2 100644 --- a/src/fbsdata/io/TkmodIO.cpp +++ b/src/fbsdata/io/TkmodIO.cpp @@ -385,6 +385,30 @@ static bool ParseFloat(const char* p, const char* end, float& out) return ep != p; } +static bool IsNonEmptyText(const char* s) +{ + return s && s[0] != '\0'; +} + +static bool IsValidCustomizeItemCommonEntry(const CustomizeItemCommonEntry& e) +{ + return e.item_id != 0 + && IsNonEmptyText(e.item_code) + && IsNonEmptyText(e.text_key) + && IsNonEmptyText(e.package_id) + && IsNonEmptyText(e.package_sub_id); +} + +static bool IsValidCustomizeItemUniqueEntry(const CustomizeItemUniqueEntry& e) +{ + return e.char_item_id != 0 && IsNonEmptyText(e.asset_name); +} + +static bool IsValidCustomizeItemUniqueBodyEntry(const CustomizeItemUniqueBodyEntry& e) +{ + return e.char_item_id != 0 && IsNonEmptyText(e.asset_name); +} + static bool ParseCustomizeItemCommonEntry(const char* start, const char* end, CustomizeItemCommonEntry& e) { @@ -433,7 +457,8 @@ static bool ParseCustomizeItemCommonJson(const std::string& json, ContentsBinDat CustomizeItemCommonEntry entry; ParseCustomizeItemCommonEntry(arr, objEnd, entry); - bin.commonEntries.push_back(entry); + if (IsValidCustomizeItemCommonEntry(entry)) + bin.commonEntries.push_back(entry); arr = objEnd; } // Skip commas and whitespace between entries @@ -637,8 +662,8 @@ static void ParseExclusiveArray(const char* json, const char* arrayKey, const char* objEnd = SkipObject(arr); if (!objEnd) break; TEntry entry{}; - parseFn(arr, objEnd, entry); - out.push_back(entry); + if (parseFn(arr, objEnd, entry)) + out.push_back(entry); arr = objEnd; } while (*arr == ',' || *arr == ' ' || *arr == '\n' || *arr == '\r' || *arr == '\t') @@ -1390,15 +1415,15 @@ static bool ParseCustomizeItemUniqueListJson(const std::string& json, ContentsBi { auto* fp = FindField(s, e, "id_19"); ParseUInt32(fp, e, entry.unk_19); } { auto* fp = FindField(s, e, "id_20"); ParseUInt32(fp, e, entry.unk_20); } { auto* fp = FindField(s, e, "id_21"); ParseUInt32(fp, e, entry.unk_21); } - return true; + return IsValidCustomizeItemUniqueEntry(entry); }); ParseExclusiveArray(p, "\"id_1\"", bin.customizeItemUniqueBodyEntries, [](const char* s, const char* e, CustomizeItemUniqueBodyEntry& entry) -> bool { { auto* fp = FindField(s, e, "id_0"); ParseString(fp, e, entry.asset_name, sizeof(entry.asset_name)); } { auto* fp = FindField(s, e, "id_1"); ParseUInt32(fp, e, entry.char_item_id); } - return true; + return IsValidCustomizeItemUniqueBodyEntry(entry); }); - return true; + return !bin.customizeItemUniqueEntries.empty() || !bin.customizeItemUniqueBodyEntries.empty(); } // ----------------------------------------------------------------------------- @@ -2545,9 +2570,11 @@ namespace TkmodIO ContentsBinData bin; bin.type = BinType::CustomizeItemUniqueList; bin.name = binName; - ParseCustomizeItemUniqueListJson(jsonStr, bin); - loaded.selectedIndex = static_cast(loaded.contents.size()); - loaded.contents.push_back(std::move(bin)); + if (ParseCustomizeItemUniqueListJson(jsonStr, bin)) + { + loaded.selectedIndex = static_cast(loaded.contents.size()); + loaded.contents.push_back(std::move(bin)); + } } else if (binName == "character_select_list.bin") {