CLAUDE.md: Update status to Phase 6 in progress, add new pitfalls and key files discovered during verification. PLAN.md: Mark Steps 1, 2, and 2b complete with results (50/50 CLSIDs, 13/13 DAT tests). Add DecalDat functional test as Step 2b. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7.4 KiB
Plan: Open-Source Decal Rebuild
Context
Decal is a plugin framework for Asheron's Call. The game's official servers shut down, but the community runs it via ACEmulator. The modern Decal installer (v2.9.8.3) is closed-source. We have old C++ source code (partial match) and have decompiled all .NET assemblies. Goal: produce a fully open-source Decal that works with the original AC client, buildable from a single Visual Studio 2022 solution.
Architecture Decision
- Rewrite native C++ DLLs as C# COM servers — same GUIDs, same interfaces, different implementation language
- Keep only 2 components in C++:
Inject.DLL(D3D hooking, ~500-800 lines) andLauncherHook.DLL(~200 lines) - Use Vortice.DirectX for D3D9 rendering in C# (DecalRender, D3DService)
- Preserve plugin compatibility by matching all COM CLSIDs/IIDs exactly
Project Structure
reconstructed/
├── Managed/ # 25 C# projects
│ ├── Decal.sln # Solution file (25 projects, 0 errors)
│ ├── Directory.Build.props # Shared: net472, x86, LangVersion 12
│ ├── Decal.Interop.*/ # 11 COM interface assemblies (decompiled)
│ ├── Decal.Adapter/ # Plugin adapter (decompiled)
│ ├── Decal.FileService/ # File service (decompiled)
│ ├── DecalUtil/ # Utility exe (decompiled)
│ ├── Decal.DecalDat/ # DAT file I/O (COM server)
│ ├── Decal.DHS/ # Hotkey system (COM server)
│ ├── Decal.SpellFilter/ # Spell data (COM server)
│ ├── Decal.DecalInput/ # Input handling (COM server)
│ ├── Decal.DecalNet/ # Network packets (COM server)
│ ├── Decal.DecalFilters/ # Game data/world objects (COM server)
│ ├── Decal.Core/ # Plugin manager/lifecycle (COM server)
│ ├── Decal.DecalControls/ # UI widgets (COM server)
│ ├── Decal.DecalRender/ # HUD rendering (COM server)
│ ├── Decal.D3DService/ # 3D markers (COM server)
│ └── Decal.DenAgent/ # System tray app (WinForms)
├── Native/
│ ├── InjectModern/ # Inject.DLL - D3D9 vtable hooking (C++)
│ ├── LauncherHookModern/ # LauncherHook.DLL - process injection (C++)
│ ├── CMakeLists.txt # Top-level CMake for native builds
│ ├── Inject/ # Old C++ source (reference only)
│ ├── LobbyHook/ # Old C++ source (reference only)
│ ├── Decal/ # Old C++ source (reference only)
│ └── ... # Other old native source (reference)
├── Installer/
│ ├── Package.wxs # WiX v5 MSI package definition
│ └── Decal.Installer.wixproj # WiX project file
├── build.cmd # Top-level build script
└── docs/
└── PLAN.md # This file
Status: Phases 1-5 Complete (Skeleton)
All 5 phases are structurally complete. The solution compiles with 0 errors. Most COM server methods are stubs — they implement the correct interfaces and preserve GUIDs, but many methods are no-ops that need real logic.
Phase 1: Build Existing .NET Layer ✅ COMPLETE
- 14 decompiled .NET projects building from
Decal.sln Directory.Build.propscentralizes build settings- All Interop projects use ProjectReference
Phase 2: Rewrite Native DLLs as C# COM Servers ✅ COMPLETE (stubs)
| # | Component | CLSID | Status |
|---|---|---|---|
| 1 | DecalDat | IDatService, IDatReader | Stub |
| 2 | DHS | IHotkeySystem, IHotkey | Stub |
| 3 | SpellFilter | ISpells, ISpell | Stub |
| 4 | DecalInput | IInputService, IKeyboard, IMouse | Stub |
| 5 | DecalNet | INetService, IMessage2, INetworkFilter2 | Stub |
| 6 | DecalFilters | IWorld, IWorldObject, ICharacterStats | Stub |
| 7 | Decal.Core | IDecalCore, IPluginSite2, IACHooks, IDecalEnum | Stub |
| 8 | DecalControls | 16 control types | Stub |
| 9 | DecalRender | IRenderService, IHUDView, IHUDBackground | Stub |
| 10 | D3DService | ID3DService, ID3DObj | Stub |
All preserve original CLSIDs/GUIDs for plugin compatibility.
Phase 3: C++ Shims ✅ COMPLETE
Native/InjectModern/— Inject.DLL (~530 lines) — D3D9 vtable hooking, lifecycleNative/LauncherHookModern/— LauncherHook.DLL (~330 lines) — CreateProcess IAT hook- CMake build:
cmake -G "Visual Studio 17 2022" -A Win32
Phase 4: DenAgent ✅ COMPLETE
Managed/Decal.DenAgent/— System tray, plugin management, options dialog
Phase 5: Build System & Installer ✅ COMPLETE
Installer/Package.wxs— WiX v5 MSIbuild.cmd— Top-level build script
Phase 6: Verification & Implementation (IN PROGRESS)
Goal: Pixel-perfect drop-in replacement for the closed-source Decal v2.9.8.3.
Strategy: Bottom-up, one DLL at a time. Replace one original DLL with our C# version while keeping all other original DLLs in place. Test. Move to the next.
Step 1: Build on Windows ✅ COMPLETE
dotnet build Managed\Decal.sln -c Release # 25 projects, 0 errors
Step 2: COM Registration Test ✅ COMPLETE
Automated script: tools/Test-ComRegistration.ps1 -Build
- 10/10 DLLs register successfully with regasm /codebase
- 50/50 CLSIDs verified in registry (WOW6432Node for 32-bit on 64-bit Windows)
- 0 GUID mismatches vs original Decal .rgs files
- 35 CLSIDs match original .rgs files exactly
- 15 additional CLSIDs from decompiled interops (no .rgs source available)
- 23 original CLSIDs not in scope (Inject module C++, internal action classes)
Step 2b: DecalDat Functional Test ✅ COMPLETE
Managed/Decal.DecalDat.Tests/ — 13/13 tests pass against real AC DAT files:
- Opens portal.dat (884 MB, sector size 1024) and cell.dat (332 MB, sector size 256)
- Reads 6 known file IDs from portal.dat (SpellTable, ComponentTable, etc.)
- DatStream: Read, Restart, ReadBinary with Tell tracking
Step 3: Smoke Test — DenAgent
Run Decal.DenAgent.exe and verify:
- Tray icon appears
- Configure dialog opens
- Plugin list populates from registry
Step 4: Integration Test (one DLL at a time)
Install original Decal v2.9.8.3, then swap one DLL at a time:
- Replace
DecalDat.DLL→ test DAT file reading - Replace
DHS.dll→ test hotkeys - Replace
SpellFilter.dll→ test spell data - Continue through all 10... Each swap that fails tells us exactly which stubs need real implementations.
Step 5: Plugin Compatibility Test
Load community Decal plugins and verify they work:
- VTank
- Mag-Tools
- Other popular plugins
Known Pitfalls
recordtypes need IsExternalInit, unavailable in net472 → use class insteadCollectionsMarshal.AsSpanunavailable in net472- LongValueKey enum: correct names are
keyStackCount,keyContainer - tagPOINT defined in Interop.Inject, needed by Interop.Render → add ProjectReference
- ICommandEvents delegates ambiguous between Interop.Controls and Interop.Inject → use
usingaliases
Build Requirements
- .NET SDK 8.0+ (targets net472 via NuGet reference assemblies)
- Visual Studio 2022 with C++ Desktop workload (for native DLLs)
- Windows SDK (for d3d9.h, Win32 APIs)
- WiX Toolset v5 (optional, for MSI installer)