Restore Vitae's omitted penalty paragraph, replace the invented character summary with gmCharacterInfoUI's ordered report and property meanings, preserve authored translucent body surfaces, and initialize the end-session button in its visible Normal DAT state. Release build and all 5,830 tests pass with five intentional skips. Connected visual gate pending. Co-authored-by: OpenAI Codex <codex@openai.com>
82 lines
2.7 KiB
C#
82 lines
2.7 KiB
C#
using DatReaderWriter;
|
|
using DatReaderWriter.DBObjs;
|
|
|
|
namespace AcDream.App.UI.Layout;
|
|
|
|
/// <summary>
|
|
/// Resolves retail <c>StringInfo</c> values through local.dat string tables.
|
|
/// The caller owns synchronization around <see cref="DatCollection"/> reads.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Retail reference: <c>StringInfo::GetString</c> and
|
|
/// <c>compute_str_hash @ 0x00413110</c>. A StringInfo's token selects one
|
|
/// localized string variant; ordinary UI labels use token zero.
|
|
/// </remarks>
|
|
public sealed class DatStringResolver
|
|
{
|
|
private readonly DatCollection _dats;
|
|
private readonly Dictionary<uint, StringTable?> _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<StringTable>(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;
|
|
}
|
|
|
|
/// <summary>Returns every literal token for one retail StringInfo entry.</summary>
|
|
public string[]? ResolveAll(uint tableId, uint stringId)
|
|
{
|
|
if (tableId == 0u || stringId == 0u)
|
|
return null;
|
|
if (!_tables.TryGetValue(tableId, out StringTable? table))
|
|
{
|
|
table = _dats.Get<StringTable>(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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Exact retail ELF-style string hash used for StringInfo keys.
|
|
/// Ported line-for-line from <c>compute_str_hash @ 0x00413110</c>.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|