hello world 🌸

This commit is contained in:
☙◦ The Tablet ❀ GamerGirlandCo ◦❧ 2026-01-10 17:49:22 -05:00
commit c9f25e7b6a
Signed by: tablet
GPG Key ID: 924A5F6AF051E87C
23 changed files with 3590 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
build/
.idea/
.vscode/
.cache/
compile_commands.json
*.log
log*.txt
*.not

BIN
3rdparty/QtScript.lib vendored Normal file

Binary file not shown.

BIN
3rdparty/ToonBoomActionManager.lib vendored Normal file

Binary file not shown.

BIN
3rdparty/ToonBoomLayout.lib vendored Normal file

Binary file not shown.

BIN
3rdparty/ToonBoomPlugInManager.lib vendored Normal file

Binary file not shown.

42
3rdparty/dll2def.ps1 vendored Normal file
View File

@ -0,0 +1,42 @@
param (
[parameter(mandatory = $true)]
[string]$dllpath,
[parameter(mandatory = $true)]
[string]$outputpath
)
# ensure dumpbin is available (must be run from vs developer command prompt or similar environment)
$dllname = split-path -path $dllpath -leaf
$basename = [system.io.path]::getfilenamewithoutextension($dllname)
$deffile = join-path -path $outputpath -childpath "$basename.def"
$exportsfile = join-path -path $outputpath -childpath "$basename-exports.txt"
write-host "dumping exports to $exportsfile..."
# execute dumpbin and redirect output to a temporary file
dumpbin /exports $dllpath > $exportsfile
write-host "creating $deffile..."
# start the .def file content
"library $basename" | out-file -filepath $deffile -encoding ascii
"exports" | out-file -filepath $deffile -encoding ascii -append
# process the dumpbin output to extract function names
# the output format can vary, so the token skip might need adjustment
# this example assumes the format where the name is the 4th token after skipping initial lines.
get-content $exportsfile | foreach-object {
if ($_ -match '^\s+\d+\s+[0-9a-fa-f]+\s+[0-9a-fa-f]+\s+([^\s=]+)') {
$functionname = $matches[1]
# handle c++ name mangling if necessary (often, mangled names are put directly in the def)
# for c-style linkage, names appear as expected.
"$functionname" | out-file -filepath $deffile -encoding ascii -append
}
}
# clean up the temporary exports file
remove-item $exportsfile
write-host "successfully generated $deffile"
lib /def:$deffile /out:$outputpath/$basename.lib /machine:x64
# example usage (run within a developer powershell/command prompt):
# new-deffilefromdll -dllpath "c:\path\to\your\library.dll"

34
CMakeLists.txt Normal file
View File

