Skip to content

Commit 008269f

Browse files
Merge remote-tracking branch 'upstream/ng' into ng
2 parents e7742e2 + 659e9ea commit 008269f

1,291 files changed

Lines changed: 17342 additions & 7779 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/maintenance.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,25 @@ name: Scripted maintenance
22

33
on:
44
push:
5-
branches: [ dev-ng ]
5+
branches: [ dev-ng, vr, ng ]
66

77
jobs:
88
maintenance:
99
runs-on: ubuntu-latest
1010

1111
steps:
12-
- uses: actions/checkout@v2
12+
- uses: actions/checkout@v4
1313

14-
- uses: actions/setup-python@v2
14+
- uses: actions/setup-python@v5
1515
with:
16-
python-version: '3.9'
16+
python-version: '3.10'
1717

1818
- name: Run clang-format
19-
run: find -type f \( -name *.h -o -name *.cpp \) | xargs clang-format-14 -style=file -i
19+
run: find -type f \( -name *.h -o -name *.cpp \) | xargs clang-format-16 -style=file -i
2020

2121
- name: Glob files
2222
run: python ${{ github.workspace }}/scripts/cmake_generate.py
2323

24-
- uses: stefanzweifel/git-auto-commit-action@v4
24+
- uses: stefanzweifel/git-auto-commit-action@v5
2525
with:
2626
commit_message: maintenance

CLAUDE.md

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
CommonLibSSE NG is a modern C++ library for SKSE (Skyrim Script Extender) plugin development that supports multiple Skyrim runtimes: SE (Special Edition), AE (Anniversary Edition), and VR. It provides a comprehensive C++ API for interacting with Skyrim's game engine and is designed to enable single-DLL plugins that work across all supported runtimes.
8+
9+
## Multi-Runtime Architecture
10+
11+
The library uses a unique multi-targeting system that allows plugins to support SE, AE, and VR simultaneously:
12+
- Runtime detection happens at loading time
13+
- Code can probe for runtime-specific features before using them
14+
- Address Library integration handles different game versions automatically
15+
- Conditional compilation flags control runtime support: `ENABLE_SKYRIM_SE`, `ENABLE_SKYRIM_AE`, `ENABLE_SKYRIM_VR`
16+
17+
## Build System Commands
18+
19+
### WSL Environment Requirements
20+
**IMPORTANT**: This project requires Windows-specific tooling (Visual Studio, MSVC compiler) and is designed for Windows development. When working in WSL environments:
21+
22+
- Use PowerShell commands via `powershell.exe` or `pwsh.exe` for build operations
23+
- CMake presets expect Windows paths and Visual Studio toolchain
24+
- Package managers (Vcpkg/Conan) need Windows environment setup
25+
26+
### Configuration and Building
27+
```bash
28+
# WSL: Use PowerShell for CMake operations
29+
powershell.exe "cmake --preset build-debug-msvc-vcpkg-all"
30+
powershell.exe "cmake --preset build-release-msvc-vcpkg-all"
31+
powershell.exe "cmake --preset build-debug-clang-cl-vcpkg-all"
32+
33+
# WSL: Build via PowerShell
34+
powershell.exe "cmake --build build/debug-msvc-vcpkg-all"
35+
powershell.exe "cmake --build build/release-msvc-vcpkg-all"
36+
37+
# WSL: Run tests via PowerShell
38+
powershell.exe "ctest --preset all"
39+
powershell.exe "ctest --preset unit" # Unit tests only
40+
powershell.exe "ctest --preset integration" # Integration tests only
41+
42+
# Native Windows (PowerShell/CMD)
43+
cmake --preset build-debug-msvc-vcpkg-all
44+
cmake --build build/debug-msvc-vcpkg-all
45+
ctest --preset all
46+
```
47+
48+
### Package Managers
49+
The project supports both Vcpkg (primary) and Conan:
50+
- **Vcpkg**: Use presets with `-vcpkg-` in the name
51+
- **Conan**: Use presets with `-conan-` in the name
52+
53+
### Runtime-Specific Builds
54+
Available runtime configurations:
55+
- `all`: Supports SE, AE, and VR
56+
- `flatrim`: SE and AE only (no VR)
57+
- `vr`: VR only
58+
- `se`: SE only
59+
- `ae`: AE only
60+
61+
### Test Categories
62+
- **Unit tests**: `[unit]` - No Skyrim module required
63+
- **Integration tests**: `[integration]` - Skyrim module at rest
64+
- **End-to-end tests**: `[e2e]` - Full Skyrim engine required
65+
66+
## Code Architecture
67+
68+
### Directory Structure
69+
- `include/RE/`: Reverse-engineered Skyrim classes organized alphabetically
70+
- `include/REL/`: Address Library integration and relocation utilities
71+
- `include/REX/`: Cross-platform abstraction layer
72+
- `include/SKSE/`: SKSE plugin framework interfaces
73+
- `src/`: Implementation files mirroring the include structure
74+
- `tests/`: Unit and integration tests using Catch2
75+
76+
### Key Components
77+
- **RE namespace**: Skyrim engine classes (Actor, TESForm, etc.)
78+
- **REL namespace**: Address resolution and version management
79+
- **SKSE namespace**: Plugin lifecycle, event systems, and serialization
80+
- **BST containers**: Bethesda's STL-like container classes
81+
- **Havok integration**: Physics system bindings
82+
83+
### Plugin Declaration
84+
Use the simplified CMake function instead of manual plugin setup:
85+
```cmake
86+
find_package(CommonLibSSE REQUIRED)
87+
add_commonlibsse_plugin(${PROJECT_NAME} SOURCES ${sources})
88+
target_link_libraries(${PROJECT_NAME} PUBLIC CommonLibSSE::CommonLibSSE)
89+
```
90+
91+
## Dependencies and Requirements
92+
93+
### Build Dependencies
94+
- Visual Studio 2022 with C++ Desktop Development
95+
- CMake 3.19+
96+
- Vcpkg or Conan
97+
- Address Library for target runtimes
98+
99+
### C++ Standard
100+
The project uses **C++23** (`cxx_std_23` is set in CMakeLists.txt)
101+
102+
### Address Library Integration
103+
Essential for cross-version compatibility:
104+
- SE: Address Library for SKSE Plugins
105+
- VR: VR Address Library for SKSEVR
106+
- Version detection and offset resolution handled automatically
107+
108+
## Development Workflow
109+
110+
### Making Changes
111+
1. Understand the multi-runtime implications of your changes
112+
2. Use runtime feature detection when accessing version-specific functionality
113+
3. Follow existing patterns for BST containers and Skyrim conventions
114+
4. Run unit tests before integration tests
115+
5. Test across different runtime configurations when possible
116+
117+
### Testing Strategy
118+
- Write unit tests for logic that doesn't require Skyrim
119+
- Use integration tests for Skyrim API interactions
120+
- Reserve e2e tests for full engine functionality
121+
- Tests can load different Skyrim executables for cross-runtime validation
122+
123+
### Code Organization
124+
- Reverse-engineered classes follow Skyrim's naming conventions
125+
- Use RTTI information for class hierarchies and virtual functions
126+
- Address Library IDs are managed through REL::ID system
127+
- Cross-runtime code uses feature detection patterns
128+
129+
### Multi-Runtime Conditional Patterns
130+
131+
CommonLibSSE NG handles runtime differences using two main conditional patterns:
132+
133+
#### Pattern 1: Runtime-Exclusive Virtual Functions
134+
135+
Use this pattern when a class has the **same base class** across runtimes, but **different virtual functions** exist in different runtimes.
136+
137+
**Example:** Camera state classes all inherit from `TESCameraState`, but VR has an extra `Unk_03()` virtual function that SE/AE don't have.
138+
139+
**For runtime-exclusive virtual functions (e.g., VR-only camera state `Unk_03()`):**
140+
141+
**Base Class Pattern:**
142+
```cpp
143+
// Header (.h file) - Three-way conditional pattern
144+
#if defined(EXCLUSIVE_SKYRIM_FLAT)
145+
// Function doesn't exist in SE/AE-only builds
146+
#elif defined(EXCLUSIVE_SKYRIM_VR)
147+
virtual void Unk_03(); // 03 - VR only
148+
#else
149+
void Unk_03(); // 03 - Multi-runtime (non-virtual)
150+
#endif
151+
152+
// Implementation (.cpp file)
153+
#ifdef SKYRIM_CROSS_VR
154+
void ClassName::Unk_03()
155+
{
156+
if (REL::Module::IsVR()) {
157+
REL::RelocateVirtual<decltype(&ClassName::Unk_03)>(0x03, 0x03, this);
158+
}
159+
// SE/AE: no-op, this function should never be called
160+
}
161+
#endif
162+
```
163+
164+
**Derived Class Pattern:**
165+
```cpp
166+
// Header (.h file) - Must match base class pattern
167+
#if defined(EXCLUSIVE_SKYRIM_FLAT)
168+
// Function doesn't exist in SE/AE-only builds
169+
#elif defined(EXCLUSIVE_SKYRIM_VR)
170+
void Unk_03() override; // 03 - VR only
171+
#else
172+
void Unk_03(); // 03 - Multi-runtime
173+
#endif
174+
175+
// Implementation (.cpp file) - Same as base class
176+
#ifdef SKYRIM_CROSS_VR
177+
void DerivedClass::Unk_03()
178+
{
179+
if (REL::Module::IsVR()) {
180+
REL::RelocateVirtual<decltype(&DerivedClass::Unk_03)>(0x03, 0x03, this);
181+
}
182+
// SE/AE: no-op, this function should never be called
183+
}
184+
#endif
185+
```
186+
187+
**CRITICAL: Vtable Slot Alignment**
188+
189+
When a runtime-exclusive function exists, ALL virtual functions that come after it in the inheritance hierarchy get shifted in the vtable:
190+
191+
```cpp
192+
// SE/AE vtable: Begin(01) -> End(02) -> Update(03) -> GetRotation(04)
193+
// VR vtable: Begin(01) -> End(02) -> Unk_03(03) -> Update(04) -> GetRotation(05)
194+
```
195+
196+
**Every derived class that overrides functions after a runtime-exclusive function MUST implement RelocateVirtual for those functions:**
197+
198+
```cpp
199+
#ifdef SKYRIM_CROSS_VR
200+
void DerivedClass::Update(BSTSmartPointer<TESCameraState>& a_nextState)
201+
{
202+
REL::RelocateVirtual<decltype(&DerivedClass::Update)>(0x03, 0x04, this, a_nextState);
203+
// SE^ VR^ (shifted by Unk_03)
204+
}
205+
206+
void DerivedClass::GetRotation(NiQuaternion& a_rotation)
207+
{
208+
REL::RelocateVirtual<decltype(&DerivedClass::GetRotation)>(0x04, 0x05, this, a_rotation);
209+
// SE^ VR^ (shifted by Unk_03)
210+
}
211+
#endif
212+
```
213+
214+
**Build behavior:**
215+
- **EXCLUSIVE_SKYRIM_FLAT** (SE/AE-only): Function doesn't exist, no vtable shift
216+
- **EXCLUSIVE_SKYRIM_VR** (VR-only): Function declared as virtual, engine provides implementation
217+
- **Multi-runtime** (ALL/FLATRIM): Function uses RelocateVirtual with runtime detection
218+
219+
**Note:** This pattern works for any runtime-exclusive function (VR-only, SE-only, AE-only, etc.), though VR-only functions are the most common case.
220+
221+
**Do NOT use `SKYRIM_REL_VR_VIRTUAL` for runtime-exclusive functions** - it breaks vtable alignment in multi-runtime builds. Use `SKYRIM_REL_VR_VIRTUAL` only for functions that exist across all runtimes but may need different calling conventions.
222+
223+
#### Pattern 2: Runtime-Exclusive Inheritance
224+
225+
Use this pattern when a class has **completely different base classes** in different runtimes.
226+
227+
**Example:** `ButtonEvent` inherits from `VRWandEvent` in VR but from `IDEvent` in SE/AE. `HUDMenu` inherits from `WorldSpaceMenu` in VR but from `IMenu` in SE/AE.
228+
229+
**Inheritance Pattern:**
230+
```cpp
231+
// Header (.h file) - Three-way conditional inheritance
232+
class ClassName :
233+
#if defined(EXCLUSIVE_SKYRIM_VR)
234+
public VROnlyBaseClass,
235+
public SharedBaseClass1,
236+
public SharedBaseClass2
237+
#elif !defined(ENABLE_SKYRIM_VR) // SE/AE-only
238+
public SEAEOnlyBaseClass,
239+
public SharedBaseClass1,
240+
public SharedBaseClass2
241+
#else
242+
// Multi-runtime: can't inherit from incompatible base classes
243+
public CommonBaseClass // Choose most compatible base
244+
#endif
245+
{
246+
public:
247+
// Upcast functions for multi-runtime builds
248+
[[nodiscard]] VROnlyBaseClass* AsVROnlyBaseClass() noexcept
249+
{
250+
if SKYRIM_REL_CONSTEXPR (!REL::Module::IsVR()) {
251+
return nullptr;
252+
}
253+
return &REL::RelocateMember<VROnlyBaseClass>(this, 0, 0);
254+
}
255+
256+
[[nodiscard]] SEAEOnlyBaseClass* AsSEAEOnlyBaseClass() noexcept
257+
{
258+
if SKYRIM_REL_CONSTEXPR (REL::Module::IsVR()) {
259+
return nullptr;
260+
}
261+
return &REL::RelocateMember<SEAEOnlyBaseClass>(this, 0, 0);
262+
}
263+
};
264+
```
265+
266+
**Critical:** Use `!defined(ENABLE_SKYRIM_VR)` for SE/AE-only case, not just `#else`, to ensure proper compilation across all preset types.
267+
268+
**Build behavior:**
269+
- **EXCLUSIVE_SKYRIM_VR** (VR-only): Inherits from VR-specific base classes
270+
- **SE/AE-only builds**: Inherits from SE/AE-specific base classes
271+
- **Multi-runtime** (ALL): Inherits from most compatible base, provides upcast functions that return nullptr when invalid
272+
273+
#### Pattern 3: Chained Inheritance Access
274+
275+
Use this pattern when you need to access base class functionality through a **chain of inheritance** that differs between runtimes.
276+
277+
**Example:** `ButtonEvent` needs access to `IDEvent` members, but:
278+
- **VR**: `ButtonEvent``VRWandEvent``IDEvent``InputEvent`
279+
- **SE/AE**: `ButtonEvent``IDEvent``InputEvent`
280+
- **Multi-runtime**: `ButtonEvent``InputEvent` (no direct inheritance relationship to `IDEvent`)
281+
282+
**Access Function Pattern:**
283+
```cpp
284+
[[nodiscard]] IDEvent* AsIDEvent() noexcept
285+
{
286+
#if defined(EXCLUSIVE_SKYRIM_VR)
287+
// VR builds: Navigate through VRWandEvent to reach IDEvent
288+
return static_cast<IDEvent*>(static_cast<VRWandEvent*>(this));
289+
#elif !defined(ENABLE_SKYRIM_VR)
290+
// SE/AE builds: Direct inheritance from IDEvent
291+
return static_cast<IDEvent*>(this);
292+
#else
293+
// Multi-runtime builds: No inheritance relationship, use RelocateMember
294+
return &REL::RelocateMember<IDEvent>(this, 0, 0);
295+
#endif
296+
}
297+
298+
// Accessor functions that work across all runtimes
299+
[[nodiscard]] std::uint32_t GetIDCode() const noexcept
300+
{
301+
if (auto idEvent = AsIDEvent()) {
302+
return idEvent->idCode;
303+
}
304+
return 0; // Fallback for invalid cases
305+
}
306+
307+
void SetIDCode(std::uint32_t a_idCode)
308+
{
309+
if (auto idEvent = AsIDEvent()) {
310+
idEvent->idCode = a_idCode;
311+
}
312+
}
313+
```
314+
315+
**Key Points:**
316+
- **Single-runtime builds**: Use efficient `static_cast` following known inheritance chains
317+
- **Multi-runtime builds**: Use `RelocateMember` since inheritance relationship doesn't exist at compile time
318+
- **Always validate**: Check return values from upcast functions for safety
319+
- **Consistent API**: Accessor functions provide the same interface regardless of runtime
320+
321+
**Build behavior:**
322+
- **EXCLUSIVE_SKYRIM_VR**: Uses double static_cast through inheritance chain
323+
- **SE/AE-only builds**: Uses direct static_cast
324+
- **Multi-runtime**: Uses RelocateMember for runtime-specific memory layout access
325+
326+
#### Multi-Runtime Architecture Patterns
327+
328+
CommonLibSSE NG uses different abstraction patterns based on the type and complexity of runtime differences:
329+
330+
**Virtual Functions (Explicit Pattern - Recommended):**
331+
- Use three-way conditionals for clarity and maintainability
332+
- Virtual functions are relatively rare, so explicit patterns don't create significant maintenance burden
333+
- Clear separation between runtime-exclusive and cross-runtime functions
334+
335+
**Data Structures (Macro Pattern - Established):**
336+
- Use `RUNTIME_DATA_CONTENT` macros for complex structural differences
337+
- Beneficial when multiple large structs have significant layout differences
338+
- Examples: `HUDMenu`, `State`, `BGSShaderParticleGeometryData`
339+
340+
**Inheritance (Three-way Conditional):**
341+
- Use explicit conditionals for different base class hierarchies
342+
- Example: VR's `WorldSpaceMenu` vs SE/AE's `IMenu` inheritance
343+
344+
**Individual Members (Mixed Approach):**
345+
- Simple exclusions: `#ifndef SKYRIM_CROSS_VR`
346+
- Type differences: Three-way conditionals
347+
- Runtime detection: Use `REL::Module::IsVR()` when both layouts exist
348+
349+
**Maintainability Guidelines:**
350+
- Prefer explicit patterns for infrequent changes (virtual functions)
351+
- Use macro abstraction for repetitive complex patterns (data structures)
352+
- Document offset calculations and Address Library relationships
353+
- Standardize member accessor patterns using `RelocateMember`

0 commit comments

Comments
 (0)