Fix DecalDat to work with real AC DAT files

The DAT file reader had several bugs inherited from the old C++ reference
code, which targeted an older format version. Verified and fixed against
real client_portal.dat and client_cell_1.dat files:

- Fix header offset: BTree root is at 0x160, not 0x148 (file size field)
- Fix BTree entry size: 24 bytes (flags+id+offset+size+timestamp), not 12
- Fix sector-chain node reading: BTree nodes span multiple sectors via
  linked-list headers; must assemble node data across sector boundaries
- Fix DatStreamImpl.Read() BSTR handling: use Buffer.BlockCopy to match
  C++ SysAllocStringByteLen instead of Marshal.PtrToStringAnsi
- Fix DatStreamImpl.ReadBinary() pointer lifetime: inline fixed block to
  keep destination buffer pinned during Marshal.Copy
- Document LoadFilters() dependency on parameterized COM properties in
  IDecalCore.Configuration that need IDispatch to call correctly

Add smoke test project (13/13 tests pass against real DAT files).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
erik 2026-02-08 23:12:04 +01:00
parent c0d1135431
commit f0b6fedc9b
5 changed files with 356 additions and 56 deletions

View file

@ -55,22 +55,25 @@ namespace Decal.DecalDat
private void LoadFilters()
{
try
{
var config = _decalCore.Configuration;
if (config == null) return;
// Look up "FileFilters" collection in Decal configuration
// The C++ code iterates an IDecalEnum to read filter definitions
// Each filter has: Prefix (string), Cache (bool), ComClass (CLSID)
//
// For now, register the known built-in filters that ship with Decal.
// When Decal.Core is implemented, this will read from live config.
}
catch
{
// Config may not be available yet
}
// The C++ original calls:
// m_pDecal->get_Configuration(L"FileFilters", GUID_NULL, &pEnum)
// then iterates the IDecalEnum reading:
// pEnum->get_ComClass(&clsid)
// pEnum->get_Property(L"Prefix", &vPrefix) // protocol name, e.g. "portal"
// pEnum->get_Property(L"Cache", &vCache) // 0 or 1
//
// However, IDecalCore.Configuration and IDecalEnum.Property are
// parameterized COM properties that the decompiler flattened to
// simple getters. They need to be called via IDispatch to pass
// the category name / property name arguments.
//
// Filters are registered externally (by plugins/installer in the
// registry under HKLM\SOFTWARE\Decal\FileFilters\{CLSID}) and
// will be loaded once Decal.Core exposes the Configuration enum.
//
// DecalDat still works without filters — callers get raw IDatStream
// objects. Filters are an optional layer that parses the stream into
// typed objects (e.g., spell tables, character data).
}
public void BeforePlugins()