@ -0,0 +1,34 @@
cmake_minimum_required(VERSION 3.20)
project(toon-boom-extension-framework
VERSION 0.1.0
DESCRIPTION "Toon Boom extension framework"
LANGUAGES CXX
)
if(NOT DEFINED VCPKG_ROOT)
message(FATAL_ERROR "VCPKG_ROOT is not defined. Please set the VCPKG_ROOT variable to the root directory of the vcpkg installation.")
endif()
if(NOT DEFINED QT5_ROOT_DIR)
message(FATAL_ERROR "QT5_ROOT_DIR is not defined. Please set the QT5_ROOT_DIR variable to the root directory of the Qt 5.15 installation.")
endif()
if(NOT DEFINED QT6_ROOT_DIR)
message(FATAL_ERROR "QT6_ROOT_DIR is not defined. Please set the QT6_ROOT_DIR variable to the root directory of the Qt 6 installation.")
endif()
include(${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
list(APPEND CMAKE_PREFIX_PATH ${QT6_ROOT_DIR})
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++20 /Zc:__cplusplus")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
find_package(Qt6 REQUIRED COMPONENTS Widgets Core Gui Core5Compat QUIET)
add_subdirectory(framework)

59
CMakePresets.json Normal file
View File

@ -0,0 +1,59 @@
{
"version": 6,
"cmakeMinimumRequired": {
"major": 4,
"minor": 2,
"patch": 0
},
"configurePresets": [
{
"name": "vs2026",
"displayName": "Visual Studio 18 2026",
"generator": "Visual Studio 18 2026",
"binaryDir": "${sourceDir}/build/vs2026",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "ninja",
"displayName": "Ninja",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/ninja",
"toolchainFile": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake",
"environment": {
"VCPKG_VISUAL_STUDIO_PATH": "C:/Program Files/Microsoft Visual Studio/18/Community",
"VCPKG_ROOT": "C:/vcpkg",
"CMAKE_GENERATOR_PLATFORM": "x64"
},
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"CMAKE_CXX_COMPILER": "cl.exe",
"CMAKE_TOOLCHAIN_FILE": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake",
"CMAKE_MAKE_PROGRAM": "C:/Program Files/Microsoft Visual Studio/18/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/Ninja/ninja.exe"
}
}
],
"buildPresets": [
{
"name": "vs2026-debug",
"configurePreset": "vs2026",
"configuration": "Debug"
},
{
"name": "vs2026-release",
"configurePreset": "vs2026",
"configuration": "Release"
},
{
"name": "ninja-debug",
"configurePreset": "ninja",
"configuration": "RelWithDebInfo"
},
{
"name": "ninja-release",
"configurePreset": "ninja",
"configuration": "Release"
}
]
}

View File

@ -0,0 +1,210 @@
# HarmonyPremium: QtScript / `QScriptEngine` integration
This document captures the current understanding of Harmony Premium's QtScript integration as observed in `RE/HarmonyPremium.exe.i64`.
## Key takeaways (high signal)
- Harmony has an internal `QObject` wrapper around the scripting runtime named **`SCR_ScriptEngineImpl`**.
- A higher-level manager **`SCR_ScriptManager`** constructs `SCR_ScriptEngineImpl`, registers a large set of interfaces (via `SCR_Scripting_registerInterfaces` at `0x140914850`), then injects itself into script as a global object named **`"___scriptManager___"`**.
- Two global native functions are installed into the script global object:
- **`include(filename)`** → forwards to a virtual method on the script/scene manager interface (and tries to execute the included file in the caller's scope by temporarily adopting the parent context's activation/`this` objects).
- **`require(filename)`** → resolves/remaps the path, loads the file, wraps it in a JavaScript closure that returns `exports`, evaluates it, and returns the `exports` object (or returns the uncaught exception).
- There are small helper routines used throughout Harmony to bind C++ into the scripting global object:
- bind `QObject*` via `QScriptEngine::newQObject(...)` then `QScriptValue::setProperty(...)`
- bind native callbacks via `QScriptEngine::newFunction(...)` then `QScriptValue::setProperty(...)`
## Main components
### `SCR_ScriptEngineImpl`
Observed in `SCR_ScriptEngineImpl_ctor` (`0x14082AEC0`).
What it does:
- Is a `QObject` subclass (has `qt_metacast`, metaobject data, etc).
- Allocates an internal `QScriptEngine` and stores it in the object (other methods access it via a field at **byte offset `+40`** from `this`).
- Installs a custom `QScriptEngineAgent` (`SCR_ScriptEngineImpl::ScriptAgent`) and sets the engine's process-events interval from preference key `"SCR_EVENT_PROCESSING_INTERVAL"`.
- Maintains a recursion depth counter (checked against **`12`**) used to prevent runaway recursion / re-entrancy during evaluation and calls.
Evaluation-related methods (renamed in the IDA DB during this session):
- `SCR_ScriptEngineImpl_callWithTempGlobal` (`0x14082B330`)
- Temporarily swaps the engine global object (`setGlobalObject`), calls a `QScriptValue` callable (`QScriptValue::call`), restores the old global, and maps uncaught exceptions into the return value.
- `SCR_ScriptEngineImpl_evalInNewGlobal` (`0x14082BEB0`)
- Creates a fresh global object (`newObject`), sets its prototype to a provided prototype or to the previous global, swaps it in via `setGlobalObject`, evaluates a `QString` script body, restores the previous global.
- On unwind: triggers `collectGarbage()` when leaving the outermost evaluation.
- `SCR_ScriptEngineImpl_evalFile` (`0x14082C590`)
- Reads a script file as UTF-8 and evaluates it.
- Writes `__file__` into the current scope chain before evaluation (see helper below).
- Uses `SCR_ScriptEngineImpl_evalInNewGlobal` when not already nested; otherwise evaluates directly in the existing global object.
### `SCR_ScriptManager`
Observed in `SCR_ScriptManager_ctor` (`0x14081FD60`).
What it does:
- Constructs `SCR_ScriptEngineImpl`.
- Calls a **large** interface registration routine (`SCR_Scripting_registerInterfaces`, `0x140914850`) which populates the scripting environment with many bindings.
- Injects itself into script as **global property** `"___scriptManager___"`:
- Done via `SCR_ScriptRuntime_defineGlobalQObject` (`0x14082CB50`)
- Installs native global functions:
- `"include"` bound to `QS_include` (`0x1408246C0`)
- `"require"` bound to `QS_require` (`0x140827D60`)
- Done via `SCR_ScriptRuntime_defineGlobalFunction` (`0x14082CAC0`)
## Builtins: `include()` / `require()`
### `include(filename)` (`QS_include`, `0x1408246C0`)
Behavior (from decompilation):
- Validates `argumentCount == 1` and that the argument is a string.
- Retrieves the global `"___scriptManager___"` object, converts it to a `QObject*`, and `QMetaObject::cast`s it to the expected interface type (error message says “scene manager interface”).
- Calls a virtual function at vtable offset `+88` on that interface, passing the filename string.
- If a parent script context exists, it temporarily sets the current context's activation object and this-object to the parent context's ones while doing the include, then restores them.
### `require(filename)` (`QS_require`, `0x140827D60`)
Behavior (from decompilation):
- Validates `argumentCount == 1` and that the argument is a string.
- Retrieves/casts global `"___scriptManager___"` as above.
- Calls a virtual function at vtable offset `+96` to resolve the requested module path, then applies an OS remap (`oswRemapPath::RemapPath2`).
- If the resolved path is a directory, appends `"/index.js"`.
- Reads the file and wraps it into a JS closure of the form:
```javascript
(function()
{
var exports = {};
var __file__ = "<resolved path>";
// file contents
return exports;
})
```
- Evaluates the wrapper; if it yields a function, calls it and returns the resulting `exports`.
- If the engine has an uncaught exception, returns the uncaught exception value.
## Helper routines used for global bindings
### Define global `QObject` (`SCR_ScriptRuntime_defineGlobalQObject`, `0x14082CB50`)
- Gets `engine->globalObject()`
- Wraps a `QObject*` using `QScriptEngine::newQObject(...)`
- Writes it into the global object with `QScriptValue::setProperty(...)`
- The property flags argument is passed as `2048` in the decompiler output.
### Define global native function (`SCR_ScriptRuntime_defineGlobalFunction`, `0x14082CAC0`)
- Gets `engine->globalObject()`
- Creates a `QScriptValue` function using `QScriptEngine::newFunction(callback)`
- Writes it into the global object via `setProperty(..., flags=2048)`
### Set `__file__` in scope chain (`SCR_ScriptRuntime_setScopeFileVar`, `0x14082CCB0`)
- Fetches `QScriptEngine::currentContext()->scopeChain()`
- Sets `scopeChain()[0].__file__ = <path>` (property flags passed as `2048`)
### Accessors used throughout registration
Several small helpers reveal how Harmony threads the live `QScriptEngine*` and other host pointers through its scripting subsystem. In decompiler output these are simple pointer-chasing accessors:
- `SCR_ScriptRuntime_getEngine` (`0x14082BCD0`)
- Returns `*(*a1 + 40)` (i.e., reads a `QScriptEngine*` stored at **byte offset `+40`** from the underlying script-engine wrapper object).
- `SCR_ScriptRuntime_getHostContext` (`0x14082CCA0`)
- Returns `*(*a1 + 24)` (a host/session/context pointer used to initialize many interface objects via a virtual method at vtable offset `+88`).
- `SCR_ScriptEngineImpl_setSceneInterface` (`0x14082CDA0`)
- Writes to `(*a1)+32`. Called from `SCR_ScriptManager_ctor` after interface registration; in the observed init flow it stores the singleton-like `"scene"` interface pointer (also injected as global `Scene`/`scene`).
## Why this matters for our injection goal
The above shows Harmony already has an established pattern for binding objects into the script global object (via `newQObject` + `setProperty`). The cleanest "natural" hook points to attach our own module object are:
- Right after `SCR_ScriptManager_ctor` injects `"___scriptManager___"` and registers `include/require`, or
- Anywhere we can get a pointer to the live `QScriptEngine` used for scripting (e.g. from a `SCR_ScriptEngineImpl` instance; other methods access it via the field at byte offset `+40`).
Next step: dig deeper into `SCR_Scripting_registerInterfaces` to map each global binding back to its concrete C++ type and owning subsystem (useful for choosing robust runtime hook points).
## Global namespace installed by Harmony
This section summarizes what `SCR_Scripting_registerInterfaces` (`0x140914850`) and `SCR_Scripting_registerUiTypes` (`0x140913920`) install into the engine.
### `SCR_Scripting_registerInterfaces` (`0x140914850`): core global objects
Direct `globalObject().setProperty(<name>, newQObject(...), flags=2048)` calls are visible in the decompilation for (non-exhaustive, but high-confidence) global names:
- `Scene` (capitalized; created from a singleton-like object named `"scene"`)
- `specialFolders`
- `exports` (set to a fresh script object created via `QScriptEngine::newObject`)
- `about`
- `Action` (registered at least once; later may be rebound if an action manager exists)
- `Drawing`
- `element`
- `fileMapper`
- `KeyModifiers`
- `MessageLog`
- `preferences`
- `scene` (lowercase; appears to alias the same underlying object as `Scene`)
- `System`
- `DrawingTools`
- `TimelineMarker`
- `Settings` (an instance of `SCR_SettingsInterface`)
- `column`
- `node`
- `selection`
- `PaletteObjectManager`
- `frame` (only when `WHO_Identity::family() != 4`)
- `compositionOrder` (backed by an object constructed with name `"composition"`)
- `copyPaste`
- `exporter`
- `func`
- `MovieImport`
- `render`
- `waypoint`
- `Backdrop`
- `sound`
- `stateUtil` (backed by an object constructed with name `"TB_StateUtil"`)
Also observed inside this function:
- Multiple `QMetaType` custom conversions registered via `QScriptEngine::registerCustomType(...)` (exact C++ types currently appear as `unk_140F7....` in the decompiler output).
- Additional interface initializers called (e.g. `SCR_DrawingKey::registerInterface(engine)`), plus many helper routines like `sub_14096.../sub_14097...` that likely register more types/enums.
### `SCR_Scripting_registerUiTypes` (`0x140913920`): UI/widget constructors and helpers
This function installs a number of `QMetaObject`-backed constructors into the global object using `newFunction(...)` + `newQMetaObject(...)` + `setProperty(..., flags=2048)`. Observed names include:
- `Dialog`
- `Label`
- `Button`
- `LineEdit`
- `NumberEdit`
- `DateEdit`
- `TimeEdit`
- `TextEdit`
- `SpinBox`
- `CheckBox`
- `RadioButton`
- `ComboBox`
- `GroupBox`
- `Slider`
- `ImportDrawingDlg` (conditional: `WHO_Identity::family() != 4`)
- `ExportVideoDlg` (conditional: `WHO_Identity::family() != 4`)
It also creates a `MessageBox` object with methods:
- `MessageBox.warning`
- `MessageBox.information`
- `MessageBox.critical`
And it registers several additional script-visible helpers:
- `DateEditEnum`
- `FileAccess`
- `DirSpec`
- `FileDialog` (conditional on an `a2` parameter in the decompiler output)
- `Input` (conditional on an `a2` parameter in the decompiler output)

7
docs/README.md Normal file
View File

@ -0,0 +1,7 @@
## Documents
- `HarmonyPremium_QtScript.md`: How HarmonyPremium wires up QtScript (`QScriptEngine`) and implements `include()` / `require()`, plus the main helper routines used to register C++ into the scripting global object.
- `ToonBoomLayout_Classes.md`: Detailed reconstruction of TULayoutView, TULayoutViewHolder, TULayoutFrame, TULayoutManager, TULayoutMainWindow, and TULayoutStorage classes from ToonBoomLayout.dll, including memory layouts, virtual tables, and method signatures.
- `ToonBoomLayout_ViewUsage.md`: Usage guide for creating and displaying custom TULayoutView instances with TUWidgetLayoutView, including code examples from HarmonyPremium.exe analysis.

View File

@ -0,0 +1,516 @@
# ToonBoomLayout.dll Class Analysis
This document contains reverse engineering analysis of key classes from `ToonBoomLayout.dll` used in Toon Boom Harmony Premium and Storyboard Pro.
## Overview
The layout system in Toon Boom uses a hierarchy of classes:
- **TULayoutView** - Base abstract class representing a view/panel that can be displayed
- **TULayoutViewHolder** - Widget container that can hold 1-2 TULayoutView instances with a splitter
- **TULayoutFrame** - Top-level frame (inherits QFrame) that contains tabs of view holders
## Class: TULayoutView
### Purpose
Abstract base class for all layout views in Toon Boom applications. Represents a dockable/tabbable view panel.
### Memory Layout (x64 MSVC)
| Offset | Size | Type | Member Name | Description |
|--------|------|------|-------------|-------------|
| 0x00 | 8 | ptr | vptr | Virtual function table pointer |
| 0x08 | 24 | QString | m_internalName | Internal identifier (default: "View" + uniqueId) |
| 0x20 | 104 | LAY_ToolbarInfo | m_toolbarInfo | Toolbar configuration for this view |
| 0x88 | 16 | AC_Menu*[2] | m_menuByType | Menus indexed by MenuType enum |
| 0x98 | 1 | bool | m_initializedFromCopy | True if initialized via copy constructor |
| 0xA0 | 24 | QString | m_caption | User-visible caption/title |
**sizeof(TULayoutView) ≈ 0xB8 (184 bytes)**
### Virtual Function Table
| VTable Idx | Method | Signature |
|------------|--------|-----------|
| 0 | destructor | `virtual ~TULayoutView()` |
| 1 | (pure virtual) | `virtual QWidget* widget() = 0` |
| 2 | initiate | `virtual TULayoutView* initiate(QWidget* parent)` |
| 3 | (pure virtual) | Unknown |
| 4 | (pure virtual) | Unknown |
| 5 | getParentHolderWidget (const) | `virtual const TULayoutViewHolder* getParentHolderWidget() const` |
| 6 | getParentHolderWidget | `virtual TULayoutViewHolder* getParentHolderWidget()` |
| 7 | hasMenu | `virtual bool hasMenu()` |
| 8 | setMenu | `virtual void setMenu(AC_Manager*, const char*, MenuType)` |
| 9 | setMenu | `virtual void setMenu(AC_Menu*, MenuType)` |
| 10 | menu | `virtual AC_Menu* menu(MenuType)` |
| 11 | toolbar | `virtual QDomElement toolbar()` |
| 12 | setToolbarInfo | `virtual void setToolbarInfo(const LAY_ToolbarInfo&)` |
| 13-14 | (other virtuals) | Implementation-specific |
| 15 | initializedFromCopy | `virtual bool initializedFromCopy()` |
| 16 | getCaption | `virtual QString getCaption(bool includeAdvanced) const` |
| 17 | getDynamicTextForCaption | `virtual QString getDynamicTextForCaption() const` |
| 18 | wantEditionStack | `virtual bool wantEditionStack() const` |
| 19 | displayName | `virtual QString displayName() const` |
### Key Methods
- **Constructor**: Generates unique internal name "View{N}" using static counter `TULayoutView::_uniqueId`
- **Copy Constructor**: Copies all members, sets `m_initializedFromCopy = true`
- **getCaption()**: Returns caption, optionally appending dynamic text in brackets if advanced display enabled
- **getParentHolderWidget()**: Traverses widget hierarchy to find parent TULayoutViewHolder
---
## Class: TULayoutViewHolder
### Purpose
Container widget that can hold 1-2 TULayoutView instances with an optional splitter for split view.
### Inheritance
`QWidget``TULayoutViewHolder`
### Memory Layout (x64 MSVC)
| Offset | Size | Type | Member Name | Description |
|--------|------|------|-------------|-------------|
| 0x00 | 8 | ptr | vptr (QObject) | QObject vtable |
| 0x10 | 8 | ptr | vptr (QPaintDevice) | QPaintDevice vtable |
| 0x18-0x27 | | | (QWidget members) | Inherited from QWidget |
| 0x28 | 8 | ptr | m_views.begin | std::vector<TULayoutView*> start |
| 0x30 | 8 | ptr | m_views.end | std::vector<TULayoutView*> end |
| 0x38 | 8 | ptr | m_views.capacity | std::vector<TULayoutView*> capacity |
| 0x40 | 8 | double | m_savedSplitterRatio | Splitter ratio (default: 0.5) |
| 0x48 | 8 | ptr | m_splitter | UI_Splitter* (vertical orientation) |
| 0x50 | 8 | ptr | m_leftLayout | WID_VBoxLayout* for left side |
| 0x58 | 8 | ptr | m_rightLayout | WID_VBoxLayout* for right side |
| 0x60 | 8 | ptr | m_leftFrame | QFrame* for left pane |
| 0x68 | 8 | ptr | m_rightFrame | QFrame* for right pane |
**sizeof(TULayoutViewHolder) = 0x70 (112 bytes)**
### Key Methods
| Address | Method | Description |
|---------|--------|-------------|
| 0x180031150 | Constructor | Creates holder with parent widget, sets up splitter |
| 0x180031360 | Destructor | Frees view vector, destroys widget |
| 0x180031480 | addView | Adds a view (max 2), returns false if full |
| 0x180031610 | nbViews | Returns count of views in holder |
| 0x180031620 | removeView | Removes view from holder, adjusts layout |
| 0x180031850 | setFocusToChild | Sets focus to first view's widget |
| 0x180031890 | splitterRatio | Returns current splitter ratio |
| 0x1800319b0 | updateWidgets | Updates layout after view changes |
### Behavior
- When empty (0 views): Splitter hidden
- When 1 view: View added directly to main layout, splitter hidden
- When 2 views: First view in left frame, second in right frame, splitter visible
- Maximum capacity: 2 views (addView returns false if full)
---
## Class: TULayoutFrame
### Purpose
Top-level dockable/floatable frame that contains a tabbed interface of TULayoutViewHolder widgets.
### Inheritance
`QFrame``TULayoutFrame`
### Constructor Signature
```cpp
TULayoutFrame(
AC_Manager* manager, // Action/menu manager
QWidget* parent, // Parent widget
const QString& name, // Object name
TULayoutManager* layoutMgr, // Layout manager
bool floating, // True if floating window
bool unknown, // Unknown flag
bool singleViewMode // True for single view mode
);
```
### Memory Layout (x64 MSVC)
| Offset | Size | Type | Member Name | Description |
|--------|------|------|-------------|-------------|
| 0x00 | 8 | ptr | vptr (QObject) | QObject vtable |
| 0x10 | 8 | ptr | vptr (QPaintDevice) | QPaintDevice vtable |
| 0x18-0x27 | | | (QFrame members) | Inherited from QFrame |
| 0x28 | 8 | ptr | m_layoutManager | TULayoutManager* |
| 0x30 | 8 | ptr | m_mainLayout | WID_VBoxLayout* main vertical layout |
| 0x38 | 8 | ptr | m_topHBoxLayout | WID_HBoxLayout* top row |
| 0x40 | 8 | ptr | m_menuButton | QToolButton* "View Menu" button |
| 0x48 | 8 | ptr | m_mainWindow | TULayoutMainWindow* (contains toolbar area) |
| 0x50 | 8 | ptr | m_displayTools | TULayoutDisplayTools* display selector |
| 0x58 | 8 | ptr | m_compositeCombo | QComboBox* composite selector |
| 0x60 | 8 | ptr | m_tabBar | GUT_TabBar* tab bar |
| 0x68 | 8 | ptr | m_stack | QStackedWidget* for tab content |
| 0x70 | 8 | ptr | m_editionStackWidget | Symbol/edition stack widget |
| 0x78 | 8 | ptr | m_closeButton | QToolButton* close button |
| 0x80 | 8 | ptr | m_createViewMenuButton | QToolButton* "Create View Menu" |
| 0x88 | 1 | bool | m_isCurrent | Is this the current/focused frame |
| 0x89 | 1 | bool | m_isDocked | True if docked (not floating) |
| 0x8A | 1 | bool | m_isInSingleViewMode | Single view mode flag |
| 0x90 | 8 | ptr | m_toolbar | AC_Toolbar* view-specific toolbar |
| 0x98 | 4 | int | m_unknown | Initialized to -1 |
| 0xA0 | 16 | | m_toolbarInfoMap | Map/list for toolbar info per view |
| 0xB0 | 8 | ptr | m_parentWidget | Parent widget reference |
| 0xB8 | 8 | ptr | m_layoutFrameMenu | AC_Menu* context menu |
| 0xC0 | 8 | ptr | m_viewToolbar | AC_Toolbar* active toolbar |
### Key Methods
| Address | Method | Description |
|---------|--------|-------------|
| 0x180015DD0 | Constructor | Full initialization with all UI elements |
| 0x180016D10 | Destructor | Cleanup menu and toolbar info |
| 0x180010D60 | getLayoutManager | Returns m_layoutManager |
| 0x180010D70 | getStack | Returns m_stack (QStackedWidget) |
| 0x18001B660 | setCurrent | Sets m_isCurrent, triggers repaint |
| 0x180010DB0 | isDocked | Returns m_isDocked |
| 0x1800190C0 | isInSingleViewMode | Returns m_isInSingleViewMode |
| 0x180018F70 | getTab | Returns m_tabBar |
| 0x180018F80 | getToolbar | Returns m_viewToolbar |
| 0x180017FB0 | currentTab | Returns current tab index |
| 0x180017FC0 | currentTabName | Returns current tab name |
| 0x180018000 | currentTabViewHolder | Returns TULayoutViewHolder* for current tab |
| 0x1800172B0 | addTab | Adds tab with view holder |
| 0x1800180E0 | delTab | Removes tab by view holder |
| 0x180018170 | delView | Removes view widget |
| 0x180018280 | delViewHolder | Removes view holder |
| 0x18001B670 | setCurrentTab(int) | Sets current tab by index |
| 0x18001B6B0 | setCurrentTab(ViewHolder*) | Sets current tab by view holder |
| 0x18001C2B0 | updateTabName | Updates tab display text |
### Signals/Slots
Connected signals (from constructor analysis):
- `TULayoutDisplayTools::activated(int)``TULayoutFrame::onDisplayChanged(int)`
- `QComboBox::textActivated(QString)``TULayoutFrame::compositeChanged(QString)`
- `GUT_TabBar::clicked()``TULayoutFrame::updateFocus()`
- `GUT_TabBar::currentChanged(int)``TULayoutFrame::setCurrentTab(int)`
- `QToolButton::clicked()``TULayoutFrame::handleCloseViewButton()`
---
## Supporting Class: LAY_ToolbarInfo
### Purpose
Stores toolbar configuration and state for a view.
### Memory Layout
| Offset | Size | Type | Member Name | Description |
|--------|------|------|-------------|-------------|
| 0x00 | 4 | int | m_x | X position |
| 0x04 | 4 | int | m_y | Y position |
| 0x08 | 4 | int | m_index | Toolbar index |
| 0x0C | 4 | int | m_width | Width |
| 0x10 | 4 | int | m_height | Height |
| 0x14 | 1 | bool | m_newline | Starts new row |
| 0x15 | 1 | bool | m_visible | Is visible |
| 0x16 | 1 | bool | m_isDefault | Is default config |
| 0x18 | 24 | QString | m_name | Toolbar name |
| 0x30 | 4 | Qt::Orientation | m_orientation | Horizontal/Vertical |
| 0x34 | 4 | Qt::ToolBarArea | m_toolBarArea | Dock area |
| 0x38 | 24 | QList<QString> | m_buttonConfig | Button configuration |
| 0x50 | 24 | QList<QString> | m_buttonDefaultConfig | Default button config |
**sizeof(LAY_ToolbarInfo) = 0x68 (104 bytes)**
---
## Analysis Methodology
1. **Constructor Analysis**: Traced member initialization order and sizes
2. **Accessor Analysis**: Used simple getter methods to confirm member offsets
3. **Destructor Analysis**: Verified cleanup order and dynamic allocations
4. **VTable Extraction**: Read vtable memory and resolved function addresses
5. **Signal/Slot Tracing**: Identified Qt connections in constructor
## Database Locations
- **TULayoutView vtable**: `0x180056F38`
- **TULayoutViewHolder vtable (QObject)**: `0x18005D600`
- **TULayoutViewHolder vtable (QPaintDevice)**: `0x18005D770`
- **TULayoutFrame vtable (QObject)**: `0x180058E18`
- **TULayoutFrame vtable (QPaintDevice)**: `0x180058F90`
- **TULayoutView::staticMetaObject**: N/A (not a QObject)
- **TULayoutViewHolder::staticMetaObject**: `0x1800646E0`
- **TULayoutFrame::staticMetaObject**: `0x180062AF0`
- **TULayoutManager vtable (QObject)**: `0x18005A1C8`
- **TULayoutManager vtable (TULayoutStorage)**: `0x18005A428`
- **TULayoutManager vtable (PLUG_ToolbarService)**: `0x18005A458`
- **TULayoutManager vtable (PLUG_MenuService)**: `0x18005A490`
- **TULayoutManager::staticMetaObject**: `0x180062BF0`
- **TULayoutMainWindow vtable**: `0x180058C60`
- **TUWidgetLayoutView vtable (QObject)**: `0x18005C0F8`
- **TUWidgetLayoutView vtable (QPaintDevice)**: `0x18005C268`
- **TUWidgetLayoutView vtable (AC_ResponderTemplateWidget)**: `0x18005C2A8`
- **TUWidgetLayoutView vtable (TULayoutView)**: `0x18005C348`
- **TUWidgetLayoutView::staticMetaObject**: `0x180064040`
---
## Class: TULayoutManager
### Purpose
Central manager for the entire layout system. Manages all frames, areas, splitters, toolbars, and layout persistence. Inherits from multiple base classes to provide comprehensive functionality.
### Inheritance
`QMainWindow` + `TULayoutStorage` + `PLUG_ToolbarService` + `PLUG_MenuService``TULayoutManager`
### Constructor Signature
```cpp
TULayoutManager(QString layoutPath);
```
### Memory Layout (x64 MSVC)
| Offset | Size | Type | Member Name | Description |
|--------|------|------|-------------|-------------|
| 0x00 | 8 | ptr | vptr (QObject) | QObject vtable |
| 0x10 | 8 | ptr | vptr (QPaintDevice) | QPaintDevice vtable |
| 0x18-0x27 | | | (QMainWindow members) | Inherited from QMainWindow |
| 0x28 | 136 | TULayoutStorage | (base class) | Layout storage at +40 |
| 0xB0 | 8 | PLUG_ToolbarService | (base class) | Toolbar service interface |
| 0xB8 | 8 | PLUG_MenuService | (base class) | Menu service interface |
| 0xC0 | 1 | bool | m_unknown | Unknown flag |
| 0xC8 | 8 | ptr | m_private | TULayoutManager_Private* |
### TULayoutManager_Private Structure (0x110 = 272 bytes)
| Offset | Size | Type | Member Name | Description |
|--------|------|------|-------------|-------------|
| 0x00 | 8 | ptr | vptr | VTable pointer |
| 0x08-0x0F | | | (QObject members) | Inherited |
| 0x10 | 8 | ptr | m_mainFrame | QFrame* central frame |
| 0x18 | 8 | ptr | m_mainLayout | WID_VBoxLayout* |
| 0x20-0x5F | | | (reserved) | Initialized to null |
| 0x60 | 24 | vector | m_splitters | std::vector<TULayoutSplitter*> |
| 0x78 | 24 | vector | m_frames | std::vector<TULayoutFrame*> |
| 0x90 | 24 | vector | m_areas | std::vector<TULayoutArea*> |
| 0xA8 | 24 | vector | m_pluginAreas | std::vector<TULayoutArea*> |
| 0xC0 | 8 | QPoint | m_savedPos | Saved window position |
| 0xC8 | 8 | QSize | m_savedSize | Saved window size |
| 0xD0 | 4 | int | m_stateFlags | State flags |
| 0xD8 | 8 | ptr | m_currentLayoutFrame | TULayoutFrame* currently focused |
| 0xF0 | 8 | ptr | m_owner | TULayoutManager* back pointer |
| 0xF8 | 8 | ptr | m_actionManager | AC_Manager* action manager |
### Key Methods
| Address | Method | Description |
|---------|--------|-------------|
| 0x18001CBA0 | Constructor | Creates manager, initializes storage, creates private data |
| 0x18001D360 | Destructor | Cleans up all views, areas, frames |
| 0x18001DDB0 | addFrame(QString) | Creates new TULayoutFrame |
| 0x18001E030 | addFrame(QWidget*) | Creates frame with parent |
| 0x18001DB50 | addArea | Adds new TULayoutArea |
| 0x18001EC00 | addView | Adds view to frame |
| 0x180020130 | delFrame | Removes and deletes frame |
| 0x18001F4F0 | closeFrame | Closes frame, may delete |
| 0x180020D90 | findFrame(int) | Find frame by index |
| 0x180020DC0 | findFrame(QWidget*) | Find frame containing widget |
| 0x180020EA0 | findInstance | Find view instance by name |
| 0x180021FE0 | getCurrentLayoutFrame | Returns m_currentLayoutFrame |
| 0x1800220D0 | getMainFrame | Returns m_mainFrame |
| 0x1800220C0 | getFrames | Returns frames vector |
| 0x180021F40 | getAreas | Returns areas vector |
| 0x180022270 | getSplitters | Returns splitters vector |
| 0x1800241D0 | setCurrentLayoutFrame | Sets focused frame |
| 0x1800241C0 | setActionManager | Sets AC_Manager |
| 0x18001FE60 | currentView | Returns currently visible view |
| 0x18001F1C0 | changeLayout | Changes to named layout |
### Signals
- `backgroundImageChanged()`
- `fullScreenStateChanged()`
- `layoutChanged(TULayout*)`
- `preferencesChange()`
- `sceneSaved()`
---
## Class: TULayoutMainWindow
### Purpose
Lightweight QMainWindow subclass used inside TULayoutFrame to provide toolbar docking area for view-specific toolbars.
### Inheritance
`QMainWindow``TULayoutMainWindow`
### Memory Layout (x64 MSVC)
| Offset | Size | Type | Member Name | Description |
|--------|------|------|-------------|-------------|
| 0x00 | 8 | ptr | vptr (QObject) | QObject vtable |
| 0x10 | 8 | ptr | vptr (QPaintDevice) | QPaintDevice vtable |
| 0x18-0x27 | | | (QMainWindow members) | Inherited |
| 0x28 | 8 | ptr | m_manager | AC_Manager* reference |
**sizeof(TULayoutMainWindow) = 0x30 (48 bytes)**
### Notes
- Created inside TULayoutFrame constructor
- Stores reference to AC_Manager for toolbar creation
- The QStackedWidget is set as central widget
- Provides toolbar docking areas (top, bottom, left, right)
---
## Class: TULayoutStorage
### Purpose
Base class providing layout persistence and toolbar configuration management.
### Memory Layout (x64 MSVC)
| Offset | Size | Type | Member Name | Description |
|--------|------|------|-------------|-------------|
| 0x00 | 8 | ptr | vptr | Virtual function table |
| 0x08 | 8 | ptr | m_currentLayout | TULayout* current active layout |
| 0x10 | 8 | ptr | m_previousLayout | TULayout* previous layout |
| 0x18 | 24 | vector | m_layouts | std::vector<TULayout*> all layouts |
| 0x30 | 24 | vector | m_toolbarLayouts | std::vector<TULayout*> toolbar layouts |
| 0x50 | 24 | QString | m_layoutPath | Path to layout storage |
| 0x68 | 8 | ptr | m_globalToolbarConfig | std::map for global toolbar configs |
| 0x70 | 8 | | (unused) | |
| 0x78 | 8 | ptr | m_viewToolbarConfig | std::map for view toolbar configs |
| 0x80 | 4 | int | m_flags | State flags |
**sizeof(TULayoutStorage) = 0x88 (136 bytes)**
### Key Methods
| Address | Method | Description |
|---------|--------|-------------|
| 0x18002B790 | Constructor | Initializes with layout path |
| 0x18002B890 | Destructor | Cleans up layouts and configs |
| 0x180009220 | getCurrentLayout | Returns m_currentLayout |
| 0x18002D0E0 | loadLayout | Loads layout from file |
| 0x18002E3B0 | loadLayouts | Loads all layouts from directory |
| 0x18002E8C0 | saveLayout | Saves layout to file |
| 0x18002C8F0 | findLayout | Finds layout by name |
| 0x18002BA70 | addLayout | Creates new layout |
| 0x18002C5D0 | delLayout | Deletes layout |
---
## Class: TUWidgetLayoutView
### Purpose
Primary concrete implementation of TULayoutView for wrapping QWidget content in the layout system. Inherits from `AC_ResponderTemplateWidget<QWidget>` and embeds `TULayoutView` at offset +104.
### Inheritance
`AC_ResponderTemplateWidget<QWidget>` + `TULayoutView` (embedded) → `TUWidgetLayoutView`
### Constructor Signature
```cpp
TUWidgetLayoutView::TUWidgetLayoutView(
AC_Manager* manager, // Action manager for menus/toolbars
const QString& viewName, // Internal name (e.g., "paletteView")
QWidget* parent, // Parent widget (usually nullptr)
const char* objectName, // QObject name (e.g., "PaletteLayoutView")
Qt::WindowFlags flags // Window flags (usually 0)
);
```
### Memory Layout (x64 MSVC)
| Offset | Size | Type | Member Name | Description |
|--------|------|------|-------------|-------------|
| 0x00 | 8 | ptr | vptr (QObject) | From AC_ResponderTemplateWidget<QWidget> |
| 0x10 | 8 | ptr | vptr (QPaintDevice) | |
| 0x18-0x27 | | | (QWidget members) | Inherited from QWidget |
| 0x28 | 8 | ptr | vptr (AC_ResponderTemplateWidget) | AC_Responder interface |
| 0x30 | 8 | ptr | m_actionManager | AC_Manager* |
| 0x38 | 24 | QString | m_responderIdentity | Responder ID |
| 0x50 | 24 | QString | m_responderDescription | Description |
| 0x68 | 8 | ptr | vptr (TULayoutView) | **TULayoutView interface starts here** |
| 0x70 | 24 | QString | m_internalName | Internal name from TULayoutView |
| 0x88 | 104 | LAY_ToolbarInfo | m_toolbarInfo | Toolbar info from TULayoutView |
| 0xF0 | 16 | AC_Menu*[2] | m_menuByType | Menus from TULayoutView |
| 0x100 | 1 | bool | m_initializedFromCopy | Copy flag from TULayoutView |
| 0x108 | 24 | QString | m_caption | Caption from TULayoutView |
**sizeof(TUWidgetLayoutView) ≈ 0x120 (288 bytes)**
### VTables
- **QObject vtable**: `0x18005C0F8`
- **QPaintDevice vtable**: `0x18005C268`
- **AC_ResponderTemplateWidget<QWidget> vtable**: `0x18005C2A8`
- **TULayoutView vtable**: `0x18005C348`
- **staticMetaObject**: `0x180064040`
### Key Methods
| Address | Method | Description |
|---------|--------|-------------|
| 0x1800300A0 | Constructor | Initializes widget, responder, and TULayoutView |
| 0x180030480 | Destructor | Destroys TULayoutView members, then base class |
| 0x180030E90 | getWidget() const | Returns `this - 104` (QWidget* from TULayoutView*) |
| 0x180030F10 | mousePressEvent | Calls base, sets focus if event not accepted |
| 0x180031140 | triggerMenuChanged | Emits menuChanged() signal |
| 0x18004A6D0 | menuChanged | Qt signal emission |
### Critical: The +104 Offset
When working with TUWidgetLayoutView and TULayoutManager:
1. **TULayoutView* is at offset +104 (0x68)** from TUWidgetLayoutView base
2. **getWidget() returns `this - 104`** when called on TULayoutView interface
3. When registering with `addArea`, pass `layoutView()`:
```cpp
TUWidgetLayoutView* widget = new MyCustomView(...);
TULayoutView* layoutView = widget->layoutView(); // returns this + 104
layoutManager->addArea("MyView", displayName, layoutView, ...);
```
### Constructor Analysis (0x1800300A0)
```cpp
TUWidgetLayoutView::TUWidgetLayoutView(
_QWORD* this, AC_Manager* manager, QString& viewName,
QWidget* parent, const char* objectName, int flags)
{
// Create objectName QString
QString objNameStr(objectName);
// Initialize AC_ResponderTemplateWidget<QWidget> base
AC_ResponderTemplateWidget<QWidget>::AC_ResponderTemplateWidget(
this, parent, objectName, objNameStr, flags);
// Initialize embedded TULayoutView at this+13*8 = this+104
TULayoutView::TULayoutView((TULayoutView*)(this + 13));
// Set up vtables
this[0] = &TUWidgetLayoutView::vftable_QObject;
this[2] = &TUWidgetLayoutView::vftable_QPaintDevice;
this[5] = &TUWidgetLayoutView::vftable_AC_ResponderTemplateWidget;
this[13] = &TUWidgetLayoutView::vftable_TULayoutView;
// Set minimum width
QWidget::setMinimumWidth(this, 150);
// Initialize action manager
if (parent && manager) {
AC_ResponderTemplateWidget::initActionManager(this, manager);
} else {
this[6] = manager; // m_actionManager at +0x30
}
}
```
### Related Classes
- **TUVBoxLayoutView**: Similar to TUWidgetLayoutView but uses QVBoxLayout
- **TUScrollViewLayoutView**: Uses QScrollArea as base
- **TUTextEditLayoutView**: Uses QTextEdit as base
- **TUCanvasViewLayoutView**: Uses QGraphicsView as base
- **TUFrameLayoutView**: Uses QFrame as base

View File

@ -0,0 +1,321 @@
# Creating and Displaying TULayoutView in Toon Boom
This document explains how to create custom views and display them in Toon Boom Harmony/Storyboard Pro using the TULayoutView system.
## Overview
Toon Boom uses a hierarchical layout system:
1. **TULayoutManager** - Central manager for all views and frames
2. **TULayoutArea** - Metadata about a view type that can be instantiated
3. **TULayoutFrame** - A window/panel containing tabbed view holders
4. **TULayoutViewHolder** - Container holding 1-2 TULayoutView instances with optional splitter
5. **TULayoutView** - Abstract base class for actual view content
## Creating Custom Views by Subclassing TULayoutView
The recommended approach is to **directly subclass `TULayoutView`** and implement the required pure virtual methods.
### Critical Discovery: How widget() Works
**IMPORTANT**: The `widget()` method (vtable slot 1) is called by `TULayoutArea::add` and the return value is treated as a `TULayoutView*`, NOT a `QWidget*`. The actual displayable widget is obtained later via `getWidget()`.
This means:
- `widget()` should return `this` (the view itself, cast to QWidget*)
- `getWidget()` should return the actual QWidget for display
### Required Pure Virtual Methods
When subclassing `TULayoutView`, you must implement these 5 pure virtual methods:
```cpp
// Slot 1: Returns THIS view as the "widget" - treated as TULayoutView* by caller
virtual QWidget *widget() = 0;
// Slots 3-4: Return the actual displayable QWidget
virtual const QWidget *getWidget() const = 0;
virtual QWidget *getWidget() = 0;
// Slot 29: Called when menus need refresh
virtual void triggerMenuChanged() = 0;
// Slot 31 (protected): Marker method
virtual void isTULayoutView() = 0;
```
### Example: Complete Custom View Implementation
```cpp
// test_frame.hpp
#pragma once
#include "toon_boom_layout.hpp"
class TestView : public TULayoutView {
public:
TestView();
~TestView() override;
// Pure virtuals - MUST implement all 5
QWidget *widget() override;
const QWidget *getWidget() const override;
QWidget *getWidget() override;
void triggerMenuChanged() override {}
// Override initiate to return this view
TULayoutView *initiate(QWidget *parent) override;
// Optional overrides
QString displayName() const override;
protected:
void isTULayoutView() override {}
private:
QFrame *m_frame; // The actual widget content
QVBoxLayout *m_mainLayout;
};
```
```cpp
// test_frame.cpp
#include "test_frame.hpp"
#include <QtWidgets/QLabel>
TestView::TestView()
: TULayoutView() {
// Create the frame that will hold our content
m_frame = new QFrame();
m_frame->setMinimumSize(400, 300);
m_mainLayout = new QVBoxLayout(m_frame);
QLabel *label = new QLabel("Hello from custom view!");
label->setAlignment(Qt::AlignCenter);
m_mainLayout->addWidget(label);
}
QWidget *TestView::widget() {
// CRITICAL: TULayoutArea::add calls this via vtable[1] and expects
// a TULayoutView* return value, NOT QWidget*. The returned pointer
// is then used to call getWidget() for the actual widget.
// So we return `this` which IS-A TULayoutView*.
return reinterpret_cast<QWidget*>(static_cast<TULayoutView*>(this));
}
const QWidget *TestView::getWidget() const {
return m_frame;
}
QWidget *TestView::getWidget() {
return m_frame;
}
TULayoutView *TestView::initiate(QWidget *parent) {
// Return this view - it's already initialized
if (parent && m_frame) {
m_frame->setParent(parent);
}
return this;
}
QString TestView::displayName() const {
return QString("My Custom View");
}
TestView::~TestView() {
delete m_frame;
}
```
## Registering Views with TULayoutManager
### TULayoutManager::addArea Signature
```cpp
bool TULayoutManager::addArea(
const char* typeName, // Type identifier (e.g., "Colour", "Node View")
const QString& displayName, // Translated display name
TULayoutView* view, // View instance (your TestView*)
bool visible, // Initially visible
bool createFrame, // Create new frame for this view
bool docked, // Is docked (not floating)
const QSize& minSize, // Minimum size
bool useMinSize, // Whether to use minimum size
bool isPlugin, // Is this a plugin area
bool defaultVisible, // Default visibility state
bool unknown // Unknown flag (usually true)
);
```
### Complete Registration Example
```cpp
void showCustomView() {
auto lm = PLUG_Services::getLayoutManager();
if (!lm) {
return;
}
// Create your custom view - it's a direct TULayoutView subclass
TestView* myView = new TestView();
// Register with the layout manager
bool success = lm->addArea(
"TestView", // typeName (unique ID)
QString("My Test View"), // displayName
myView, // TULayoutView* - pass directly, no offset needed!
true, // visible
true, // createFrame
true, // docked
QSize(500, 400), // minSize
true, // useMinSize
false, // isPlugin
true, // defaultVisible
true // unknown
);
if (success) {
// Raise the view to show it
auto area = lm->findArea(QString("TestView"));
if (area) {
lm->raiseArea(area, nullptr, false, QPoint(100, 100));
}
}
}
```
## The Call Flow Explained
When you call `TULayoutManager::addArea`, the following happens:
1. **addArea** stores your `TULayoutView*` in a new `TULayoutArea`
2. When the view needs to be displayed, **TULayoutArea::add** is called
3. `add` calls `view->widget()` (vtable slot 1) - **expects TULayoutView* return!**
4. The return value is passed to **TULayoutViewHolder::addView**
5. `addView` calls `view->getWidget()` to get the actual QWidget
6. The QWidget is reparented and displayed
This is why `widget()` must return `this` - the calling code treats the return as `TULayoutView*` to make further virtual calls.
## Opening Views at Runtime
To programmatically show a view that's already registered:
```cpp
// Using TULayoutManager::raiseArea
TULayoutView* view = layoutManager->raiseArea(
QString("TestView"), // Area name
targetFrame, // TULayoutFrame* (or nullptr for current)
true, // Create new instance if needed
QPoint(0, 0) // Position hint
);
```
## Key Patterns
### 1. DPI Scaling
For views with minimum size requirements, use `UT_DPI::scale()`:
```cpp
QSize baseSize(260, 450);
QSize scaledSize = UT_DPI::scale(baseSize);
```
### 2. Common View Sizes (from HarmonyPremium analysis)
| View Type | Base Size |
|-----------|-----------|
| Colour | 260 × 450 |
| Coord. And Control Points | 260 × 300 |
| Layer Properties | 260 × 450 |
| Onion Skin | 480 × 300 |
| Timeline | 800 × 400 |
| Tool Properties | 260 × 450 |
| Top | 300 × 400 |
| Camera, Drawing, Node View, etc. | 0 × 0 (no minimum) |
### 3. Menu Registration
Set menus on views using `TULayoutView::setMenu`:
```cpp
TULayoutView::setMenu(
layoutView,
actionManager, // AC_Manager*
"MENU_ID", // Menu identifier
MenuType::Primary // 0 = Primary, 1 = Secondary
);
```
## TULayoutViewHolder Usage
The `TULayoutViewHolder` is a QWidget container that can hold 1-2 TULayoutView instances with an optional vertical splitter.
### How Views Get Into TULayoutViewHolder
When you call `TULayoutManager::addArea` or `TULayoutManager::raiseArea`, the system:
1. Creates a `TULayoutFrame` if needed
2. Creates a `TULayoutViewHolder` within the frame
3. Calls `TULayoutViewHolder::addView(view, splitterRatio)` to add your view
4. The view's `getWidget()` method is called to get the actual QWidget
5. The widget is reparented and added to the holder's internal layout
### TULayoutViewHolder::addView
```cpp
bool TULayoutViewHolder::addView(
TULayoutView* view, // View to add
double splitterRatio // Ratio for splitter (default 0.5)
);
```
**Returns**: `true` if added successfully, `false` if holder is full (max 2 views)
### Internal Structure (from ToonBoomLayout.dll)
```
TULayoutViewHolder (sizeof = 0x70 = 112 bytes)
├── QWidget base class (0x00-0x27)
├── std::vector<TULayoutView*> m_views (+0x28, 24 bytes)
├── double m_savedSplitterRatio (+0x40, default 0.5)
├── UI_Splitter* m_splitter (+0x48, vertical orientation)
├── WID_VBoxLayout* m_leftLayout (+0x50)
├── WID_VBoxLayout* m_rightLayout (+0x58)
├── QFrame* m_leftFrame (+0x60)
└── QFrame* m_rightFrame (+0x68)
```
### Behavior
- **0 views**: Splitter hidden, empty container
- **1 view**: View widget added directly to main layout, splitter hidden
- **2 views**: First view in left frame, second in right frame, splitter visible
## Important Notes
1. **Direct Subclassing**: Subclass `TULayoutView` directly - no need for complex multiple inheritance.
2. **widget() Returns this**: The `widget()` method must return `this` (cast to QWidget*) because the calling code treats it as `TULayoutView*`.
3. **getWidget() Returns Content**: The `getWidget()` method returns the actual displayable QWidget.
4. **TULayoutManager Access**: Get the TULayoutManager via `PLUG_Services::getLayoutManager()`.
5. **Memory Management**: Views registered with `addArea` are managed by the layout system. Don't delete them manually.
## Database Locations (HarmonyPremium.exe)
- **TULayoutManager::addArea import**: `0x140b22668`
- **TULayoutManager::raiseArea import**: `0x140b22a18`
- **View creation function (example)**: `0x1400375C0` (main session init)
## Database Locations (ToonBoomLayout.dll)
- **TULayoutView vtable**: `0x180056f38`
- **TULayoutView constructor**: `0x18002fc80`
- **TULayoutViewHolder constructor**: `0x180031150`
- **TULayoutViewHolder::addView**: `0x180031480`
- **TULayoutViewHolder::removeView**: `0x180031620`
- **TULayoutViewHolder::nbViews**: `0x180031610`
- **TULayoutViewHolder::splitterRatio**: `0x180031890`
- **TULayoutViewHolder::updateWidgets**: `0x1800319b0`

View File

@ -0,0 +1,335 @@
# ToonBoomPlugInManager.dll - PLUG_Services Class Analysis
This document contains reverse engineering analysis of the `PLUG_Services` class hierarchy from `ToonBoomPlugInManager.dll` used in Toon Boom Harmony Premium and Storyboard Pro.
## Overview
The plugin services system in Toon Boom uses a singleton pattern to provide access to various application services. The main classes are:
- **PLUG_Services** - Static class providing access to service interfaces
- **PLUG_ServicesPrivate** - Extended static functionality with setters
- **PLUG_ServicesPrivateImpl** - (alias name, same as PLUG_ManagerImpl)
- **PLUG_ManagerImpl** - Main singleton implementation containing all service pointers
- **PLUG_Manager** - Base class for the manager
## Architecture
```
PLUG_Services (static class)
│ getters return from
┌──────────────────────────────────────────────┐
│ PLUG_ManagerImpl (singleton) │
│ (inherits: QObject → PLUG_Manager) │
│ │
│ +0x000: vftable (PLUG_ManagerImpl) │
│ +0x010: embedded interface object │
│ +0x188: QCoreApplication* │
│ +0x190: unknown interface │
│ +0x198-0x240: Service interface pointers │
│ +0x248: QString │
│ +0x260: bool flag │
└──────────────────────────────────────────────┘
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
AC_Manager* CM_Services* PLUG_ScriptingInterface*
...
```
## Global Variables
| Address | Name | Type | Description |
|---------|------|------|-------------|
| 0x180016590 | g_PLUG_ManagerImpl_instance | PLUG_ManagerImpl* | Main singleton instance |
| 0x180016570 | g_PLUG_ModuleLibraryInterface | PLUG_ModuleLibraryInterface* | Module library (separate global) |
| 0x180016578 | g_PLUG_SetupModeQueryInterface | PLUG_SetupModeQueryInterface* | Setup mode query interface |
| 0x180016580 | g_PLUG_PlaybackRange | PLUG_PlaybackRange | Static playback range instance |
| 0x180016538 | g_PLUG_InteractiveViewManagerImpl | PLUG_InteractiveViewManagerImpl* | Interactive view manager singleton |
---
## Class: PLUG_Services
### Purpose
Static utility class providing access to all plugin service interfaces. All methods are static.
### Public Static Methods
| Address | Method | Return Type | Offset in Singleton |
|---------|--------|-------------|---------------------|
| 0x180005a50 | getActionManager() | AC_Manager* | +0x1A0 (416) |
| 0x180005a70 | getColorManagerServices() | CM_Services* | +0x218 (536) |
| 0x180005a90 | getCurrentFrameInterface() | SC_CurrentFrameInterface* | +0x1B0 (432) |
| 0x180005ab0 | getDataToolInterface() | SC_CVDataToolInterface* | +0x220 (544) |
| 0x180005ad0 | getDragDropInterface() | PLUG_DragDropInterface* | +0x1F8 (504) |
| 0x180005af0 | getEditionStackInterface() | SC_SceneEditionStackInterface* | +0x1B8 (440) |
| 0x180005b10 | getExpressionScriptingInterface() | AT_ExprScriptEngine* | +0x200 (512)* |
| 0x180005b40 | getHttpAPI() | SC_HttpAPI* | +0x228 (552) |
| 0x180005b60 | getImportEngine() | PLUG_ImportEngine* | +0x1E0 (480) |
| 0x180005b80 | getInteractiveRenderManager() | SC_InteractiveRenderManagerInterface* | +0x198 (408) |
| 0x180005ba0 | getInteractiveViewManager() | PLUG_InteractiveViewManager* | +0x210 (528)** |
| 0x180005bf0 | getKeyStateInterface() | PLUG_KeyStateInterface* | +0x1E8 (488) |
| 0x180005c10 | getLayoutManager() | TULayoutManager* | +0x1F0 (496) |
| 0x180005c30 | getMenuService() | PLUG_MenuService* | +0x1D8 (472) |
| 0x180005c50 | getModuleLibraryInterface() | PLUG_ModuleLibraryInterface* | (separate global) |
| 0x180005c60 | getNetworkViewInterface() | SC_NetworkViewInterface* | +0x240 (576) |
| 0x180005c80 | getOGLRenderPlaybackInterface() | PLUG_OGLRenderPlaybackInterface* | +0x230 (560) |
| 0x180005d00 | getPluginPath(const QString&) | QString | via vtable |
| 0x180005d50 | getPreference() | PLUG_PreferenceUI* | (thunk to PLUG_PreferenceUIImpl::instance) |
| 0x180005d60 | getScriptingInterface() | PLUG_ScriptingInterface* | +0x1C8 (456) |
| 0x180005d80 | getSelection() | SL_Selection* | +0x1A8 (424) |
| 0x180005da0 | getSessionContext() | SC_SessionContext* | +0x1C0 (448) |
| 0x180005dc0 | getToolbarService() | PLUG_ToolbarService* | +0x1D0 (464) |
| 0x180005de0 | getVectorizationInterface() | PLUG_VectorizationInterface* | +0x238 (568) |
| 0x180005e00 | getWidgetFactoryRegistry() | PLUG_WidgetFactoryRegistry* | +0x208 (520) |
\* `getExpressionScriptingInterface()` requires `SC_SessionContext` at +0x1C0 to be non-null
\*\* `getInteractiveViewManager()` lazily creates `PLUG_InteractiveViewManagerImpl` if null
### Implementation Pattern
Most getters follow this pattern:
```cpp
Type* PLUG_Services::getXXX() {
if (g_PLUG_ManagerImpl_instance)
return g_PLUG_ManagerImpl_instance->m_xxx; // at specific offset
return nullptr; // returns 0 if singleton not initialized
}
```
---
## Class: PLUG_ServicesPrivate
### Purpose
Extended static class with additional functionality and setters (typically used internally).
### Public Static Methods
| Address | Method | Return Type | Description |
|---------|--------|-------------|-------------|
| 0x180005cb0 | getPluginBinFilePath(const QString&) | QString | Gets binary file path for plugin |
| 0x180005fa0 | isSetupMode() | bool | Queries setup mode via g_PLUG_SetupModeQueryInterface |
| 0x180007390 | setModuleLibraryInterface(PLUG_ModuleLibraryInterface*) | bool | Sets g_PLUG_ModuleLibraryInterface |
| 0x1800073f0 | setSetupModeQueryInterface(PLUG_SetupModeQueryInterface*) | void | Sets g_PLUG_SetupModeQueryInterface |
| 0x1800072D0 | setColorManagerServices(CM_Services*) | void | Sets +0x218 offset |
| 0x1800072F0 | setDataToolInterface(SC_CVDataToolInterface*) | void | Sets +0x220 offset |
| 0x180007330 | setHttpAPI(SC_HttpAPI*) | void | Sets +0x228 offset |
| 0x1800073B0 | setOGLRenderPlaybackInterface(PLUG_OGLRenderPlaybackInterface*) | void | Sets +0x230 offset |
| 0x180007410 | setVectorizationInterface(PLUG_VectorizationInterface*) | void | Sets +0x238 offset |
| 0x180007420 | setWidgetFactoryRegistry(PLUG_WidgetFactoryRegistry*) | void | Sets +0x208 offset |
---
## Class: PLUG_ManagerImpl
### Purpose
Main singleton implementation that holds all service interface pointers. Inherits from QObject and PLUG_Manager.
### Constructor
**Address**: `0x180004180`
**Signature**:
```cpp
PLUG_ManagerImpl(QObject* parent, UnknownInterface* iface1, UnknownInterface* iface2);
```
### Memory Layout (x64 MSVC)
| Offset | Hex | Size | Type | Member Name | Description |
|--------|-----|------|------|-------------|-------------|
| 0 | 0x000 | 8 | ptr | vftable | PLUG_ManagerImpl vtable |
| 8 | 0x008 | 8 | | (QObject data) | d_ptr from QObject |
| 16 | 0x010 | 8 | ptr | embedded_vftable | Embedded interface vtable |
| 24 | 0x018 | 72 | | (reserved) | Zeroed on construction |
| 96 | 0x060 | 304 | | (reserved block) | memset to 0, size 0x130 |
| 392 | 0x188 | 8 | ptr | m_coreApp | QCoreApplication* instance |
| 400 | 0x190 | 8 | ptr | m_unknownInterface | Constructor param a3 |
| 408 | 0x198 | 8 | ptr | m_interactiveRenderManager | SC_InteractiveRenderManagerInterface* |
| 416 | 0x1A0 | 8 | ptr | m_actionManager | AC_Manager* |
| 424 | 0x1A8 | 8 | ptr | m_selection | SL_Selection* |
| 432 | 0x1B0 | 8 | ptr | m_currentFrameInterface | SC_CurrentFrameInterface* |
| 440 | 0x1B8 | 8 | ptr | m_editionStackInterface | SC_SceneEditionStackInterface* |
| 448 | 0x1C0 | 8 | ptr | m_sessionContext | SC_SessionContext* |
| 456 | 0x1C8 | 8 | ptr | m_scriptingInterface | PLUG_ScriptingInterface* |
| 464 | 0x1D0 | 8 | ptr | m_toolbarService | PLUG_ToolbarService* |
| 472 | 0x1D8 | 8 | ptr | m_menuService | PLUG_MenuService* |
| 480 | 0x1E0 | 8 | ptr | m_importEngine | PLUG_ImportEngine* |
| 488 | 0x1E8 | 8 | ptr | m_keyStateInterface | PLUG_KeyStateInterface* |
| 496 | 0x1F0 | 8 | ptr | m_layoutManager | TULayoutManager* |
| 504 | 0x1F8 | 8 | ptr | m_dragDropInterface | PLUG_DragDropInterface* |
| 512 | 0x200 | 8 | ptr | m_exprScriptEngine | AT_ExprScriptEngine* |
| 520 | 0x208 | 8 | ptr | m_widgetFactoryRegistry | PLUG_WidgetFactoryRegistry* |
| 528 | 0x210 | 8 | ptr | m_interactiveViewManager | PLUG_InteractiveViewManager* |
| 536 | 0x218 | 8 | ptr | m_colorManagerServices | CM_Services* |
| 544 | 0x220 | 8 | ptr | m_dataToolInterface | SC_CVDataToolInterface* |
| 552 | 0x228 | 8 | ptr | m_httpAPI | SC_HttpAPI* |
| 560 | 0x230 | 8 | ptr | m_oglRenderPlaybackInterface | PLUG_OGLRenderPlaybackInterface* |
| 568 | 0x238 | 8 | ptr | m_vectorizationInterface | PLUG_VectorizationInterface* |
| 576 | 0x240 | 8 | ptr | m_networkViewInterface | SC_NetworkViewInterface* |
| 584 | 0x248 | 24 | QString | m_unknownString | QString member |
| 608 | 0x260 | 1 | bool | m_flag | Initialized to true |
**sizeof(PLUG_ManagerImpl) ≈ 0x268 (616 bytes minimum)**
---
## Class: PLUG_Manager
### Purpose
Base class for the manager singleton.
### Constructor
**Address**: `0x180001210`
### Memory Layout
| Offset | Size | Type | Description |
|--------|------|------|-------------|
| 0 | 8 | ptr | vftable (PLUG_Manager) |
---
## Class: PLUG_ScriptingInterface
### Purpose
Interface for scripting functionality. Base class for script execution.
### Constructor
**Address**: `0x180009cd0`
### Memory Layout
| Offset | Size | Type | Description |
|--------|------|------|-------------|
| 0 | 8 | ptr | vftable |
### Nested Struct: Program
Represents a script program to be executed.
**Memory Layout**:
| Offset | Size | Type | Member | Description |
|--------|------|------|--------|-------------|
| 0 | 24 | QString | path | Script path/name |
| 24 | 24 | QString | description | Script description |
| 48 | 24 | QString | content | Script content |
| 72 | 8+ | QDateTime | timestamp | Modification timestamp |
**sizeof(PLUG_ScriptingInterface::Program) ≈ 80+ bytes**
---
## Class: PLUG_InteractiveViewManager / PLUG_InteractiveViewManagerImpl
### Purpose
Manages interactive view delegates for drawing and tool handling.
### Constructor
**Address**: `0x180001ef0`
### Memory Layout
| Offset | Size | Type | Description |
|--------|------|------|-------------|
| 0 | 8 | ptr | vftable |
| 8 | 8 | ptr | m_delegateList (linked list) |
| 16 | 8 | ptr | m_delegate2 |
### Key Methods
| Address | Method | Description |
|---------|--------|-------------|
| 0x180003060 | instance() | Returns singleton from g_PLUG_InteractiveViewManagerImpl |
| 0x180003180 | registerDelegate(PLUG_InteractiveViewDelegate*) | Registers a view delegate |
| 0x180003320 | unregisterDelegate(PLUG_InteractiveViewDelegate*) | Unregisters a view delegate |
| 0x1800030a0 | isDelegateTypeRegistered(PLUG_InteractiveViewDelegate*) | Checks if delegate type exists |
| 0x180003070 | invalidateAllViews(int) | Invalidates all views |
| 0x180003080 | invalidateTimeline() | Invalidates timeline view |
| 0x180002e20 | handleMouseDown(...) | Handles mouse down events |
| 0x180002ee0 | handleMouseMove(...) | Handles mouse move events |
| 0x180002fa0 | handleMouseUp(...) | Handles mouse up events |
| 0x180002d60 | handleGetCursor(...) | Gets cursor for position |
---
## Class: PLUG_PreferenceUI / PLUG_PreferenceUIImpl
### Purpose
Manages preference panels and UI customization.
### Singleton Access
**Address**: `0x180007a20` (PLUG_PreferenceUIImpl::instance)
Uses thread-local storage for singleton initialization with `Init_thread_header`/`Init_thread_footer` pattern.
### Key Methods
| Address | Method | Description |
|---------|--------|-------------|
| 0x180007970 | addCustomizer(const PLUG_PreferenceUICustomizerInterface*) | Adds preference customizer |
| 0x180007aa0 | onCreateColorPreferencePanel(eAppContext, QWidget*) | Creates color pref panel |
| 0x180007b20 | onCreatePreferencePanel(eAppContext, QWidget*) | Creates general pref panel |
---
## Service Interface Types Reference
### Action & Menu System
- **AC_Manager** - Action/command manager
- **PLUG_MenuService** - Menu service interface
- **PLUG_ToolbarService** - Toolbar service interface
### Scene & Session
- **SC_SessionContext** - Current session context
- **SC_CurrentFrameInterface** - Current frame access
- **SC_SceneEditionStackInterface** - Scene edition stack
- **SC_NetworkViewInterface** - Network view interface
- **SC_HttpAPI** - HTTP API for server communication
### Drawing & Rendering
- **SC_InteractiveRenderManagerInterface** - Render management
- **SC_CVDataToolInterface** - Drawing data tool interface
- **CM_Services** - Color manager services
- **PLUG_VectorizationInterface** - Vectorization tools
- **PLUG_OGLRenderPlaybackInterface** - OpenGL render playback
### Selection & Layout
- **SL_Selection** - Selection management
- **TULayoutManager** - Layout system manager
- **PLUG_InteractiveViewManager** - Interactive view management
### Scripting
- **PLUG_ScriptingInterface** - Script execution interface
- **AT_ExprScriptEngine** - Expression script engine
### Import/Export
- **PLUG_ImportEngine** - Import functionality
- **PLUG_ModuleLibraryInterface** - Module library access
### Input & UI
- **PLUG_KeyStateInterface** - Keyboard state tracking
- **PLUG_DragDropInterface** - Drag and drop handling
- **PLUG_WidgetFactoryRegistry** - Widget factory registration
- **PLUG_PreferenceUI** - Preference panels
---
## Environment Variables
- **TOONBOOM_PLUGINPATH** - Overrides plugin search path (checked in `sub_180005380`)
---
## Analysis Methodology
1. **Function List Analysis**: Extracted all functions matching `*PLUG_Services*` pattern
2. **Decompilation**: Analyzed each getter method to determine offsets in singleton
3. **Constructor Analysis**: Traced `PLUG_ManagerImpl` constructor to map memory layout
4. **Cross-Reference Analysis**: Found setters and initialization points via xrefs to globals
5. **Pattern Recognition**: Identified common getter/setter patterns
6. **Global Identification**: Named and documented global singleton variables
## Database File
- **Source**: `RE/ToonBoomPlugInManager.dll.i64`
- **Module Base**: `0x180000000`

56
framework/CMakeLists.txt Normal file
View File

@ -0,0 +1,56 @@
file(GLOB_RECURSE FRAMEWORK_SOURCES CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/*.c"
"${CMAKE_CURRENT_SOURCE_DIR}/*.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/*.cxx"
"${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
)
file(GLOB_RECURSE FRAMEWORK_HOOK_SOURCES CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/hook/*.c"
"${CMAKE_CURRENT_SOURCE_DIR}/hook/*.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/hook/*.cxx"
"${CMAKE_CURRENT_SOURCE_DIR}/hook/*.cpp"
)
file(GLOB_RECURSE FRAMEWORK_HEADERS CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/*.hpp"
)
list(FILTER FRAMEWORK_SOURCES EXCLUDE REGEX "/(out|build|cmake-build-|CMakeFiles)/")
file(COPY "${QT5_ROOT_DIR}/include/QtScript" DESTINATION ${CMAKE_BINARY_DIR}/include)
find_package(minhook CONFIG REQUIRED)
function(link_libs_and_set_properties target_name)
target_compile_features(${target_name} PUBLIC cxx_std_20)
target_include_directories(${target_name} PUBLIC "${CMAKE_BINARY_DIR}/include")
target_link_libraries(${target_name} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/QtScript.lib")
target_link_libraries(${target_name} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/ToonBoomActionManager.lib")
target_link_libraries(${target_name} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/ToonBoomLayout.lib")
target_link_libraries(${target_name} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/ToonBoomPluginManager.lib")
target_include_directories(${target_name} PUBLIC "${QT6_ROOT_DIR}/include")
target_include_directories(${target_name} PUBLIC "${QT6_ROOT_DIR}/include/QtGui")
target_include_directories(${target_name} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/toon_boom")
target_include_directories(${target_name} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/hooks")
target_include_directories(${target_name} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include/internal")
target_link_directories(${target_name} PUBLIC "${QT6_ROOT_DIR}/lib")
target_link_libraries(${target_name} PUBLIC "${QT6_ROOT_DIR}/lib/Qt6Core.lib")
target_link_libraries(${target_name} PUBLIC "${QT6_ROOT_DIR}/lib/Qt6Gui.lib")
target_link_libraries(${target_name} PUBLIC "${QT6_ROOT_DIR}/lib/Qt6Widgets.lib")
target_link_libraries(${target_name} PUBLIC "${QT6_ROOT_DIR}/lib/Qt6Core5Compat.lib")
target_compile_options(${target_name} PUBLIC "/EHsc")
target_link_libraries(${target_name} PUBLIC minhook::minhook)
set_target_properties(${target_name} PROPERTIES
AUTOMOC ON
AUTOUIC ON
AUTORCC ON
)
endfunction()
add_library(libtoonboom_objs OBJECT ${FRAMEWORK_SOURCES} ${FRAMEWORK_HEADERS} ${FRAMEWORK_HOOK_SOURCES})
set_property(TARGET libtoonboom_objs PROPERTY POSITION_INDEPENDENT_CODE ON)
add_library(libtoonboom_static STATIC $<TARGET_OBJECTS:libtoonboom_objs>)
add_library(libtoonboom SHARED $<TARGET_OBJECTS:libtoonboom_objs>)
link_libs_and_set_properties(libtoonboom_objs)
link_libs_and_set_properties(libtoonboom_static)
link_libs_and_set_properties(libtoonboom)

View File

@ -0,0 +1,199 @@
#include "harmony_signatures.hpp"
#include "sigscan.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>
namespace toon_boom_module::harmony {
namespace {
std::size_t count_forward(const std::byte* p, const std::byte* end, std::uint8_t value) {
std::size_t n = 0;
while (p < end && static_cast<std::uint8_t>(*p) == value) {
++n;
++p;
}
return n;
}
std::size_t count_backward(const std::byte* begin, const std::byte* p, std::uint8_t value) {
std::size_t n = 0;
while (p > begin && static_cast<std::uint8_t>(p[-1]) == value) {
++n;
--p;
}
return n;
}
bool looks_like_function_boundary(const std::byte* text_begin,
std::size_t text_size,
const std::byte* match_addr,
std::size_t pattern_size) {
const auto* text_end = text_begin + text_size;
if (match_addr < text_begin || match_addr + pattern_size > text_end) return false;
// For HarmonyPremium's SCR_ScriptRuntime_getEngine thunk, IDA shows:
// 48 8B 01 48 8B 40 28 C3 CC CC CC ...
// So: require a run of int3 padding immediately after the ret.
constexpr std::size_t kMinCcAfter = 4;
const auto* after = match_addr + pattern_size;
if (after >= text_end) return false;
const auto cc_after = count_forward(after, text_end, 0xCC);
if (cc_after < kMinCcAfter) return false;
// Optional: also prefer that the match is preceded by at least one 0xCC,
// unless it happens to be at the start of the section.
if (match_addr != text_begin) {
const auto cc_before = count_backward(text_begin, match_addr, 0xCC);
if (cc_before == 0) return false;
}
return true;
}
struct FunctionRange {
std::uintptr_t begin{};
std::uintptr_t end{};
};
std::optional<FunctionRange> function_range_from_unwind(HMODULE target_module, std::uintptr_t addr) {
if (!target_module) return std::nullopt;
DWORD64 image_base = 0;
const auto* rf = ::RtlLookupFunctionEntry(static_cast<DWORD64>(addr), &image_base, nullptr);
if (!rf || image_base == 0) return std::nullopt;
// BeginAddress/EndAddress are RVAs from image_base.
const auto begin = static_cast<std::uintptr_t>(image_base + rf->BeginAddress);
const auto end = static_cast<std::uintptr_t>(image_base + rf->EndAddress);
if (begin >= end) return std::nullopt;
return FunctionRange{begin, end};
}
} // namespace
std::optional<std::uintptr_t> find_SCR_ScriptRuntime_getEngine(HMODULE target_module) {
// Exact bytes from IDA at HarmonyPremium.exe:0x14082BCD0:
// 48 8B 01 48 8B 40 28 C3
constexpr std::string_view kPattern = "48 8B 01 48 8B 40 28 C3";
auto text = toon_boom_module::sigscan::get_pe_section(target_module, ".text");
if (!text) return std::nullopt;
const auto pat = toon_boom_module::sigscan::parse_ida_pattern(kPattern);
auto matches = toon_boom_module::sigscan::find_all(*text, pat);
if (matches.empty()) return std::nullopt;
// Filter for plausible function boundaries to reduce collisions with other
// identical byte sequences embedded in the middle of code.
std::vector<const std::byte*> filtered;
filtered.reserve(matches.size());
for (const auto* m : matches) {
if (looks_like_function_boundary(text->begin, text->size, m, pat.bytes.size())) {
filtered.push_back(m);
}
}
if (filtered.size() != 1) return std::nullopt;
return reinterpret_cast<std::uintptr_t>(filtered[0]);
}
std::optional<std::uintptr_t> find_SCR_ScriptManager_ctor(HMODULE target_module) {
// This is a mid-function signature extracted from HarmonyPremium.exe around:
// QString("___scriptManager___"); defineGlobalQObject(...)
// QString("include"); defineGlobalFunction(QS_include)
// QString("require"); defineGlobalFunction(QS_require)
//
// RIP-relative displacements and call targets are wildcarded.
//
// Source bytes were pulled from IDA around 0x14081FEE0.
constexpr std::string_view kPattern =
"48 8B 18 "
"48 8D 15 ?? ?? ?? ?? "
"48 8D 4C 24 30 "
"FF 15 ?? ?? ?? ?? "
"90 "
"4C 8B C6 "
"48 8D 54 24 30 "
"48 8B CB "
"E8 ?? ?? ?? ?? "
"90 "
"48 8D 4C 24 30 "
"FF 15 ?? ?? ?? ?? "
"48 8B 46 20 "
"48 8B 18 "
"48 8D 15 ?? ?? ?? ?? "
"48 8D 4C 24 30 "
"FF 15 ?? ?? ?? ?? "
"90 "
"4C 8D 05 ?? ?? ?? ?? "
"48 8D 54 24 30 "
"48 8B CB "
"E8 ?? ?? ?? ?? "
"90 "
"48 8D 4C 24 30 "
"FF 15 ?? ?? ?? ?? "
"48 8B 46 20 "
"48 8B 18 "
"48 8D 15 ?? ?? ?? ?? "
"48 8D 4C 24 30 "
"FF 15 ?? ?? ?? ?? "
"90 "
"4C 8D 05 ?? ?? ?? ?? "
"48 8D 54 24 30 "
"48 8B CB "
"E8 ?? ?? ?? ?? "
"90 "
"48 8D 4C 24 30 "
"FF 15 ?? ?? ?? ??";
auto text = toon_boom_module::sigscan::get_pe_section(target_module, ".text");
if (!text) return std::nullopt;
const auto pat = toon_boom_module::sigscan::parse_ida_pattern(kPattern);
auto hits = toon_boom_module::sigscan::find_all(*text, pat);
if (hits.empty()) return std::nullopt;
// Convert each hit to its containing function start via unwind info, and
// keep only plausible ctor-sized functions (~0x280 in the analyzed build).
constexpr std::size_t kMinSize = 0x200;
constexpr std::size_t kMaxSize = 0x400;
std::vector<std::uintptr_t> candidates;
candidates.reserve(hits.size());
for (const auto* hit : hits) {
const auto hit_addr = reinterpret_cast<std::uintptr_t>(hit);
auto fr = function_range_from_unwind(target_module, hit_addr);
if (!fr) continue;
const auto size = static_cast<std::size_t>(fr->end - fr->begin);
if (size < kMinSize || size > kMaxSize) continue;
// Ensure the function is inside .text.
const auto text_begin = reinterpret_cast<std::uintptr_t>(text->begin);
const auto text_end = text_begin + text->size;
if (fr->begin < text_begin || fr->end > text_end) continue;
candidates.push_back(fr->begin);
}
if (candidates.empty()) return std::nullopt;
std::sort(candidates.begin(), candidates.end());
candidates.erase(std::unique(candidates.begin(), candidates.end()), candidates.end());
if (candidates.size() != 1) return std::nullopt;
return candidates[0];
}
} // namespace toon_boom_module::harmony

82
framework/hook/hook.cpp Normal file
View File

@ -0,0 +1,82 @@
#include "../include/hooks/toon_boom_hooks.hpp"
#include "../include/internal/harmony_signatures.hpp"
#include <iostream>
#include <vector>
QScriptEngine *global_engine_ptr = NULL;
bool is_first_load = true;
SCR_ScriptManager_ctor_t SCR_ScriptManager_ctor_original_ptr = NULL;
std::vector<ScriptEngine_hook_t> script_engine_hooks;
void *SCR_ScriptManager_ctor_hook(void *_this, void *_engine, void *_parent) {
std::cout << "SCR_ScriptManager_ctor_hook" << std::endl;
void *result = SCR_ScriptManager_ctor_original_ptr(_this, _engine, _parent);
HMODULE target_module = GetModuleHandle(NULL);
std::optional<std::uintptr_t> SCR_ScripRuntime_getEngine_original =
toon_boom_module::harmony::find_SCR_ScriptRuntime_getEngine(
target_module);
if (SCR_ScripRuntime_getEngine_original == std::nullopt) {
std::cerr << "Failed to find SCR_ScriptRuntime_getEngine" << std::endl;
return result;
}
auto SCR_ScripRuntime_getEngine_original_ptr = reinterpret_cast<SCR_ScriptRuntime_getEngine_t>(SCR_ScripRuntime_getEngine_original.value());
void* mgr_data = *reinterpret_cast<void**>(reinterpret_cast<std::byte*>(_this) + 0x20);
if (!mgr_data) {
std::cerr << "SCR_ScriptManager data pointer was null" << std::endl;
return result;
}
void* runtime_handle = *reinterpret_cast<void**>(mgr_data);
if (!runtime_handle) {
std::cerr << "SCR_ScriptManager runtime handle was null" << std::endl;
return result;
}
QScriptEngine* engine = SCR_ScripRuntime_getEngine_original_ptr(runtime_handle);
if (!engine) {
std::cerr << "SCR_ScriptRuntime_getEngine returned null" << std::endl;
return result;
}
global_engine_ptr = engine;
for(auto hook : script_engine_hooks) {
hook(engine);
}
return result;
}
void Add_ScriptEngine_hook(ScriptEngine_hook_t hook) {
script_engine_hooks.push_back(hook);
}
BOOL hookInit() {
if(!is_first_load) {
return TRUE;
}
if(MH_Initialize() != MH_OK) {
std::cerr << "Failed to initialize MinHook" << std::endl;
return FALSE;
}
auto scr_ScriptManager_ctor_ptr = toon_boom_module::harmony::find_SCR_ScriptManager_ctor(GetModuleHandle(NULL));
if(scr_ScriptManager_ctor_ptr == std::nullopt) {
std::cerr << "Failed to find SCR_ScriptManager_ctor" << std::endl;
return FALSE;
}
auto SCR_ScriptManager_ctor_original_ptr_val = reinterpret_cast<SCR_ScriptManager_ctor_t>(scr_ScriptManager_ctor_ptr.value());
MH_STATUS status = MH_CreateHook(
reinterpret_cast<LPVOID>(SCR_ScriptManager_ctor_original_ptr_val),
reinterpret_cast<LPVOID>(&SCR_ScriptManager_ctor_hook),
reinterpret_cast<LPVOID *>(&SCR_ScriptManager_ctor_original_ptr));
if(status != MH_OK) {
std::cerr << "Failed to create hook for SCR_ScriptManager_ctor" << std::endl;
return FALSE;
}
status = MH_EnableHook(MH_ALL_HOOKS);
if(status != MH_OK) {
std::cerr << "Failed to enable hooks" << std::endl;
MH_RemoveHook(reinterpret_cast<LPVOID>(SCR_ScriptManager_ctor_original_ptr_val));
MH_Uninitialize();
return FALSE;
}
std::cout << "Hooks initialized and enabled" << std::endl;
is_first_load = false;
return TRUE;
}

130
framework/hook/sigscan.cpp Normal file
View File

@ -0,0 +1,130 @@
#include "../include/internal/sigscan.hpp"
#include <algorithm>
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <stdexcept>
namespace toon_boom_module::sigscan {
namespace {
bool is_hex_digit(char c) {
return std::isxdigit(static_cast<unsigned char>(c)) != 0;
}
std::uint8_t hex_byte_from_2chars(char hi, char lo) {
auto nybble = [](char c) -> std::uint8_t {
if (c >= '0' && c <= '9') return static_cast<std::uint8_t>(c - '0');
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
if (c >= 'a' && c <= 'f') return static_cast<std::uint8_t>(10 + (c - 'a'));
throw std::invalid_argument("invalid hex digit");
};
return static_cast<std::uint8_t>((nybble(hi) << 4) | nybble(lo));
}
std::vector<std::string_view> split_ws(std::string_view s) {
std::vector<std::string_view> out;
std::size_t i = 0;
while (i < s.size()) {
while (i < s.size() && std::isspace(static_cast<unsigned char>(s[i]))) ++i;
if (i >= s.size()) break;
std::size_t j = i;
while (j < s.size() && !std::isspace(static_cast<unsigned char>(s[j]))) ++j;
out.emplace_back(s.substr(i, j - i));
i = j;
}
return out;
}
} // namespace
Pattern parse_ida_pattern(std::string_view ida_pattern) {
Pattern p;
auto toks = split_ws(ida_pattern);
p.bytes.reserve(toks.size());
p.mask.reserve(toks.size());
for (auto tok : toks) {
if (tok == "?" || tok == "??") {
p.bytes.push_back(0);
p.mask.push_back(false);
continue;
}
if (tok.size() != 2 || !is_hex_digit(tok[0]) || !is_hex_digit(tok[1])) {
throw std::invalid_argument("invalid IDA pattern token: expected 2 hex chars or ??");
}
p.bytes.push_back(hex_byte_from_2chars(tok[0], tok[1]));
p.mask.push_back(true);
}
if (p.bytes.empty()) {
throw std::invalid_argument("empty pattern");
}
return p;
}
std::optional<SectionView> get_pe_section(HMODULE module, std::string_view section_name) {
if (!module) return std::nullopt;
if (section_name.empty() || section_name.size() > 8) return std::nullopt;
const auto base = reinterpret_cast<const std::byte*>(module);
const auto* dos = reinterpret_cast<const IMAGE_DOS_HEADER*>(base);
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return std::nullopt;
const auto* nt = reinterpret_cast<const IMAGE_NT_HEADERS*>(base + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) return std::nullopt;
const IMAGE_SECTION_HEADER* sec = IMAGE_FIRST_SECTION(nt);
for (unsigned i = 0; i < nt->FileHeader.NumberOfSections; ++i) {
char name_buf[9] = {};
std::memcpy(name_buf, sec[i].Name, 8);
if (section_name == name_buf) {
const auto* begin = base + sec[i].VirtualAddress;
const auto size = static_cast<std::size_t>(sec[i].Misc.VirtualSize);
return SectionView{begin, size};
}
}
return std::nullopt;
}
std::vector<const std::byte*> find_all(SectionView region, const Pattern& pat) {
std::vector<const std::byte*> matches;
if (!region.begin || region.size == 0) return matches;
if (pat.bytes.size() != pat.mask.size()) return matches;
if (pat.bytes.empty()) return matches;
if (region.size < pat.bytes.size()) return matches;
const auto* hay = reinterpret_cast<const std::uint8_t*>(region.begin);
const auto hay_size = region.size;
const auto n = pat.bytes.size();
for (std::size_t i = 0; i + n <= hay_size; ++i) {
bool ok = true;
for (std::size_t j = 0; j < n; ++j) {
if (pat.mask[j] && hay[i + j] != pat.bytes[j]) {
ok = false;
break;
}
}
if (ok) matches.push_back(region.begin + i);
}
return matches;
}
std::optional<const std::byte*> find_unique(SectionView region, const Pattern& pat) {
auto all = find_all(region, pat);
if (all.size() != 1) return std::nullopt;
return all[0];
}
} // namespace toon_boom_module::sigscan

View File

@ -0,0 +1,15 @@
#pragma once
#include <MinHook.h>
#include <QtScript/QScriptEngine>
typedef QScriptEngine* (__stdcall *SCR_ScriptRuntime_getEngine_t)(void*);
typedef void* (__stdcall *SCR_ScriptManager_ctor_t)(void*, void*, void*);
QScriptEngine* SCR_ScriptRuntime_getEngine(void*);
typedef void (__stdcall *ScriptEngine_hook_t)(QScriptEngine*);
__declspec(dllexport) void Add_ScriptEngine_hook(ScriptEngine_hook_t hook);
__declspec(dllexport) BOOL hookInit();

View File

@ -0,0 +1,33 @@
#pragma once
#include <cstdint>
#include <optional>
#include <windows.h>
namespace toon_boom_module::harmony {
// Returns the address of HarmonyPremium's internal helper:
// QScriptEngine* SCR_ScriptRuntime_getEngine(SCR_ScriptRuntime* rt)
//
// This is resolved by scanning the target module's .text section for the exact
// machine-code bytes observed in IDA:
// 48 8B 01 48 8B 40 28 C3
//
// If the pattern is not found uniquely, returns std::nullopt.
std::optional<std::uintptr_t> find_SCR_ScriptRuntime_getEngine(HMODULE target_module);
// Returns the address of HarmonyPremium's SCR_ScriptManager constructor.
//
// Resolution strategy:
// - Scan .text for a unique mid-function sequence that:
// - constructs QString("___scriptManager___") then calls defineGlobalQObject
// - constructs QString("include") then calls defineGlobalFunction(QS_include)
// - constructs QString("require") then calls defineGlobalFunction(QS_require)
// - Convert the match address to the containing function start using x64 unwind
// metadata via RtlLookupFunctionEntry, and sanity-check the function size.
std::optional<std::uintptr_t> find_SCR_ScriptManager_ctor(HMODULE target_module);
} // namespace toon_boom_module::harmony

View File

@ -0,0 +1,40 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <optional>
#include <span>
#include <string_view>
#include <vector>
#include <windows.h>
namespace toon_boom_module::sigscan {
struct Pattern {
std::vector<std::uint8_t> bytes; // pattern bytes (wildcard bytes may be any value)
std::vector<bool> mask; // true = match byte, false = wildcard
};
// IDA-style pattern string parser. Examples:
// - "48 8B 01 48 8B 40 28 C3"
// - "48 8B ?? ?? 89"
Pattern parse_ida_pattern(std::string_view ida_pattern);
struct SectionView {
const std::byte* begin{};
std::size_t size{};
};
// Reads a PE section by name (e.g. ".text") from a loaded module.
std::optional<SectionView> get_pe_section(HMODULE module, std::string_view section_name);
// Returns all matches in the provided memory region.
std::vector<const std::byte*> find_all(SectionView region, const Pattern& pat);
// Returns the single match or std::nullopt (0 or >1 matches).
std::optional<const std::byte*> find_unique(SectionView region, const Pattern& pat);
} // namespace toon_boom_module::sigscan

View File

@ -0,0 +1,453 @@
/**
* @file PLUG_Services.hpp
* @brief Reconstructed header for Toon Boom PLUG_Services class hierarchy
*
* This header was reverse-engineered from ToonBoomPlugInManager.dll
* IDA Database: RE/ToonBoomPlugInManager.dll.i64
*
* @note All offsets and structures derived from decompilation analysis.
* This is NOT official Toon Boom code.
*/
#pragma once
#include "./toon_boom_layout.hpp"
#include <QtCore/QString>
#include <QtCore/QDateTime>
#include <QtCore/QObject>
#include <QtWidgets/QWidget>
// Forward declarations of service interfaces
struct AT_ExprScriptEngine;
struct CM_Services;
struct PLUG_DragDropInterface;
struct PLUG_ImportEngine;
struct PLUG_InteractiveViewManager;
struct PLUG_KeyStateInterface;
struct PLUG_MenuService;
struct PLUG_ModuleLibraryInterface;
struct PLUG_OGLRenderPlaybackInterface;
struct PLUG_PlaybackRange;
struct PLUG_PreferenceUI;
struct PLUG_ScriptingInterface;
struct PLUG_SetupModeQueryInterface;
struct PLUG_ToolbarService;
struct PLUG_VectorizationInterface;
struct PLUG_WidgetFactoryRegistry;
struct SC_CurrentFrameInterface;
struct SC_CVDataToolInterface;
struct SC_HttpAPI;
struct SC_InteractiveRenderManagerInterface;
struct SC_NetworkViewInterface;
struct SC_SceneEditionStackInterface;
struct SC_SessionContext;
struct SL_Selection;
/**
* @class PLUG_Services
* @brief Static class providing access to all Toon Boom plugin service interfaces
*
* This class provides static accessor methods to retrieve various service
* interfaces from the global PLUG_ManagerImpl singleton. All methods are static.
*
* The singleton is initialized during application startup and must be valid
* before calling any getters (except getModuleLibraryInterface which uses
* a separate global).
*/
class PLUG_Services {
public:
// Delete constructors - this is a static-only class
PLUG_Services() = delete;
PLUG_Services(const PLUG_Services&) = delete;
PLUG_Services& operator=(const PLUG_Services&) = delete;
/**
* @brief Get the action/command manager
* @return AC_Manager* or nullptr if singleton not initialized
* @note Offset: +0x1A0 (416) in PLUG_ManagerImpl
*/
static AC_Manager* getActionManager();
/**
* @brief Get color manager services
* @return CM_Services* or nullptr if singleton not initialized
* @note Offset: +0x218 (536) in PLUG_ManagerImpl
*/
static CM_Services* getColorManagerServices();
/**
* @brief Get current frame interface
* @return SC_CurrentFrameInterface* or nullptr if singleton not initialized
* @note Offset: +0x1B0 (432) in PLUG_ManagerImpl
*/
static SC_CurrentFrameInterface* getCurrentFrameInterface();
/**
* @brief Get data tool interface
* @return SC_CVDataToolInterface* or nullptr if singleton not initialized
* @note Offset: +0x220 (544) in PLUG_ManagerImpl
*/
static SC_CVDataToolInterface* getDataToolInterface();
/**
* @brief Get drag and drop interface
* @return PLUG_DragDropInterface* or nullptr if singleton not initialized
* @note Offset: +0x1F8 (504) in PLUG_ManagerImpl
*/
static PLUG_DragDropInterface* getDragDropInterface();
/**
* @brief Get scene edition stack interface
* @return SC_SceneEditionStackInterface* or nullptr if singleton not initialized
* @note Offset: +0x1B8 (440) in PLUG_ManagerImpl
*/
static SC_SceneEditionStackInterface* getEditionStackInterface();
/**
* @brief Get expression scripting engine
* @return AT_ExprScriptEngine* or nullptr if singleton not initialized or no session context
* @note Requires SC_SessionContext (+0x1C0) to be non-null
* @note Offset: +0x200 (512) in PLUG_ManagerImpl
*/
static AT_ExprScriptEngine* getExpressionScriptingInterface();
/**
* @brief Get HTTP API interface
* @return SC_HttpAPI* or nullptr if singleton not initialized
* @note Offset: +0x228 (552) in PLUG_ManagerImpl
*/
static SC_HttpAPI* getHttpAPI();
/**
* @brief Get import engine
* @return PLUG_ImportEngine* or nullptr if singleton not initialized
* @note Offset: +0x1E0 (480) in PLUG_ManagerImpl
*/
static PLUG_ImportEngine* getImportEngine();
/**
* @brief Get interactive render manager
* @return SC_InteractiveRenderManagerInterface* or nullptr if singleton not initialized
* @note Offset: +0x198 (408) in PLUG_ManagerImpl
*/
static SC_InteractiveRenderManagerInterface* getInteractiveRenderManager();
/**
* @brief Get interactive view manager (lazily created)
* @return PLUG_InteractiveViewManager* - creates new instance if null
* @note Lazily creates PLUG_InteractiveViewManagerImpl if not set
* @note Offset: +0x210 (528) in PLUG_ManagerImpl
*/
static PLUG_InteractiveViewManager* getInteractiveViewManager();
/**
* @brief Get keyboard state interface
* @return PLUG_KeyStateInterface* or nullptr if singleton not initialized
* @note Offset: +0x1E8 (488) in PLUG_ManagerImpl
*/
static PLUG_KeyStateInterface* getKeyStateInterface();
/**
* @brief Get layout manager
* @return TULayoutManager* or nullptr if singleton not initialized
* @note Offset: +0x1F0 (496) in PLUG_ManagerImpl
*/
static TULayoutManager* getLayoutManager();
/**
* @brief Get menu service
* @return PLUG_MenuService* or nullptr if singleton not initialized
* @note Offset: +0x1D8 (472) in PLUG_ManagerImpl
*/
static PLUG_MenuService* getMenuService();
/**
* @brief Get module library interface
* @return PLUG_ModuleLibraryInterface* from separate global (not singleton)
* @note Uses g_PLUG_ModuleLibraryInterface global, not main singleton
*/
static PLUG_ModuleLibraryInterface* getModuleLibraryInterface();
/**
* @brief Get network view interface
* @return SC_NetworkViewInterface* or nullptr if singleton not initialized
* @note Offset: +0x240 (576) in PLUG_ManagerImpl
*/
static SC_NetworkViewInterface* getNetworkViewInterface();
/**
* @brief Get OpenGL render playback interface
* @return PLUG_OGLRenderPlaybackInterface* or nullptr if singleton not initialized
* @note Offset: +0x230 (560) in PLUG_ManagerImpl
*/
static PLUG_OGLRenderPlaybackInterface* getOGLRenderPlaybackInterface();
/**
* @brief Get plugin path
* @param relativePath Relative path within plugin directory
* @return QString with full path
* @note Uses virtual call through embedded interface at +0x10
*/
static QString getPluginPath(const QString& relativePath);
/**
* @brief Get preference UI singleton
* @return PLUG_PreferenceUI* - uses separate singleton pattern with TLS
* @note This is a thunk to PLUG_PreferenceUIImpl::instance()
*/
static PLUG_PreferenceUI* getPreference();
/**
* @brief Get scripting interface
* @return PLUG_ScriptingInterface* or nullptr if singleton not initialized
* @note Offset: +0x1C8 (456) in PLUG_ManagerImpl
*/
static PLUG_ScriptingInterface* getScriptingInterface();
/**
* @brief Get selection manager
* @return SL_Selection* or nullptr if singleton not initialized
* @note Offset: +0x1A8 (424) in PLUG_ManagerImpl
*/
static SL_Selection* getSelection();
/**
* @brief Get session context
* @return SC_SessionContext* or nullptr if singleton not initialized
* @note Offset: +0x1C0 (448) in PLUG_ManagerImpl
*/
static SC_SessionContext* getSessionContext();
/**
* @brief Get toolbar service
* @return PLUG_ToolbarService* or nullptr if singleton not initialized
* @note Offset: +0x1D0 (464) in PLUG_ManagerImpl
*/
static PLUG_ToolbarService* getToolbarService();
/**
* @brief Get vectorization interface
* @return PLUG_VectorizationInterface* or nullptr if singleton not initialized
* @note Offset: +0x238 (568) in PLUG_ManagerImpl
*/
static PLUG_VectorizationInterface* getVectorizationInterface();
/**
* @brief Get widget factory registry
* @return PLUG_WidgetFactoryRegistry* or nullptr if singleton not initialized
* @note Offset: +0x208 (520) in PLUG_ManagerImpl
*/
static PLUG_WidgetFactoryRegistry* getWidgetFactoryRegistry();
};
/**
* @class PLUG_ServicesPrivate
* @brief Extended static class with additional private functionality
*
* Provides setters and additional query methods not exposed through PLUG_Services.
*/
class PLUG_ServicesPrivate {
public:
PLUG_ServicesPrivate() = delete;
PLUG_ServicesPrivate(const PLUG_ServicesPrivate&) = delete;
PLUG_ServicesPrivate& operator=(const PLUG_ServicesPrivate&) = delete;
/**
* @brief Get plugin binary file path
* @param relativePath Relative path within plugin binary directory
* @return QString with full path
*/
static QString getPluginBinFilePath(const QString& relativePath);
/**
* @brief Get playback range (const)
* @return const PLUG_PlaybackRange* static instance
*/
static const PLUG_PlaybackRange* getPlaybackRange();
/**
* @brief Check if application is in setup mode
* @return true if setup mode is active
* @note Queries g_PLUG_SetupModeQueryInterface via virtual call
*/
static bool isSetupMode();
/**
* @brief Set the module library interface global
* @param iface Pointer to module library interface
* @return true always
*/
static bool setModuleLibraryInterface(PLUG_ModuleLibraryInterface* iface);
/**
* @brief Set the setup mode query interface global
* @param iface Pointer to setup mode query interface
*/
static void setSetupModeQueryInterface(PLUG_SetupModeQueryInterface* iface);
};
/**
* @class PLUG_ScriptingInterface
* @brief Interface for script execution functionality
*
* Abstract base class providing scripting capabilities for plugins.
*/
class PLUG_ScriptingInterface {
public:
PLUG_ScriptingInterface();
virtual ~PLUG_ScriptingInterface();
/**
* @struct Program
* @brief Represents a script program
*/
struct Program {
QString path; ///< Script path/name (offset +0x00)
QString description; ///< Script description (offset +0x18)
QString content; ///< Script content (offset +0x30)
QDateTime timestamp; ///< Modification timestamp (offset +0x48)
Program();
Program(const QString& path, const QString& description, const QString& content);
Program(const Program& other);
Program(Program&& other);
~Program();
Program& operator=(const Program& other);
Program& operator=(Program&& other);
};
};
/**
* @class PLUG_ScriptingModuleInterface
* @brief Interface for scripting modules that can be registered
*/
class PLUG_ScriptingModuleInterface {
public:
PLUG_ScriptingModuleInterface();
virtual ~PLUG_ScriptingModuleInterface();
};
/**
* @class PLUG_ModuleLibraryInterface
* @brief Interface for module library functionality
*/
class PLUG_ModuleLibraryInterface {
public:
PLUG_ModuleLibraryInterface();
virtual ~PLUG_ModuleLibraryInterface();
};
/**
* @class PLUG_DragDropInterface
* @brief Interface for drag and drop operations
*/
class PLUG_DragDropInterface {
public:
PLUG_DragDropInterface();
virtual ~PLUG_DragDropInterface();
};
/**
* @class PLUG_PreferenceUI
* @brief Abstract interface for preference panel UI
*/
class PLUG_PreferenceUI {
public:
PLUG_PreferenceUI();
virtual ~PLUG_PreferenceUI();
/**
* @brief Add a preference UI customizer
* @param customizer Customizer interface to add
*/
virtual void addCustomizer(const class PLUG_PreferenceUICustomizerInterface* customizer) = 0;
};
/**
* @class PLUG_PreferenceUICustomizerInterface
* @brief Interface for customizing preference panels
*/
class PLUG_PreferenceUICustomizerInterface {
public:
/**
* @enum eAppContext
* @brief Application context for preference panels
*/
enum eAppContext {
// Values to be determined via further RE
};
PLUG_PreferenceUICustomizerInterface();
virtual ~PLUG_PreferenceUICustomizerInterface();
/**
* @brief Called when creating preference panel
* @param context Application context
* @param parent Parent widget
*/
virtual void onCreatePreferencePanel(eAppContext context, QWidget* parent) const;
};
/**
* @class PLUG_InteractiveViewManager
* @brief Abstract base class for interactive view management
*/
class PLUG_InteractiveViewManager {
public:
virtual ~PLUG_InteractiveViewManager();
// Pure virtual methods (to be discovered)
virtual void invalidateAllViews(int flags) = 0;
virtual void invalidateTimeline() = 0;
virtual void registerDelegate(class PLUG_InteractiveViewDelegate* delegate) = 0;
virtual void unregisterDelegate(class PLUG_InteractiveViewDelegate* delegate) = 0;
virtual bool isDelegateTypeRegistered(class PLUG_InteractiveViewDelegate* delegate) = 0;
virtual void releaseAllDelegates() = 0;
virtual void clearAllDrawingSelection() = 0;
protected:
PLUG_InteractiveViewManager();
};
/**
* @class PLUG_Manager
* @brief Base class for the plugin manager singleton
*/
class PLUG_Manager {
public:
PLUG_Manager();
virtual ~PLUG_Manager();
};
// ============================================================================
// Offset Constants (for reference/debugging)
// ============================================================================
namespace PLUG_ManagerImpl_Offsets {
constexpr size_t CoreApp = 0x188; // 392
constexpr size_t UnknownInterface = 0x190; // 400
constexpr size_t InteractiveRenderManager = 0x198; // 408
constexpr size_t ActionManager = 0x1A0; // 416
constexpr size_t Selection = 0x1A8; // 424
constexpr size_t CurrentFrameInterface = 0x1B0; // 432
constexpr size_t EditionStackInterface = 0x1B8; // 440
constexpr size_t SessionContext = 0x1C0; // 448
constexpr size_t ScriptingInterface = 0x1C8; // 456
constexpr size_t ToolbarService = 0x1D0; // 464
constexpr size_t MenuService = 0x1D8; // 472
constexpr size_t ImportEngine = 0x1E0; // 480
constexpr size_t KeyStateInterface = 0x1E8; // 488
constexpr size_t LayoutManager = 0x1F0; // 496
constexpr size_t DragDropInterface = 0x1F8; // 504
constexpr size_t ExprScriptEngine = 0x200; // 512
constexpr size_t WidgetFactoryRegistry = 0x208; // 520
constexpr size_t InteractiveViewManager = 0x210; // 528
constexpr size_t ColorManagerServices = 0x218; // 536
constexpr size_t DataToolInterface = 0x220; // 544
constexpr size_t HttpAPI = 0x228; // 552
constexpr size_t OGLRenderPlaybackInterface = 0x230; // 560
constexpr size_t VectorizationInterface = 0x238; // 568
constexpr size_t NetworkViewInterface = 0x240; // 576
constexpr size_t UnknownString = 0x248; // 584
constexpr size_t Flag = 0x260; // 608
}

File diff suppressed because it is too large Load Diff

5
vcpkg.json Normal file
View File

@ -0,0 +1,5 @@
{
"dependencies": [
"minhook"
]
}