using ModelRenderer.Scripts.Common; using System; using System.Collections.Generic; using System.IO; using System.Text; using UnityEngine; // thêm để dùng Resources & TextAsset namespace BrewMonster { public class CECStringTab { private readonly Dictionary m_AStrTab = new Dictionary(); private readonly Dictionary m_WStrTab = new Dictionary(); private bool m_bInit = false; private bool m_bUnicode = false; public CECStringTab() { } ~CECStringTab() { Release(); } /// /// Initialize the table directly from a Unity TextAsset (e.g. loaded via Addressables). /// public bool InitFromTextAsset(TextAsset textAsset, bool bUnicode) { Release(); m_bUnicode = bUnicode; try { if (textAsset == null) { Debug.LogError("[CECStringTab] InitFromTextAsset failed: textAsset is null"); return false; } bool ok; if (bUnicode) { // Unity TextAsset.text is already UTF-8 decoded. using var sr = new StringReader(textAsset.text); ok = ParseIntoDict(sr, isWide: true); } else { // ANSI tables are in CP936 in original PW; keep using CP936 decoder. string content = ByteToStringUtils.ByteArrayToCP936String(textAsset.bytes); using var sr = new StringReader(content); ok = ParseIntoDict(sr, isWide: false); } m_bInit = ok; return ok; } catch (Exception e) { Debug.LogError($"[CECStringTab] InitFromTextAsset failed: {e}"); Release(); return false; } } public bool Init(string szFile, bool bUnicode) { Release(); m_bUnicode = bUnicode; try { bool ok = bUnicode ? LoadWideStrings(szFile) : LoadANSIStrings(szFile); m_bInit = ok; return ok; } catch (Exception e) { Debug.LogError($"[CECStringTab] Init failed: {e}"); Release(); return false; } } public void Release() { m_AStrTab.Clear(); m_WStrTab.Clear(); m_bInit = false; m_bUnicode = false; } public string GetANSIString(int n) => m_AStrTab.TryGetValue(n, out var s) ? s : null; public string GetWideString(int n) => m_WStrTab.TryGetValue(n, out var s) ? s : null; public string GetWideStringObject(int n) => GetWideString(n); public bool IsInitialized() => m_bInit; // ==== Đọc từ Resources thay vì đường dẫn ==== protected bool LoadANSIStrings(string resourceName) { try { // If a real file path is provided (e.g. StreamingAssets), read directly from disk. // 如果提供的是实际文件路径(例如 StreamingAssets),则直接从磁盘读取。 if (File.Exists(resourceName)) { // ANSI tables are in CP936 in original PW; keep using CP936 decoder. // 原版完美世界的ANSI表是CP936编码,这里保持一致。 byte[] bytes = File.ReadAllBytes(resourceName); string content = ByteToStringUtils.ByteArrayToCP936String(bytes); using var srFile = new StringReader(content); return ParseIntoDict(srFile, isWide: false); } // Fallback to Resources (old behaviour). // 回退到 Resources 加载(旧行为)。 TextAsset textAsset = Resources.Load(resourceName); if (textAsset == null) { Debug.LogError($"[CECStringTab] Resource not found: {resourceName}"); return false; } string resContent = ByteToStringUtils.ByteArrayToCP936String(textAsset.bytes); using var srRes = new StringReader(resContent); return ParseIntoDict(srRes, isWide: false); } catch (Exception e) { Debug.LogError($"[CECStringTab] LoadANSIStrings failed for '{resourceName}': {e}"); return false; } } protected bool LoadWideStrings(string resourceName) { try { // Support absolute / relative filesystem paths (e.g. StreamingAssets/configs/*.txt) // 支持文件系统路径(例如 StreamingAssets/configs/*.txt) if (File.Exists(resourceName)) { // String tables we ship in StreamingAssets are saved as UTF-8. // 我们放在 StreamingAssets 里的字符串表保存为 UTF-8。 string content = File.ReadAllText(resourceName, Encoding.UTF8); using var srFile = new StringReader(content); return ParseIntoDict(srFile, isWide: true); } // Fallback to Resources-based loading (old behaviour) // 回退到基于 Resources 的加载(旧行为) TextAsset textAsset = Resources.Load(resourceName); if (textAsset == null) { Debug.LogError($"[CECStringTab] Resource not found: {resourceName}"); return false; } // Unity TextAsset.text is already UTF-8 decoded. string resContent = textAsset.text; using var srRes = new StringReader(resContent); return ParseIntoDict(srRes, isWide: true); } catch (Exception e) { Debug.LogError($"[CECStringTab] LoadWideStrings failed for '{resourceName}': {e}"); return false; } } private static Encoding DetectEncoding(byte[] bom) { if (bom.Length >= 3 && bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF) return Encoding.UTF8; if (bom.Length >= 2 && bom[0] == 0xFF && bom[1] == 0xFE) return Encoding.Unicode; if (bom.Length >= 2 && bom[0] == 0xFE && bom[1] == 0xFF) return Encoding.BigEndianUnicode; return null; } private bool ParseIntoDict(StringReader sr, bool isWide) { bool bIndexMode = false; bool bBegan = false; int autoIndex = 0; var allLines = new List(); string line; while ((line = sr.ReadLine()) != null) { allLines.Add(line); } for (int i = 0; i < allLines.Count; i++) { var ln = allLines[i].Trim(); if (ln.Length == 0) continue; if (ln.Equals("#_index", StringComparison.OrdinalIgnoreCase)) { bIndexMode = true; } else if (ln.Equals("#_begin", StringComparison.OrdinalIgnoreCase)) { bBegan = true; for (int j = i + 1; j < allLines.Count; j++) { var payload = allLines[j].Trim(); if (payload.Length == 0) continue; if (payload.StartsWith("#")) continue; if (payload.StartsWith("//")) continue; if (bIndexMode) { if (!TrySplitIndexAndText(payload, out int idx, out string text)) continue; // Check if the text is a multiline quoted string string fullText = ReadMultilineQuotedString(text, allLines, ref j); PutString(idx, fullText, isWide); } else { PutString(autoIndex++, payload, isWide); } } break; } } return bBegan; } private static bool TrySplitIndexAndText(string line, out int index, out string text) { index = 0; text = null; int eq = line.IndexOf('='); if (eq >= 0) { var left = line.Substring(0, eq).Trim(); var right = line.Substring(eq + 1); if (int.TryParse(left, out index)) { text = right; return true; } return false; } int sp = FirstWhiteSpaceIndex(line); if (sp <= 0) return false; var left2 = line.Substring(0, sp).Trim(); var right2 = line.Substring(sp).TrimStart(); if (int.TryParse(left2, out index)) { if (right2.Length > 0 && (right2[0] == '"' || right2[0] == '\'')) { text = right2; return true; } } return false; } private static int FirstWhiteSpaceIndex(string s) { for (int i = 0; i < s.Length; i++) if (char.IsWhiteSpace(s[i])) return i; return -1; } /// /// Reads a multiline quoted string similar to C++ AWScriptFile.GetNextToken(false) /// If the string starts with " but doesn't end with ", continues reading subsequent lines /// private string ReadMultilineQuotedString(string firstLine, List allLines, ref int currentIndex) { // If it doesn't start with a quote, return as-is if (string.IsNullOrEmpty(firstLine) || firstLine[0] != '"') return firstLine; // Check if the string is already complete (starts and ends with quotes on same line) if (firstLine.Length >= 2 && firstLine[firstLine.Length - 1] == '"') { // Check if it's not an escaped quote by looking at preceding character // Simple check: if there's more than one char and last is ", assume complete return firstLine; } // The string is incomplete - need to read more lines StringBuilder sb = new StringBuilder(); sb.Append(firstLine); // Continue reading lines until we find the closing quote for (int k = currentIndex + 1; k < allLines.Count; k++) { string nextLine = allLines[k]; // Append newline to preserve original formatting (matching C++ behavior) sb.Append("\n"); sb.Append(nextLine); // Check if this line contains the closing quote // Look for " at the end of the trimmed line string trimmedNext = nextLine.TrimEnd(); if (trimmedNext.Length > 0 && trimmedNext[trimmedNext.Length - 1] == '"') { // Found the closing quote, update the index and return currentIndex = k; return sb.ToString(); } } // If we reach here, the closing quote wasn't found - return what we have return sb.ToString(); } private void PutString(int id, string value, bool isWide) { if (string.IsNullOrEmpty(value)) return; // Many PW string tables wrap the payload in double quotes, e.g.: // 12345 "^ffcb4aSome text\rMore text" // Strip a single leading/trailing quote pair to avoid showing raw quotes in UI. // 许多字符串表会用双引号包裹内容,这里去掉首尾各一个引号以避免在UI中显示多余的引号。 if (value.Length >= 2 && value[0] == '"' && value[value.Length - 1] == '"') { value = value.Substring(1, value.Length - 2); } if (isWide) m_WStrTab[id] = value; else m_AStrTab[id] = value; } } }