This style is based on the Linux kernel coding style.
Indentations should be 4 spaces.
As we are a heretic movement when creating a Switch statement, they should follow a clear and consistent indentation style where subordinate case blocks are indented and wrapped in braces to clearly delineate their scope.
Each case should be followed by a block wrapped in {}. Fallthroughs are only acceptable if clearly grouped and scoped together using braces:
switch (suffix)
{
case 'G':
case 'g':
{
mem <<= 30;
break;
}
case 'M':
case 'm':
{
mem <<= 20;
break;
}
default:
{
break;
}
}A case may omit braces only when all of the following conditions are met:
- The case contains exactly one statement
- The statement fits on one line
- The statement is immediately followed by
break;orreturn; - No fallthrough occurs
- The statement has no side-effects that depend on scope (e.g. no variable declarations)
- The statement is trivial and self-contained (like a single assignment or function call)
switch (input)
{
case STAMINA: addBonusMultExt(&figure->figures[target].stamina, value); return;
case ARMOR: statistic = &figure->figures[target].armor; break;
default: log_warning("INVALID STATISTIC KEY: %d", key); return;
}switch (input)
{
case ARMOR:
FigureStatistic *s = &figure->figures[target].armor;
break;
case AGILITY:
statistic = &figure->figures[target].agility;
do_something_else();
break;
}Fallthrough without braces or implicit continuation between cases is not permitted, as it reduces clarity and increases the chance of bugs:
switch (input)
{
case 'a':
do_a();
case 'b':
do_b(); // Not clear if fallthrough is intended
break;
}Don't put multiple statements on a single line unless you have something to hide:
if (condition) do_this;
do_something_everytime;Don't put multiple assignments on a single line either. Avoid tricky expressions. Don't leave whitespace at the end of lines.
Coding style is all about readability and maintainability using commonly available tools.
The limit on the length of lines is 120 columns and this is strongly preferred.
Statements longer than 120 columns will be broken into sensible chunks unless exceeding 120 columns significantly increases readability and does not hide information. Descendants are always shorter than the parent and placed substantially to the right.
Acceptable
if (user_is_admin(user) &&
user->permissions.can_edit &&
user->permissions.can_delete &&
!user->is_suspended)
perform_admin_action(user);Rejected
if (user_is_admin(user) && user->permissions.can_edit && user->permissions.can_delete && !user->is_suspended)
perform_admin_action(user);The preferred brace placement follows the Allman style, where opening braces { are placed on a new line for all blocks.
This style puts the brace associated with a control statement on the next line, indented to the same level as the control statement. Statements within the braces are indented to the next level.
For example this is an example for the correct placement for braces.
while (x == y)
{
something();
something_else();
}
final_thing();Use short, meaningful names. C is a Spartan language:
- Use
tmpnotThisVariableIsATemporaryCounter - Global functions should be descriptive:
count_active_users(), notcntusr() - Don’t use Hungarian notation
- Use
iortmpfor simple locals
Avoid growing functions that make variable naming hard.
Functions should be concise, focused, and do one thing well. Ideally, they should be no longer than 72 lines.
- Shorter is better, especially for functions with deep nesting or complex logic.
- If the function is conceptually simple (like a long but flat switch statement), longer length is acceptable.
- Use helper functions to break up complexity and improve readability. Give them clear, descriptive names.
- Avoid having more than 5–10 local variables; exceeding this suggests the function is doing too much.
Exception: When duplicating game logic from Spellforce, function size and complexity rules may be relaxed to preserve correctness. However, this style guide should still be followed where possible without affecting game behavior.
Best practices:
- Separate functions with a blank line.
- Function prototypes should include parameter names for clarity.
int system_is_up(void)
{
return system_state == SYSTEM_RUNNING;
}
EXPORT_SYMBOL(system_is_up);Comments should explain what the code does not how it works. If your code is hard to understand, improve the code itself rather than relying on comments to explain it. However, when writing example mods, comments should be used to explain how the code works, as these serve as educational material for others.
- Avoid over-commenting and inline comments inside functions unless absolutely necessary.
- Prefer placing comments above functions, summarizing what they do and why (if not obvious).
- If you need to explain a specific tricky or non-obvious section, keep it brief and relevant.
Comments should be written in a way that Doxygen can parse for documentation generation — especially for public APIs or important internal functions.
An example of a Doxygen Compatibale Comment.
/**
* function_name - Short description
* @param1: Description of first parameter
* @param2: Description of second parameter
*
* Longer description if needed.
*
* Return: What the function returns
*/Use this format for multi-line comments:
/*
* This is the preferred style for multi-line
* comments in general Linux-style code.
*/- Always comment data structures and variables.
- Use one declaration per line to allow individual comments:
int socket_fd; /* File descriptor for the socket */
int retry_count; /* Number of allowed retry attempts */Avoid:
int socket_fd, retry_count; /* Bad: can't comment both easily */Keep it concise, meaningful, and documented in a format that tooling like Doxygen can understand.
Enums should be named like datatypes with the first letter of each word capitalized, and entries within the enum should be capitalized (using uppercase and underscores to separate words). The name of the enum itself should reflect the type or category it represents.
- Acceptable Enum Name:
SpellFlagKey - Unacceptable Enum Name:
SPELLFLAGKEY
Example:
typedef enum
{
CHECK_SPELLS_BEFORE_CHECK_BATTLE = 0x04,
CHECK_SPELLS_BEFORE_JOB2 = 0x02,
UNFREEZE = 0x1
} SpellFlagKey;In this example, the enum SpellFlagKey is clearly named to reflect its purpose (as a datatype), and the entries are capitalized to distinguish them as constants.
To simplify usage, enums should be defined using typedef to avoid having to declare variables as enum SpellFlagKey key. Instead, the variable can be declared directly as SpellFlagKey key.
Macros should be capitalized entirely (using uppercase and underscores to separate words). Macros can be used for constants, labels, or simple inline code, but avoid using math operations within macros. This is because math operations inside macros can quickly get out of hand and make the macro's performance and effect hard to understand or debug.
Example of a good macro:
#define MAX_SPELLS 100
#define PLAYER_SPEED 10This is straightforward, with clear constant values.
Avoid complex macros involving math operations, as it obfuscates how expensive the macro is and can introduce hard-to-track bugs. For instance:
Avoid:
#define SQUARE(x) (x * x)Instead, consider using a function for calculations, as it is easier to understand, debug, and optimize:
inline int square(int x) {
return x * x;
}Memory management is crucial in ensuring that the game runs efficiently and avoids unnecessary overhead. When writing code, it's important to avoid direct memory allocation as much as possible because it can negatively affect the game's memory state, potentially leading to crashes or instability.
-
Avoid Direct Memory Allocation Direct memory allocation (e.g., using
malloc,calloc, orrealloc) should be avoided unless absolutely necessary. Constantly allocating and deallocating memory at runtime can be inefficient and lead to fragmented memory, which can make the game more prone to errors or slowdowns. -
Prefer Stack Allocation Whenever possible, use stack memory instead of heap memory. The stack is managed automatically, and objects are cleaned up when they go out of scope. This avoids the risk of memory leaks and unnecessary allocation overhead.
Example:
void example_function() { int local_var = 5; // Allocated on the stack // Do something with local_var }
Stack memory is fast and is generally used for short-lived variables. It should be your preferred method for memory allocation whenever possible.
-
Pre-Allocate Memory at Compile Time If dynamic memory allocation is required, try to pre-allocate memory at compile time rather than allocating it during runtime. This ensures that memory is reserved ahead of time, minimizing performance hits from dynamic allocation.
Example (using
staticallocation):#define MAX_ITEMS 100 // Avoid Magic Numbers int item_array[MAX_ITEMS]; // Pre-allocated array
In this example, the array is allocated at compile time, ensuring it is available when the game runs without the need for runtime allocation. If data must persist for the lifetime of the program, consider using static or global variables. These variables are automatically allocated at compile time and persist for the entire execution of the program.
-
When Heap Allocation is Absolutely Necessary If dynamic memory allocation (heap allocation) is required for complex data structures or objects that cannot be pre-allocated, it is crucial to manage the allocated memory carefully. Ensure that every allocation has a corresponding deallocation and that there are no memory leaks.
Example of proper heap allocation:
void* ptr = malloc(sizeof(MyStruct)); if (ptr == NULL) { // Handle memory allocation failure } // Use the allocated memory free(ptr); // Free memory when done
Important: Ensure this does not impact Spellforce and always check for allocation failures and ensure you free allocated memory when it is no longer needed.
- Avoid direct memory allocation as much as possible to ensure stable memory management and prevent fragmentation.
- Prefer stack allocation for short-lived variables and function calls.
- Pre-allocate memory at compile time whenever feasible to reduce runtime overhead.
- Minimize heap allocations and ensure proper memory management by freeing memory when it's no longer in use.
- Inline assembly
Inline assembly should only be used when absolutely necessary specifically when writing hooks for SpellForce functions. In all other cases, prefer writing your logic in C or C++. If inline assembly is required for a hook:
- Keep it minimal
- Ensure it is well-documented
- Keep it self-contained
- Always preserve the calling convention and register state unless explicitly modifying them
This ensures that even low-level code can be maintained by other developers and prevents breaking the game engine’s expected behavior.
static void __declspec(naked) menuload_hook_beta()
{
asm("push %%edi \n\t" // Store EDI (typically points to CAppMenu instance)
"call %P0 \n\t" // Call our hook function (which can also call the original)
"pop %%edi \n\t" // Restore EDI after hook logic is done
"jmp *%1 \n\t" // Jump back to the return address after the original hook point
: : "i"(sf_menu_hook), "o"(g_menu_return_addr));
}- This pattern ensures the original function's flow is resumed properly after the hook.
- Make sure to understand the function prologue/epilogue you’re hooking into, so you don’t inadvertently corrupt the stack or registers.
- Always test hooks thoroughly, as inline assembly bugs are often subtle and hard to diagnose.
- Do not use inline assembly for micro-optimizations. Modern compilers are often better at optimizing plain C/C++.
Before opting to use inline assembly, always evaluate whether the original function's behavior can be replicated in pure C or C++. In many cases, you can avoid the complexity and risks of inline assembly by simply rewriting the function and redirecting execution to your version using a jump or call hook.
This method improves code maintainability but also avoids undefined behavior caused by low-level manipulation of registers or the stack.
Instead of writing inline assembly to hook and manipulate execution flow, you can safely redirect the function to your C++ implementation:
static void initialize_spellrefresh_hook()
{
// State how much memory to overwrite, in this case it is the first 9 bytes of the target function
ASI::MemoryRegion refresh_spell_mreg(ASI::AddrOf(0x329f90), 9);
// Remove Memory Write Protection
ASI::BeginRewrite(refresh_spell_mreg);
// Overwrite the original function with NOPs and a JMP to our replacement
*(unsigned char *)(ASI::AddrOf(0x329f90)) = 0x90; // NOP
*(unsigned char *)(ASI::AddrOf(0x329f91)) = 0x90; // NOP
*(unsigned char *)(ASI::AddrOf(0x329f92)) = 0x90; // NOP
*(unsigned char *)(ASI::AddrOf(0x329f93)) = 0x90; // NOP
*(unsigned char *)(ASI::AddrOf(0x329f94)) = 0xE9; // JMP opcode
*(int *)(ASI::AddrOf(0x329f95)) = (int)(&sf_refresh_hook) - ASI::AddrOf(0x329f99); // Offset to Jump.
// Restore Memory Write Protection
ASI::EndRewrite(refresh_spell_mreg);
}sf_refresh_hookcan now be a clean, pure C or C++ function that replicates or extends the original behavior.- You're avoiding inline assembly entirely, unless strictly required.
- The redirection is transparent to the rest of the system.
Only fall back to inline assembly if replicating the original logic is impractical or impossible due to complexity or required access to specific registers at specific times.