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;
}
}