openDecal/docs/PLAN.md
erik 84e3cb5a77 Update docs with Phase 6 progress
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>
2026-02-09 00:15:06 +01:00

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) and LauncherHook.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.props centralizes 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, lifecycle
  • Native/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 MSI
  • build.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:

  1. Replace DecalDat.DLL → test DAT file reading
  2. Replace DHS.dll → test hotkeys
  3. Replace SpellFilter.dll → test spell data
  4. 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

  • record types need IsExternalInit, unavailable in net472 → use class instead
  • CollectionsMarshal.AsSpan unavailable 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 using aliases

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)