Files
test/Assets/Scripts/CECStringTab.cs
T
2026-02-02 18:16:11 +07:00

334 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<int, string> m_AStrTab = new Dictionary<int, string>();
private readonly Dictionary<int, string> m_WStrTab = new Dictionary<int, string>();
private bool m_bInit = false;
private bool m_bUnicode = false;
public CECStringTab() { }
~CECStringTab() { Release(); }
/// <summary>
/// Initialize the table directly from a Unity TextAsset (e.g. loaded via Addressables).
/// </summary>
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<TextAsset>(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<TextAsset>(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>();
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;
}
/// <summary>
/// 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
/// </summary>
private string ReadMultilineQuotedString(string firstLine, List<string> 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;
}
}
}