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>
This commit is contained in:
commit
d1442e3747
1382 changed files with 170725 additions and 0 deletions
213
Managed/Decal.DecalDat/DatFile.cs
Normal file
213
Managed/Decal.DecalDat/DatFile.cs
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.IO.MemoryMappedFiles;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Decal.DecalDat
|
||||
{
|
||||
/// <summary>
|
||||
/// Low-level reader for Asheron's Call DAT archive files.
|
||||
/// DAT files use a sector-based linked-list format with a hierarchical directory.
|
||||
/// </summary>
|
||||
internal sealed class DatFile : IDisposable
|
||||
{
|
||||
private const int FileCount = 62;
|
||||
private const int FileCountOffset = 0x03E;
|
||||
private const int RootDirPtrOffset = 0x148;
|
||||
|
||||
// Directory entry layout (packed):
|
||||
// DWORD subdirs[62] = 248 bytes
|
||||
// DWORD fileCount = 4 bytes
|
||||
// FileEntry files[62] = 744 bytes (62 * 12)
|
||||
// Total = 996 bytes
|
||||
private const int SubdirsSize = FileCount * 4;
|
||||
private const int FileEntrySize = 12; // 3 DWORDs: ID, Offset, Size
|
||||
|
||||
private MemoryMappedFile _mmf;
|
||||
private MemoryMappedViewAccessor _accessor;
|
||||
private readonly int _sectorSize;
|
||||
private readonly long _fileLength;
|
||||
|
||||
public DatFile(string filename, int sectorSize = 256)
|
||||
{
|
||||
_sectorSize = sectorSize;
|
||||
|
||||
var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
_fileLength = fs.Length;
|
||||
_mmf = MemoryMappedFile.CreateFromFile(fs, null, 0, MemoryMappedFileAccess.Read, HandleInheritability.None, false);
|
||||
_accessor = _mmf.CreateViewAccessor(0, _fileLength, MemoryMappedFileAccess.Read);
|
||||
}
|
||||
|
||||
public DatFileEntry GetFile(uint fileId)
|
||||
{
|
||||
uint rootDirOffset = _accessor.ReadUInt32(RootDirPtrOffset);
|
||||
return FindFile(rootDirOffset, fileId);
|
||||
}
|
||||
|
||||
private DatFileEntry FindFile(uint dirOffset, uint fileId)
|
||||
{
|
||||
// Read file count from directory
|
||||
uint fileCount = _accessor.ReadUInt32(dirOffset + SubdirsSize);
|
||||
|
||||
// Binary search through sorted file entries
|
||||
int lo = 0, hi = (int)fileCount - 1;
|
||||
|
||||
while (lo <= hi)
|
||||
{
|
||||
int mid = (lo + hi) / 2;
|
||||
long entryOffset = dirOffset + SubdirsSize + 4 + (mid * FileEntrySize);
|
||||
uint entryId = _accessor.ReadUInt32(entryOffset);
|
||||
|
||||
if (entryId == fileId)
|
||||
{
|
||||
uint offset = _accessor.ReadUInt32(entryOffset + 4);
|
||||
uint size = _accessor.ReadUInt32(entryOffset + 8);
|
||||
return new DatFileEntry(this, offset, size);
|
||||
}
|
||||
else if (fileId < entryId)
|
||||
{
|
||||
// Recurse into left subdirectory
|
||||
uint subdir = _accessor.ReadUInt32(dirOffset + (uint)(mid * 4));
|
||||
if (subdir != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
return FindFile(subdir, fileId);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// Not in this subtree
|
||||
}
|
||||
}
|
||||
hi = mid - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
lo = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Check rightmost subdirectory
|
||||
uint rightSubdir = _accessor.ReadUInt32(dirOffset + (uint)(lo * 4));
|
||||
if (rightSubdir != 0)
|
||||
{
|
||||
return FindFile(rightSubdir, fileId);
|
||||
}
|
||||
|
||||
throw new FileNotFoundException($"File 0x{fileId:X8} not found in DAT");
|
||||
}
|
||||
|
||||
internal int SectorSize => _sectorSize;
|
||||
internal int DataPerSector => _sectorSize - 4; // First 4 bytes = next sector pointer
|
||||
|
||||
internal void ReadBytes(long offset, byte[] buffer, int bufferOffset, int count)
|
||||
{
|
||||
_accessor.ReadArray(offset, buffer, bufferOffset, count);
|
||||
}
|
||||
|
||||
internal uint ReadUInt32(long offset)
|
||||
{
|
||||
return _accessor.ReadUInt32(offset);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_accessor?.Dispose();
|
||||
_accessor = null;
|
||||
_mmf?.Dispose();
|
||||
_mmf = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single file within a DAT archive.
|
||||
/// Reads through a linked list of sectors.
|
||||
/// </summary>
|
||||
internal sealed class DatFileEntry
|
||||
{
|
||||
private readonly DatFile _source;
|
||||
private readonly uint _firstSectorOffset;
|
||||
private readonly uint _size;
|
||||
|
||||
private uint _currentSectorOffset;
|
||||
private int _posInSector; // position within current sector's data area
|
||||
private int _totalRead; // total bytes read so far
|
||||
|
||||
public DatFileEntry(DatFile source, uint sectorOffset, uint size)
|
||||
{
|
||||
_source = source;
|
||||
_firstSectorOffset = sectorOffset;
|
||||
_size = size;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public int Size => (int)_size;
|
||||
public int Tell => _totalRead;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_currentSectorOffset = _firstSectorOffset;
|
||||
_posInSector = 0;
|
||||
_totalRead = 0;
|
||||
}
|
||||
|
||||
public void Skip(int bytes)
|
||||
{
|
||||
int remaining = bytes;
|
||||
while (remaining > 0)
|
||||
{
|
||||
int dataPerSector = _source.DataPerSector;
|
||||
int availableInSector = dataPerSector - _posInSector;
|
||||
|
||||
if (remaining < availableInSector)
|
||||
{
|
||||
_posInSector += remaining;
|
||||
_totalRead += remaining;
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip rest of this sector and move to next
|
||||
_totalRead += availableInSector;
|
||||
remaining -= availableInSector;
|
||||
MoveToNextSector();
|
||||
}
|
||||
}
|
||||
|
||||
public int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int remaining = Math.Min(count, (int)_size - _totalRead);
|
||||
int totalCopied = 0;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
int dataPerSector = _source.DataPerSector;
|
||||
int availableInSector = dataPerSector - _posInSector;
|
||||
int toCopy = Math.Min(remaining, availableInSector);
|
||||
|
||||
// Data starts at sector offset + 4 (skip next-sector pointer)
|
||||
long readOffset = _currentSectorOffset + 4 + _posInSector;
|
||||
_source.ReadBytes(readOffset, buffer, offset + totalCopied, toCopy);
|
||||
|
||||
_posInSector += toCopy;
|
||||
_totalRead += toCopy;
|
||||
totalCopied += toCopy;
|
||||
remaining -= toCopy;
|
||||
|
||||
if (_posInSector >= dataPerSector && remaining > 0)
|
||||
{
|
||||
MoveToNextSector();
|
||||
}
|
||||
}
|
||||
|
||||
return totalCopied;
|
||||
}
|
||||
|
||||
private void MoveToNextSector()
|
||||
{
|
||||
// Next sector pointer is stored in first 4 bytes of current sector
|
||||
uint nextSector = _source.ReadUInt32(_currentSectorOffset);
|
||||
_currentSectorOffset = nextSector;
|
||||
_posInSector = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue