using DatReaderWriter; using DatReaderWriter.DBObjs; namespace AcDream.App.UI.Layout; /// /// Resolves retail StringInfo values through local.dat string tables. /// The caller owns synchronization around reads. /// /// /// Retail reference: StringInfo::GetString and /// compute_str_hash @ 0x00413110. A StringInfo's token selects one /// localized string variant; ordinary UI labels use token zero. /// public sealed class DatStringResolver { private readonly DatCollection _dats; private readonly Dictionary _tables = new(); public DatStringResolver(DatCollection dats) => _dats = dats ?? throw new ArgumentNullException(nameof(dats)); public string? Resolve(UiStringInfoValue info) => Resolve(info.TableId, info.StringId, info.Token); public string? Resolve(uint tableId, uint stringId, int token = 0) { if (tableId == 0u || stringId == 0u) return null; if (!_tables.TryGetValue(tableId, out StringTable? table)) { table = _dats.Get(tableId); _tables[tableId] = table; } if (table is null || !table.Strings.TryGetValue(stringId, out var entry) || entry.Strings.Count == 0) return null; int index = token >= 0 && token < entry.Strings.Count ? token : 0; return entry.Strings[index].Value; } /// Returns every literal token for one retail StringInfo entry. public string[]? ResolveAll(uint tableId, uint stringId) { if (tableId == 0u || stringId == 0u) return null; if (!_tables.TryGetValue(tableId, out StringTable? table)) { table = _dats.Get(tableId); _tables[tableId] = table; } return table is not null && table.Strings.TryGetValue(stringId, out var entry) && entry.Strings.Count != 0 ? entry.Strings.Select(value => value.Value).ToArray() : null; } /// /// Exact retail ELF-style string hash used for StringInfo keys. /// Ported line-for-line from compute_str_hash @ 0x00413110. /// public static uint ComputeHash(string value) { ArgumentNullException.ThrowIfNull(value); uint result = 0u; foreach (char c in value) { result = unchecked((result << 4) + (byte)c); uint high = result & 0xF0000000u; if (high != 0u) result = ((high >> 24) ^ result) & 0x0FFFFFFFu; } return result == uint.MaxValue ? uint.MaxValue - 1u : result; } }