openDecal/Managed/Decal.DecalDat/DatLibraryImpl.cs
erik d1442e3747 Initial commit: Complete open-source Decal rebuild
All 5 phases of the open-source Decal rebuild:

Phase 1: 14 decompiled .NET projects (Interop.*, Adapter, FileService, DecalUtil)
Phase 2: 10 native DLLs rewritten as C# COM servers with matching GUIDs
  - DecalDat, DHS, SpellFilter, DecalInput, DecalNet, DecalFilters
  - Decal.Core, DecalControls, DecalRender, D3DService
Phase 3: C++ shims for Inject.DLL (D3D9 hooking) and LauncherHook.DLL
Phase 4: DenAgent WinForms tray application
Phase 5: WiX installer and build script

25 C# projects building with 0 errors.
Native C++ projects require VS 2022 + Windows SDK (x86).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 18:27:56 +01:00

159 lines
4.7 KiB
C#

using System;
using System.Globalization;
using System.Runtime.InteropServices;
using Decal.Interop.Core;
using Decal.Interop.Dat;
namespace Decal.DecalDat
{
internal enum LibraryType
{
Cell,
Portal
}
[ComVisible(true)]
[Guid("6FA05FDA-B4B5-4386-AB45-92D7E6A5D698")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("DecalDat.DatLibrary")]
public class DatLibraryImpl : IDatLibrary, IDecalDirectory
{
private DatFile _datFile;
private LibraryType _libraryType;
private DatServiceImpl _service;
internal void Load(DatServiceImpl service, string filename, LibraryType libraryType, int sectorSize)
{
_service = service;
_libraryType = libraryType;
try
{
_datFile = new DatFile(filename, sectorSize);
}
catch
{
// Silent failure matching original C++ behavior
_datFile = null;
}
}
/// <summary>
/// IDatLibrary.Stream - parameterized property returning raw stream for a file ID.
/// The COM interface declares this as a parameterized property (DispId 1) taking a DWORD File parameter.
/// In the C++ implementation, get_Stream(DWORD dwFile, LPUNKNOWN *pVal) creates a DatStream directly.
/// </summary>
public object Stream
{
get
{
// This parameterized property is called via IDispatch with the file ID argument.
// When called without parameters from managed code, return null.
return null;
}
}
internal object GetStream(uint fileId)
{
if (_datFile == null) return null;
try
{
var fileEntry = _datFile.GetFile(fileId);
var stream = new DatStreamImpl();
stream.Load(fileEntry);
return stream;
}
catch
{
return null;
}
}
public object Open(string Protocol, uint File)
{
if (_datFile == null) return null;
// Look up filter by protocol name
var filter = _service.GetFilter(Protocol);
if (filter != null)
{
// Check cache first
var cached = _service.FindInCache(filter, _libraryType, File);
if (cached != null) return cached;
// Create raw stream
DatFileEntry fileEntry;
try
{
fileEntry = _datFile.GetFile(File);
}
catch
{
return null;
}
var stream = new DatStreamImpl();
stream.Load(fileEntry);
// Create and initialize filter
var filterObj = _service.CreateFilter(filter);
if (filterObj == null) return null;
var fileFilter = filterObj as IFileFilter;
if (fileFilter != null)
{
fileFilter.Initialize((DatStream)(object)stream);
}
// Cache if applicable
if (filter.Cache)
{
_service.AddToCache(filter, _libraryType, File, filterObj);
}
return filterObj;
}
// No filter - return raw stream
return GetStream(File);
}
/// <summary>
/// IDecalDirectory.Lookup - parses "PROTOCOL:HEXID" or just "HEXID" format.
/// </summary>
public object Lookup(string strName)
{
if (string.IsNullOrEmpty(strName)) return null;
int colonIdx = strName.IndexOf(':');
if (colonIdx >= 0)
{
// Format: "protocol:0xHEXID"
string protocol = strName.Substring(0, colonIdx);
string hexPart = strName.Substring(colonIdx + 1).Trim();
uint fileId = ParseHex(hexPart);
return Open(protocol, fileId);
}
else
{
// Format: "0xHEXID" - raw stream
uint fileId = ParseHex(strName.Trim());
return GetStream(fileId);
}
}
private static uint ParseHex(string hex)
{
if (hex.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
hex = hex.Substring(2);
return uint.Parse(hex, NumberStyles.HexNumber);
}
internal void Dispose()
{
_datFile?.Dispose();
_datFile = null;
}
}
}