BlueBlur is a modding API designed for the Steam version of Sonic Generations. You can use it to interface with the original C++ types of the game when creating your own DLL mods.
- Ensure your project targets C++17 or newer.
- Add BlueBlur directory to following properties in your project settings:
- C/C++ -> General -> Additional Include Directories
- Linker -> General -> Additional Library Directories
- Include BlueBlur.h in a source file in your project. You should preferably do this in a precompiled header.
Your IDE might show errors when browsing BlueBlur code. However, these are usually false positives, so you should be able to compile without issue.
There currently aren't any samples associated with this project. However, you can check these DLL mods for reference:
- ActualMandM/HedgeDllMods
- blueskythlikesclouds/DllMods
- brianuuu/DllMods
- GenerationsSM64/GenerationsSM64
- HyperBE32/App-Extension-Mods
- LadyLunanova/DllMods
- PTKay/ModUpdates
Refer to these singletons included in the game for a head-start:
Sonic::CApplicationDocument::GetInstance()
Sonic::CGameDocument::GetInstance()
Sonic::CInputState::GetInstance()
Sonic::Player::CPlayerSpeedContext::GetInstance()
Sonic::Player::CSonicClassicContext::GetInstance()
Sonic::Player::CSonicContext::GetInstance()
Sonic::Player::CSonicSpContext::GetInstance()
You should use Sonic::Player::CPlayerSpeedContext
if you want to handle every player regardless of their type. Use type specific singletons for specific players.
- Use
camelCase
for local variables,SNAKE_CASE
for preprocessor macros, andPascalCase
for everything else. BlueBlur-specific types that don't exist in the game should usesnake_case
for better differentiation. - Class names should be prefixed with
C
, e.g.,CSonicContext
. - Struct names should be prefixed with
S
, e.g.,SUpdateInfo
. - Class members should be prefixed with
m_
, e.g.,m_Time
. Do not use this prefix for struct members. - Enum names should be prefixed with
E
, e.g.,ELightType
. - Enum members should start with
e
, followed by the enum name and an underscore, e.g.,eLightType_Point
. - For enum members indicating the count of elements, prefix with
n
, followed by the name, e.g.,nLightType
. - Pointers should be prefixed with
p
, e.g.,pSonicContext
. - Shared pointers should be prefixed with
sp
, e.g.,spDatabase
. - References should be prefixed with
r
, e.g.,rMessage
. - Input function arguments should be prefixed with
in_
, e.g.,in_Name
. - Output function arguments should be prefixed with
out_
, e.g.,out_Value
. - Static class members should be prefixed with
ms_
, e.g.,ms_Instance
. - Static members outside a class should be prefixed with
g_
, e.g.,g_AllocationTracker
. - BlueBlur-specific preprocessor macros should start with
BB_
, e.g.,BB_INSERT_PADDING
. - Hedgehog namespace-specific preprocessor macros should start with
HH_
along with the library's shorthand, e.g.,HH_FND_MSG_MAKE_TYPE
. - Function pointers should be prefixed with
fp
, e.g.,fpCGameObjectConstructor
.
Combine prefixes as necessary, e.g., m_sp
for a shared pointer as a class member or in_r
for a const reference as a function argument.
- Always place curly brackets on a new line.
- Prefer forward declaring types over including their respective headers.
- Use <> includes relative to the project's root directory path.
- Use C++17's nested namespace feature instead of defining multiple namespaces on separate lines.
- Enum classes are prohibited as they were not available when the game was developed.
- Avoid placing function definitions in .h files, instead, implement functions in the header's respective .inl file, similar to a .cpp file.
- Ensure that all class members are declared as public. Even if you suspect that a class member was private in the original code, having it public is more convenient in a modding API.
- Avoid placing multiple class definitions in a single header file unless you have a good reason to do so.
- Keep function pointers or addresses outside functions, define them as global variables in the corresponding .inl file. Mark these global variables as
inline
and never nest them within class definitions. You do not need to use theg_
prefix for function pointers,fp
is sufficient. - Use primitive types defined in
cstdint
instead of using types that come with the language, e.g., useuint32_t
instead ofunsigned int
. Usingfloat
,double
andbool
is okay.
- Always include the corresponding
offsetof
/sizeof
assertions for mapped classes/structs. If you are uncertain about the type's size, you can omit thesizeof
assertion. - Use the exact type name from the game if it's available through RTTI, otherwise, you can look for shared pointers that may reveal the original type name.
- If you are unsure about the name of a class/struct member, use
Field
followed by the hexadecimal byte offset (e.g.,m_Field194
). Avoid names likem_StoresThisThingMaybe
, you can write comments next to the definition for speculations. - If a portion of the byte range is irrelevant to your research or not mapped yet, use the
BB_INSERT_PADDING
macro to align class/struct members correctly. - When the class has a virtual function table, if you don't want to map every function in it, you can map only the virtual destructor.
- The original file locations are likely available in the executable file as assertion file paths. If you cannot find the file path, use your intuition to place the file in a sensible place.