/* betterapi.h - the public API of BetterConsole */
/* https://www.nexusmods.com/starfield/mods/3683 */
/* https://github.com/SomeCrazyGuy/Starfield-Console-Replacer */
/* Author: Seth Royer */
/* License: http://unlicense.org/ */
// You can always reach out to me if you have any questions or need help
// integrating betterapi into your mod. I'm almost always available in the
// Constellation by v2 discord or nexusmods.
// Always use the latest betterapi.h file from github!
// Sometimes small hacks or fixes are put here to resolve
// issues in the published mod between releases. You
// can find the latest version here:
// https://raw.githubusercontent.com/SomeCrazyGuy/Starfield-Console-Replacer/master/betterapi.h
///////////////////////////////////////////////////////////////////////////////
// Table Of Contents
///////////////////////////////////////////////////////////////////////////////
// 1) The Unlicense
// 2) Release History
// 3) About this file
// 4) Quick Start Guide
// 5) Configuring Better API
// 6) What are SFSE and ASI mods?
// 7) The betterapi philosophy
// 8) Thread safety
// 9) What is userdata?
// 10) Typedefs
// 11) Enums
// 12) Callback Types
// 13) Callback API
// 14) Config API
// 15) Hook API
// 16) Graphics API
// 17) LogBuffer API
// 18) C Library API
// 19) Game Console API
// 20) BetterConsole API
// 21) SFSE Minimal Interface
///////////////////////////////////////////////////////////////////////////////
// 1) The Unlicense
///////////////////////////////////////////////////////////////////////////////
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to
///////////////////////////////////////////////////////////////////////////////
// 2) Release History
///////////////////////////////////////////////////////////////////////////////
//
// 1.3.0 and older - no longer supported!
//
// 1.3.1 - 2024-06-09 Starfield v1.2.30
// First version using the new entrypoint "BetterConsoleReceiver"
//
// 1.4.0 - 2024-06-14 Starfield v1.2.32
// No public API changes
//
///////////////////////////////////////////////////////////////////////////////
// 3) About this file
///////////////////////////////////////////////////////////////////////////////
//
// This is the public API for the Starfield mod BetterConsole:
//
//
// This file is a zero dependency header only library in the style of STB
// libraries:
//
// As such it is written in a way that shows the limitations features
// of the C programming language. This is all an excuse to say: "if it looks like
// something is being done the hard or wrong way, don't worry, it is intentional"
//
// BetterConsole itself is mostly written in C with some C++ features sprinkled in
// for flavor, but the public API (betterapi.h) is intended to be plain C99 code
// and include only two standard C headers: "stdint.h" and "stdbool.h". "betterapi.h"
// itself calls no external functions: not from the game, not the windows api,
// not even standard library functions. It is trivial to integrate into any other
// mod as it is a single file and there is no library to link with. Betterapi uses
// a callback system to mediate between your mod and BetterConsole. Betterapi is
// always a soft dependency which means that your mod can use BetterConsole APIs
// when they are available, but the lack of BetterConsole in the load order does
// not break your mod. The way this works is by exporting a function
// "BetterConsoleReceiver" which, if BetterConsole is loaded, will be called
// automatically just after Starfield creates a DirectX graphics context.
// BetterConsoleReceiver is generated by this file when you define the macro
// BETTERAPI_IMPLEMENTATION before including "betterapi.h". Because this function
// is not just declared but fully implemented in this header you must only define
// the BETTERAPI_IMPLEMENTATION macro in a single translation unit (.c or .cpp file)
// of your mod project to avoid linker errors about "multiple definition of function"
// As part of the glue code BetterConsoleReceiver will perform version and
// compatibility checking with the BetterConsole runtime component before calling
// your entrypoint for the API: "OnBetterConsoleLoad". Please read through the quick
// start guide to see an example of how you would implement "OnBetterConsoleLoad".
///////////////////////////////////////////////////////////////////////////////
// 4) Quick Start Guide
///////////////////////////////////////////////////////////////////////////////
// Adding BetterConsole features to your mod is very easy, you should be able
// to get a basic GUI up and running within minutes. The "betterapi.h" file can
// be included anywhere in your project and the only build requirement is to
// enable the implementation of the BetterConsole "glue code" by defining
// BETTERAPI_IMPLEMENTATION before including "betterapi.h" in *one* .c or .cpp
// file. The only code you need to write is the "OnBetterConsoleLoad" function.
// OnBetterConsoleLoad is called by BetterConsole when Starfield begins drawing
// to the screen. This function will be used to register your mod with BetterConsole.
// Take a look at this example for a basic plugin or build the example yourself
// by defining BETTERAPI_BUILTIN_EXAMPLE before including "betterapi.h" in an empty
// .c or .cpp file.
#ifdef BETTERAPI_BUILTIN_EXAMPLE
#undef BETTERAPI_BUILTIN_EXAMPLE
// Create the implementation glue code, this can only be done once per project
#define BETTERAPI_IMPLEMENTATION
// The example builds a combination ASI and SFSE plugin by enabling the minimal
// SFSE glue code also available in betterapi
#define BETTERAPI_ENABLE_SFSE_MINIMAL
// Use betterapi, you can include this anywhere you need to access hte features
#include "betterapi.h"
// It is usually convenient to make aliases for the betterconsole API structures
static const struct better_api_t* API = NULL;
static const struct simple_draw_t* UI = NULL;
// Forward declarations of callback functions used in this example
void MyDrawCallback(void*);
void MyConfigCallback(ConfigAction);
void MyHotkeyCallback(uintptr_t);
// Global plugin state
static uint32_t ButtonClickCounter = 0;
// Usually for hotkey requests you want your hotkey userdata to be an enum or a
// pointer to som hotkey-specific data.
enum MyHotkeyAction {
MHA_Reset,
MHA_Click,
MHA_ClickTwice
};
// This function will be called automatically when BetterConsole is loaded and
// your plugin is compatible with the runing version of BetterConsole.
static int OnBetterConsoleLoad(const struct better_api_t* better_api) {
API = better_api;
UI = API->SimpleDraw;
// The first step is to register your plugin name with BetterConsole
// The name used here will show up in the GUI so that the user can
// recognize your mod.
RegistrationHandle my_mod_handle = API->Callback->RegisterMod("Example");
// Everything else is optional, but likely you will want to also
// register a draw callback so your mod shows up in the UI.
API->Callback->RegisterDrawCallback(my_mod_handle, &MyDrawCallback);
// There are several types of callbacks depending on what kind of event
// you want to make a handler for. This line registers a configuration
// callback.
API->Callback->RegisterConfigCallback(my_mod_handle, &MyConfigCallback);
// The hotkey feature is activated in two steps, first register a callback:
API->Callback->RegisterHotkeyCallback(my_mod_handle, &MyHotkeyCallback);
// Then for each hotkey action you want to have, request a hotkey for it.
// The specific key combination used to activate a hotkey is set by the
// user in the "Mod Menu" > "Hotkeys" tab of BetterConsole.
API->Callback->RequestHotkey(my_mod_handle, "Reset Click Counter", MHA_Reset);
API->Callback->RequestHotkey(my_mod_handle, "Add 1 Click", MHA_Click);
API->Callback->RequestHotkey(my_mod_handle, "Add 2 Clicks", MHA_ClickTwice);
// return 0 if your plugin loaded correctly or
// return any positive number to indicate a failure
return 0;
}
// The draw callback is called whenever your plugin needs to draw something.
// There are many widgets available, but for simplicity lets make a basic example.
void MyDrawCallback(void*) {
UI->Text("Hello World!");
if (UI->Button("Click Me")) {
++ButtonClickCounter;
}
UI->Text("You pressed the button %u times", ButtonClickCounter);
}
// The config callback is called when the "BetterConsoleConfig.txt" file is loaded
// (read action), when the file is saved (write action), or when the user is in
// "Mod Menu" > "Settings" and selects your mod to configure it (edit action).
void MyConfigCallback(ConfigAction action) {
// The Config api offers default actions for certain types (like uint32_t)
API->Config->ConfigU32(action, "Button Click Couter", &ButtonClickCounter);
}
// Hotkeys are global, they can be activated even when BetterConsole is not shown.
// Only one draw callback per mod is allowed, but you can use the userdata parameter
// of the callback to tell which action was requested.
void MyHotkeyCallback(uintptr_t userdata) {
enum MyHotkeyAction act = (enum MyHotkeyAction)userdata;
if (act == MHA_Reset) {
ButtonClickCounter = 0;
}
else if (act == MHA_Click) {
ButtonClickCounter++;
}
else if (act == MHA_ClickTwice) {
ButtonClickCounter += 2;
}
}
// The following allows the example plugin to also be an sfse plugin
// more details are in the sfse section
// Step 1) Export this struct so sfse knows your DLL is compatible
DLLEXPORT SFSEPluginVersionData SFSEPlugin_Version = {
1, // SFSE api version, 1 is current
1, // Plugin api version, 1 is current
"BetterConsole Example Plugin", // Mod/Plugin Name (limit: 255 characters)
"Linuxversion", // Mod Author(s) (limit: 255 characters)
// Address Independance:
1, // 0 - hardcoded offsets (game version specific)
// 1 - signature scanning (not version specific)
// Structure Independance:
1, // 0 - relies on specific game structs
// 1 - mod does not care if game structs change
// Compatible Game Versions:
{ // A list of up to 15 game versions
MAKE_VERSION(1, 11, 36), // This means compatible with 1.11.36
0 // The list must be terminated with 0
}, // if address & structure independent
// then this is minimum version required
0, // 0 = does not rely on any specific sfse version
0, 0 // reserved fields, must be 0
};
// Step 2) Export this function so sfse knows to load your dll.
// Doing anything inside the function is optional.
DLLEXPORT bool SFSEPlugin_Load(const SFSEInterface* sfse) { return true; }
// Need to undef this for the example because the example needs to include itself
// and thus would create the implmentation functions twice
#undef BETTERAPI_IMPLEMENTATION
#endif // BETTERAPI_BUILTIN_EXAMPLE
#ifndef BETTERAPI_API_H /* Prevent multiple includes of same file */
#define BETTERAPI_API_H
// This is used to convert version numbers like 1.3.1 into a single number
// that can be compared, follows the same scheme as sfse and the game
#define MAKE_VERSION(MAJOR, MINOR, BUILD) ((((MAJOR)&0xFF)<<24)|(((MINOR)&0xFF)<<16)|(((BUILD)&0xFFF)<<4))
///////////////////////////////////////////////////////////////////////////////
// 5) Configuring Better API
///////////////////////////////////////////////////////////////////////////////
// Without configuration, "betterapi.h" will use the most compatible and lowest
// supported feature set. It is recommended to always use the lowest feature
// level necessary for your mod to function to maintain compatibility with
// BetterConsole versions in the past as well as the future. As new versions
// of BetterConsole are released, new API functions become available that were
// not in previous versions. At the minimum, your mod should support at least
// one previous feature level to allow mod collections that include an older
// release of BetterConsole to catch up to newer game releases.
// By default betterapi.h only enables the base featureset
// from version 1.3.1 (which itself is almost 100%) compatible
// with the first version released. To access newer functions
// and capabilities you will need to define the configuration
// variable BETTERAPI_FEATURE_LEVEL to a specific release of
// BetterConsole like MAKE_VERSION(1,4,1) for version 1.4.1
#ifndef BETTERAPI_FEATURE_LEVEL
#define BETTERAPI_FEATURE_LEVEL MAKE_VERSION(0, 0, 0)
#endif // BETTERAPI_FEATURE_LEVEL
// You might see the use of BETTERAPI_DEVELOPMENT_FEATURES in the code below.
// these are experimental features that might be in the next version of
// betterconsole but are not part of any current release on nexusmods.
// If you are developing a mod using the betterconsole api you MUST NOT define
// BETTERAPI_DEVELOPMENT_FEATURES, as the resulting plugin will not be compatible
// with the version of betterconsole that everyone is using.
//#define BETTERAPI_DEVELOPMENT_FEATURES
///////////////////////////////////////////////////////////////////////////////
// 6) What are SFSE and ASI mods?
///////////////////////////////////////////////////////////////////////////////
// If your reading this file you should already know? But just so we are all on
// the same page: SFSE and ASI mods are DLL files that are loaded into the game
// using a loader. The main difference between an SFSE and ASI mods is that ASI
// mods are loaded into the game using the ASI loader which just runs the winapi
// function LoadLibrary() on any file named *.asi in the Plugins/ folder. SFSE
// mods are loaded into the game using the SFSE loader which calls the same
// LoadLibrary() function as ASI mods, but offers a more complete interface for
// the game. ASI loaders are generic, just about every game has an ASI loader
// available for running mods, but SFSE is specific to Starfield. Due to the
// specificity of the SFSE loader, its possible to interact with many game
// functions and data structures that you must reverse-engineer youself in ASI
// mods.
//
// But what do the mods do and how do they work?
// You may be familiar with EXE programs and DLL libraries. When writing programs
// your code is converted into an executable file that calls function from dll
// libraries:
// [PROGRAM calls CreateWindow()] -------------> [DLL implments CreateWindow()]
// *This is an example of normal execution
//
// when you write a mod, you are performing the reverse operation, you are writing
// a program that compiles to a DLL and manipulating the functions of other DLLs
// or, with SFSE, using the functions of the game as if the game is the DLL and
// your mod is the EXE:
// [PROGRAM calls CreateWindow()] -> [MOD hooks CreateWindow()] -> [DLL implments CreateWindow()]
// *This is an example of DLL injection, the intent is to modify the window created by the game
// *for example, to change the window name, or set window flags, or adjust resolution more than
// *the game allows.
//
// [PROGRAM implements ExecuteConsole()] <--[MOD calls ExecuteConsole()]
// *This is an example of using the reverse engineered functions of the game to provide
// *a mod that ability to run commands through the in-game console. SFSE acts as
// *a centralized repository for reverse engineered functions that mod authors can use.
//
// [PROGRAM implements ExecuteConsole()] <---> [MOD hooks ExecuteConsole()]
// *Instead of calling ExecuteConsole(), a mod could hook it. This is what BetterConsole
// *does to provide a combination of execution and logging of all commands run even by the
// *game internally.
//
// BetterConsole can be used with SFSE or ASI mods by providing its own API to any other
// loaded DLL that exports a specific function "BetterConsoleReceiver"
//
// [ B e t t e r C o n s o l e ]
// | |
// v v
// [GAME] <-------------------> [MOD]
// | |
// v v
// ExecuteConsole() BetterConsoleReceiver(api*) -> SimpleDraw, Hook, Console, etc...
// PrintConsole()
// DX12_Present()
///////////////////////////////////////////////////////////////////////////////
// 7) The betterapi philosophy
///////////////////////////////////////////////////////////////////////////////
// On a philisophical level if its possible to export an API using plain C99 and
// include only standard C headers, then your API is deemed simple enough. And if
// you cannot describe an interface in C then you cannot truely understand your own
// code. In addition, almost every programming language has some type of C
// compatible foreign function interface, this allows you to write mods in almost
// any programming language albeit with some challenges.
#include
#include
// Implements the api version checks
// Dont worry, I'll undef this at the end of the file, nobody
// wants single letter macros polluting the namespace.
#ifdef V
#error "V is already defined!"
#endif // V
#define V(MAJOR, MINOR, PATCH) (MAKE_VERSION(MAJOR, MINOR, PATCH)<=(BETTERAPI_FEATURE_LEVEL))
#ifdef BETTERAPI_DEVELOPMENT_FEATURES
#pragma message ("BETTERAPI_DEVELOPMENT_FEATURES is defined, any plugin using the api will not be compatible the nexusmods release of betterconsole.")
#undef BETTERAPI_FEATURE_LEVEL
#define BETTERAPI_FEATURE_LEVEL MAKE_VERSION( 9, 9, 9)
#endif
///////////////////////////////////////////////////////////////////////////////
// 8) Thread safety
///////////////////////////////////////////////////////////////////////////////
// There is no thread safety! All of the betterapi functions are operating within
// the game's idxgiswapchain::present call as the game is trying submit a
// frame to the gpu. Do not use any of these apis from a separate thread unless
// it is explicitly marked as safe to do so and please keep it quick - we don't
// want to slow down the game. I will add an api for a separate task thread to
// do sig scanning or other slow tasks in a future update.
///////////////////////////////////////////////////////////////////////////////
// 9) What is userdata?
///////////////////////////////////////////////////////////////////////////////
// Several of the APIs in BetterConsole take a "userdata" argument
// since we are exposing all these capabilities in plain C99 we
// need a way to express a generic type or piece of data that you
// want to work with. Often this will show up in the SimpleDraw functions.
// Lets look at an example of why and how you would do this...
//
// say you have an array of pointers to some struct:
// struct GameType {
// ...
// uint32_t formid;
// ...
// const char* name;
// ...
// } *data_array[100] = some_pointer;
//
// how would you make a selectable list of these in a gui that knows nothing
// about GameType? The answer is userdata and a callback! Let's look at an
// example using the SimpleDraw SelectionList function:
//
// Create a to_string function for GameType:
//
// const char* gametype_to_string(const void* userdata, uint32_t index, char* out_tmp, uint32_t tmp_size) {
// const GameType** pGT = (const GameType**)userdata;
// const GameType* GT = pGT[index];
// snprintf(out_tmp, tmp_size, "FormID: %X, Name: %s", GT->formid, GT->name);
// return out_tmp;
// }
//
// Then make it a selectable list:
//
// static uint32_t selected_item = 0;
// bool was_clicked = API->SimpleDraw->SelectionList(&selected_item, (const void*)data_array, 100, gametype_to_string);
//
// Now the user can scroll through and click on an entry in the list without
// the UI code ever needing to know what the heck a GameType is.
// Note: selections lists dont always start at the first index, in our example above
// there were 100 items in the list, but selectionlist will only call gametype_to_string
// on the items that are visible, if the selection list can only show 20 items
// your gametype_to_string function may only be called for say index 53-73.
// So make sure your data is random access iterable.
///////////////////////////////////////////////////////////////////////////////
// 10) Typedefs
///////////////////////////////////////////////////////////////////////////////
// When you register a mod you receive a handle to your mod
// this handle can be used to register additional functionality
// like hotkeys or draw callbacks
typedef uint32_t RegistrationHandle;
// Opaque handle for the log buffer system
typedef uint32_t LogBufferHandle;
// while many compilers let you cast a function pointer to a void*
// the C standard only lets you cast a function pointer to another
// function pointer. This also helps document the API better.
typedef void (*FUNC_PTR)(void);
///////////////////////////////////////////////////////////////////////////////
// 11) Enums
///////////////////////////////////////////////////////////////////////////////
// The settings callback tells you what type of action to perform from these options:
// ConfigAction_Read - betterconsole finished reading the settings file
// and now you can try loading any settings you saved
// ConfigAction_Write - betterconsole is about to save all settings to
// the settings file
// ConfigAction_Edit - the user is in the settings menu and wants to adjust a setting
// show a UI to the user to edit the setting
typedef enum ConfigAction {
ConfigAction_Read,
ConfigAction_Write,
ConfigAction_Edit,
} ConfigAction;
///////////////////////////////////////////////////////////////////////////////
// 12) Callback Types
///////////////////////////////////////////////////////////////////////////////
// To show a GUI you must register a draw callback with this signature.
//
// `imgui_context` can be ignored if using the SimpleDraw API, but if you link with
// imgui directly this can be used to create more complex user interfaces but you
// must call ImGui::SetCurrentContext(imgui_context) first in your draw callback
// and match the version of imgui used by betterconsole exactly.
//
// Note: this callback will be called every frame when betterconsole is open and
// the tab for your mod is selected.
typedef void (*DRAW_CALLBACK)(void* imgui_context);
// if your application would like to take advantage of the built-in settings
// registry you will need to provide a callback function that is called when
// the settings file is loaded, saved, or when the user is in the settings menu
// and selects your mod. The Settings API provides automatic read/write/edit
// functionality if the built-in types work for you.
// `read_write_or_edit` is one of: ConfigAction_Read, ConfigAction_Write, or
// ConfigAction_Edit
typedef void (*CONFIG_CALLBACK)(ConfigAction read_write_or_edit);
// This is a function called when the user presses a hotkey combination that
// matches the hotkey set for your plugin. Setting a hotkey is done by the user
// in the hotkeys menu. Hotkeys are saved automatically in the settings registry
// so a set hotkey combo is preserved across sessions. Only one hotkey callback
// per mod can be registered, but you can request hotkeys multiple times with
// different user data.
//
// `userdata` is any arbitrary data that you want to pass to the callback usually
// to help identify the hotkey if multiple hotkeys are requested
typedef void (*HOTKEY_CALLBACK)(uintptr_t userdata);
// For the simpledraw selectionlist, this is a function called to provide text for
// a single entry in a selectable list of items. This function will be called every
// frame for every *visible* item in the list. The return value should be a null
// terminated string that will be shown in the UI.
//
// `userdata` is any arbitrary data that you want to pass to the callback
//
// `index` is the index of the item in the list to "stringify"
//
// `out_buffer` is provided as a temporary buffer that can be used
// to store the result of snprintf() or similar since
// freeing a heap allocation made within the callback
// is a very difficult task.
typedef const char* (*CALLBACK_SELECTIONLIST)(const void* userdata, uint32_t index, char* out_buffer, uint32_t out_buffer_size);
// For the simpledraw table renderer, used to build the UI shown in tables.
// This callback will be called for each *visible* cell in the table (unseen cells
// will not be rendered and thus will not call this function). Tables are drawn
// first left to right for each column, then from top to bottom for each row.
//
// `table_userdata` is any arbitrary data that you want to pass to the callback and
// should be used for the data you wish to draw in the table
//
// `current_row` is the index of the current row in the table that is being drawn
//
// `current_column` is the index of the current column in the table that is being drawn
// TODO: next version update this to use unsigned integers and possibly use uintptr_t
// instead of void* for table_userdata
typedef void (*CALLBACK_TABLE)(uintptr_t table_userdata, int current_row, int current_column);
///////////////////////////////////////////////////////////////////////////////
// 13) Callback API
///////////////////////////////////////////////////////////////////////////////
// This is the main api you will use to add betterconsole integration to your mod
struct callback_api_t {
// Register your mod with betterconsole.
// The returned handle can be used to register additional functionality
// such as hotkeys or draw callbacks.
//
// `mod_name` should uniquely identify your mod and conform to the following:
// - must be less than 32 characters
// - must have a length of >3 characters
// - must not begin or end with whitespace
//
// Almost all mods will need to use this to interact with betterconsole.
RegistrationHandle(*RegisterMod)(const char* mod_name);
// Register a function to show your mod's user interface.
//
// `handle` is your registration handle from a previous call to RegisterMod
//
// `draw_callback` will be called every frame when the following conditions are met:
// - the BetterConsole UI is open
// - your mod is the one in focus
//
// Usually, you would utilize the SimpleDraw API to create your UI, advanced
// users may link with imgui directly and utilize the imgui context provided
// as the first parameter to the draw callback. If using imgui directly, you
// must call ImGui::SetCurrentContext() in the draw callback before using
// any imgui functions AND you must match the version of imgui with the
// version used by betterconsole.
void (*RegisterDrawCallback)(RegistrationHandle handle, DRAW_CALLBACK draw_callback);
// Register a configuration callback
//
// `handle` is your registration handle from a previous call to RegisterMod
//
// `config_callback` will be called when the settings file is loaded (read event)
// saved (write event) or when the user is in the setting menu
// and selects your mod in the UI (edit event)
void (*RegisterConfigCallback)(RegistrationHandle handle, CONFIG_CALLBACK config_callback);
// Register a hotkey callback
//
// `handle` is your registration handle from a previous call to RegisterMod
//
// `hotkey_callback` will be called when the user presses the hotkey combo
// set for your plugin. to have an effect, you must also
// call RequestHotkey. This is a two-step process because
// your plugin can have only one hotkey callback but you
// request any number of hotkeys with different userdata.
void (*RegisterHotkeyCallback)(RegistrationHandle handle, HOTKEY_CALLBACK hotkey_callback);
// Request a hotkey handler, the user configures the hotkey in the hotkeys menu
//
// `handle` is the handle you created when registering your mod
//
// `hotkey_name` is the name of the hotkey in the ui, use a descriptive name
// less than the 32 character internal buffer. this string is
// copied to an internal buffer so feel free to use a generated
// name via snprintf() or similar on stack allocated memory.
//
// `userdata` is optional extra data that is sent to the hotkey callback.
// if you want to request multiple hotkeys this parameter is
// how you would differentiate them.
void (*RequestHotkey)(RegistrationHandle handle, const char* hotkey_name, uintptr_t userdata);
#ifdef BETTERAPI_DEVELOPMENT_FEATURES
// Register an about page draw callback.
//
// `handle` is the handle you created when registering your mod
//
// `about_callback` is a draw callback that you can use to show the user infomation
// about your mod like version, changelog, and copyright or
// provide links to a file (like a log file) or url
// (like nexusmods page, discord, or github repo)
void (*RegisterAboutPage)(RegistrationHandle handle, DRAW_CALLBACK about_callback);
#endif
};
///////////////////////////////////////////////////////////////////////////////
// 14) Config API
///////////////////////////////////////////////////////////////////////////////
/// This API is used for saving and loading data to a configuration file.
struct config_api_t {
// this handles read / write / edit on 32-bit unsigned integers
// if a config value for key_name is not found, value is not modified
// this way you can initialize all variables to a default value
void (*ConfigU32)(ConfigAction action, const char* key_name, uint32_t* value);
#ifdef BETTERAPI_DEVELOPMENT_FEATURES
// dev - 1.4.0
// read, write, and edit for string values
// `in_out_buffer` is a pointer to a buffer that will be read, written, or edited
// `buffer_size` is the length of the bufer in bytes
// ConfigString stops on the first null character of the input or output
// unlike other config functions, this function does write to the output if lookup fails on read (first byte becomes null)
// and sets "in_out_buffer[buffer_size - 1] = 0" on action write
void (*ConfigString)(ConfigAction action, const char* key_name, char* in_out_buffer, uint32_t buffer_size);
// dev - 1.4.1
// read, write, and edit for boolean values
// `out_value` is a pointer to a boolean variable that will be read, written, or edited
void (*ConfigBool)(ConfigAction action, const char* key_name, bool* out_value);
#endif // BETTERAPI_DEVELOPMENT_FEATURES
};
///////////////////////////////////////////////////////////////////////////////
// 15) Hook API
///////////////////////////////////////////////////////////////////////////////
// This api deals with hooking functions and vtables
struct hook_api_t {
// Hook old_func so it is redirected to new_func
// returns a function pointer to call old_func after the hook
// uses the minhook library internally
FUNC_PTR (*HookFunction)(FUNC_PTR old_func, FUNC_PTR new_func);
// Hook a vtable function for all instances of a class
// returns the old function pointer
FUNC_PTR(*HookVirtualTable)(void* class_instance, unsigned method_index, FUNC_PTR new_func);
// Relocate an offset from imagebase
// sfse offers a better version of this, but if you are using the minimal api (see below)
// then you might want to use this
void* (*Relocate)(unsigned imagebase_offset);
// Write memory
// same as Relocate, use the sfse version if you can
bool(*WriteMemory)(void* dest, const void* src, unsigned size);
// get the address of a function through starfield's import address table
FUNC_PTR(*GetProcAddressFromIAT)(const char* dll_name, const char* func_name);
// hook a function in the Import Address Table of starfield
FUNC_PTR(*HookFunctionIAT)(const char* dll_name, const char* func_name, const FUNC_PTR new_function);
// !EXPERIMENTAL API! AOB scan the exe memory and return the first match
void* (*AOBScanEXE)(const char* signature);
#ifdef BETTERAPI_DEVELOPMENT_FEATURES
// build a wall so high that nobody can see the top from the highest ladder
// https://devblogs.microsoft.com/oldnewthing/20110310-00/?p=11253
void** (*LiterallyReplaceEntireVtable)(void*** ppClassInstance, uint32_t method_count);
#endif // BETTERAPI_DEVELOPMENT_FEATURES
};
///////////////////////////////////////////////////////////////////////////////
// 16) Graphics API
///////////////////////////////////////////////////////////////////////////////
// This API allows you to create a basic mod menu without linking to imgui.
// if you play your cards right, you can write an entire mod without any other
// headers or libraries... not even the standard library!
struct simple_draw_t {
// Draw a separator line that fills the horizontal space
void (*Separator)(void);
// Draw some text, supports format specifiers
void (*Text)(const char *fmt, ...);
// Draw a button, returns true if the button was clicked
// Example:
// if (SimpleDraw->Button("Hello")) { /* do something on click */ }
bool (*Button)(const char* name);
// Draw a checkbox, this widget needs to store persistent state
// usually you would pass a static bool as a pointer
// returns true if the state was changed this frame
// Example:
// static bool state = false;
// if (SimpleDraw->Checkbox("Hello", &state)) { /* do something on state change */ }
bool (*Checkbox)(const char* name, bool* state);
// Draw a text input widget for a single line of text
// `buffer` will be used to store the text the user types
// `true_on_enter` determines if the widget returns true on every character
// typed or only when enter is pressed
// Example:
// static char buffer[32];
// if (SimpleDraw->InputText("Hello", buffer, sizeof(buffer), true)) { /* do something on enter */ }
bool (*InputText)(const char* name, char* buffer, uint32_t buffer_size, bool true_on_enter);
// Split the remainaing window space into a left and right region.
// the left region occupies a `left_size` (eg: .5) fraction of the screen
// the remaining space is reserved for the HBoxRight() side
// `min_size_em` is a safety minimum number of charactes wide that the
// HBoxLeft region can be and overrides the `left_size` if it is smaller.
// If you call HBoxLeft, you must also call HBoxRight and HBoxEnd
// HBox and VBox regions can be nested for more complex layouts but
// are more performance intensive than most other SimpleDraw functions.
void (*HBoxLeft)(float left_size, float min_size_em);
// Move to the right side region after a call to HBoxLeft
// no argument needed, the size of the region is the remaining
// space not taken up by HBoxLeft
void (*HBoxRight)();
// Finish the HBoxLeft and HBoxRight regions
// no argument needed, this signals the end of the region
// and resumes normal layout
void (*HBoxEnd)();
// This is the same as hbox, but for vertically separated spaces
void (*VboxTop)(float top_size, float min_size_em);
void (*VBoxBottom)();
void (*VBoxEnd)();
// Display an int number editor as a draggable slider that
// can be double-clicked to edit the value directly.
// `min` and `max` define the range of the *value
// the return value is true if the value was changed this frame
bool (*DragInt)(const char* name, int* value, int min, int max);
// Follows the same rules as the sliderint above
bool (*DragFloat)(const char* name, float* value, float min, float max);
// Use the remaining vertical space to render the logbuffer
// `handle` is the logbuffer handle to render
// `scroll_to_bottom` if true, force the logbuffer region to scroll
// from its current position to the end (newest data)
void (*ShowLogBuffer)(LogBufferHandle handle, bool scroll_to_bottom);
// Use the remaining vertical space to render a filtered logbuffer
// `handle` is the logbuffer handle to render
// `lines` is an array of line numbers to display
// `line_count` is the number of lines to display from the `lines` array
// `scroll_to_bottom` if true, force the logbuffer region to scroll
// from its current position to the end (newest data)
void (*ShowFilteredLogBuffer)(LogBufferHandle handle, const uint32_t* lines, uint32_t line_count, bool scroll_to_bottom);
// Show a selectable list of text items.
// returns true when the user click on an item and the selection changes.
// `selection` is the index of the selected item and should be a static variable
// `userdata` is any arbitrary data that you want to pass to the callback
// `count` is the number of items in the list
// `to_string` is a function that converts an index and userdata into a string
// an is called for each *visible* item in the list every frame.
// Proper clipping of non-visible items is done automatically so large lists
// can be drawn in a performant manner.
// Usually you would want to contain this in an HBox or VBox otherwise
// it will take up all remaining horizontal and vertical space.
bool(*SelectionList)(uint32_t *selection, const void* userdata, uint32_t count, CALLBACK_SELECTIONLIST to_string);
// Draw a table one cell at a time through the `draw_cell` callback
// `header_labels` is an array of strings that are the column headers
// `header_count` is the number of strings in the `header_labels` array
// this is assumed to be the same as the count of columns
// `table_userdata` is any arbitrary data that you want to pass to the callback
// `row_count` is the number of rows in the table, the total number of cells in
// the table is row_count * header_count
// `draw_cell` is a callback that is called for each *visible* cell in the table
// Proper vertical and horizontal clipping is done to accelerate even very large tables
void (*Table)(const char * const * const header_labels, uint32_t header_count, uintptr_t table_userdata, uint32_t row_count, CALLBACK_TABLE draw_cell);
// draw tabs that can be switched between
void (*TabBar)(const char* const* const headers, uint32_t header_count, int* state);
// draw buttons in a row, similar to a toolbar
// returns -1 if no button was pressed this frame (common case)
// otherwise returns the index of the button pressed this frame
// `labels` is an array of strings that are the button labels
// `label_count` is the number of strings in the `labels` array
// this is assumed to be the count of buttons
// if there is not enough horizontal space available the buttons
// out of bounds will wrap to the next row.
int (*ButtonBar)(const char* const* const labels, uint32_t label_count);
// show a tooltip marker that looks like "(?)"
// when the user hovers over the marker `text` will be shown
// in a little popup
void (*Tip)(const char* text);
// place multiple simpledraw elements on the same line
void (*SameLine)();
#ifdef BETTERAPI_DEVELOPMENT_FEATURES
// Draw a radiobutton, returns true if the button was selected this frame
//
// `selection_group` is the group this radiobutton belongs to.
// use the address of a static variable here.
// the value is the currently selected radiobutton.
//
// `current_id` the id of the current button in a group.
// set this to 0 every frame before drawing the
// first button in a group. no need for a static variable
bool (*RadioButton)(const char* text, uint32_t* selection_group, uint32_t* current_id);
// Show a small button that invokes the "open" shell action on click
//
// `display_text` This is the text shown inside the button
//
// `url_or_path` This is the link or file path to open using the default
// handler (like a web browser or notepad)
void (*LinkButton)(const char* display_text, const char* url_or_path);
#endif
};
///////////////////////////////////////////////////////////////////////////////
// 17) LogBuffer API
///////////////////////////////////////////////////////////////////////////////
// the logbuffer API is deprecated and will be changed in a future release
struct log_buffer_api_t {
// Create a handle, needed to identify the log buffer
// name is the name of the logbuffer as it shows up in the UI
// path is an optional path to a logfile to append with logs
LogBufferHandle (*Create)(const char* name, const char* path);
// Get the number of lines stored in the buffer
uint32_t (*GetLineCount)(LogBufferHandle);
// Retrieve a specific line for storage
const char* (*GetLine)(LogBufferHandle, uint32_t line);
// Log a line of text to the buffer
void (*Append)(LogBufferHandle, const char* log_line);
// Restore the log buffer from a file, the file is then used for appending
LogBufferHandle (*Restore)(const char* name, const char* filename);
#if V(1,4,1)
// Clear the log buffer
void (*Clear)(LogBufferHandle handle);
#endif
};
///////////////////////////////////////////////////////////////////////////////
// 18) C Library API
///////////////////////////////////////////////////////////////////////////////
// this api is just a small selection of the most commonly needed standard library functions
// using this api means that your plugin might not need to link with the standard library at all
struct std_api_t {
// allocate `bytes` of ram, return NULL on error
void* (*malloc)(size_t bytes);
// free a previously allocated block of ram
void (*free)(void* malloc_buffer);
// printf style formatting into `buffer` of `buffer_size` bytes, returns <0 on error or bytes used
int (*snprintf)(char* buffer, size_t buffer_size, const char* fmt, ...);
// copy `bytes` of memory from `src` to `dest`, return dest
void* (*memcpy)(void* dest, const void* src, size_t bytes);
// set `bytes` of memory in `dest` to `value`, return dest, value must be <256
void* (*memset)(void* dest, int value, size_t bytes);
};
///////////////////////////////////////////////////////////////////////////////
// 19) Game Console API
///////////////////////////////////////////////////////////////////////////////
struct console_api_t {
// run `command` on the in-game console
// max length of `command` is limited to 512 bytes by the game
// NOTE: the game wont run console commands until the main menu
// has finished loading otherwise it freezes waiting
// for the console to be ready (if your compiling shaders
// the game could seem frozen for several minutes)
// there should be a way to know when the console is ready, but
// as I dont link with sfse, addresslibrary, commonlibsf or anything
// I have to figure it out myself. Maybe see when startingconsolecommand
// is run?
// Version 1.4.1 - copy command to internal buffer to take const char*
// and string literals, not API breaking for old plugins
// running on newer betterconsole
#if V(1,4,1)
void (*RunCommand)(const char* command);
#else
void (*RunCommand)(char* command);
#endif
#if BETTERAPI_DEVELOPMENT_FEATURES
// Pause or unpause the game
//
// `paused` true to pause the game
// false to unpause
void (*SetGamePaused)(bool paused);
#endif
};
///////////////////////////////////////////////////////////////////////////////
// 20) BetterConsole API
///////////////////////////////////////////////////////////////////////////////
// This is all the above structs wrapped up in one place
// why pointers to apis instead of the api itself? so that
// I may extend any api without changing the size of BetterAPI
// this helps with forwards compatibility and i wont need to
// update the BETTERAPI_VERSION as often.
typedef struct better_api_t {
const struct hook_api_t* Hook;
const struct log_buffer_api_t* LogBuffer;
const struct simple_draw_t* SimpleDraw;
const struct callback_api_t* Callback;
const struct config_api_t* Config;
const struct std_api_t* Stdlib;
const struct console_api_t* Console;
} BetterAPI;
// Open to feedback:
// in the future, instead of having one version of the api
// and one version of the betterapi struct, pass in a
// service locator and resolve dependencies on a more
// granular level that does not rely on struct layout
#if BETTERAPI_VERSION > 1
enum ServiceType {
ServiceType_Callback,
ServiceType_Hook,
// etc...
};
// Get a pointer to an api struct.
// This allows betterconsole to return different versions of the api
// depending on mod compatibility.
typedef const void* (*BetterServiceLocator)(enum ServiceType type, uint32_t version);
//then instead of providing a betterapi pointer, only send the locator and version data:
DLLEXPORT int BetterConsoleReceiver2(BetterServiceLocator locator, uint32_t betterconsole_api_level) {
static better_api_t api;
api.Callback = locator(ServiceType_Callback, 0); //something like this?
// we could have #defines for BETTERAPI_ENABLE_CONFIG or something
// so you only enable the parts of the api that you use, less chance of
// breaking compatibility if we only incrementally update unpopular APIs
return OnBetterConsoleLoad(&api);
}
#endif
// Dont keep single letter macros around outside this file
#ifdef V
#undef V
#endif // V
#endif // !BETTERAPI_API_H
///////////////////////////////////////////////////////////////////////////////
// 21) SFSE Minimal Interface
///////////////////////////////////////////////////////////////////////////////
// For people that want to port Cheat Engine or ASI mods to sfse without including
// sfse code into the project, define BETTERAPI_ENABLE_SFSE_MINIMAL before
// including betterapi.h, this should get you 90% of the way to a
// plug and play sfse plugin with no brain required. Take a look at the example
// plugin for an example of how to do this.
#ifdef BETTERAPI_ENABLE_SFSE_MINIMAL
#ifndef BETTERAPI_SFSE_MINIMAL
#define BETTERAPI_SFSE_MINIMAL
typedef uint32_t PluginHandle;
typedef enum SFSEEnumeration_t {
InterfaceID_Invalid = 0,
InterfaceID_Messaging,
InterfaceID_Trampoline,
MessageType_SFSE_PostLoad = 0,
MessageType_SFSE_PostPostLoad,
} InterfaceID;
typedef struct SFSEPluginVersionData_t {
uint32_t dataVersion;
uint32_t pluginVersion;
char name[256];
char author[256];
uint32_t addressIndependence;
uint32_t structureIndependence;
uint32_t compatibleVersions[16];
uint32_t seVersionRequired;
uint32_t reservedNonBreaking;
uint32_t reservedBreaking;
} SFSEPluginVersionData;
typedef struct SFSEPluginInfo_t {
uint32_t infoVersion;
const char* name;
uint32_t version;
} SFSEPluginInfo;
typedef struct SFSEInterface_t {
uint32_t sfseVersion;
uint32_t runtimeVersion;
uint32_t interfaceVersion;
void* (*QueryInterface)(uint32_t id);
PluginHandle (*GetPluginHandle)(void);
SFSEPluginInfo* (*GetPluginInfo)(const char* name);
} SFSEInterface;
typedef struct SFSEMessage_t {
const char* sender;
uint32_t type;
uint32_t dataLen;
void* data;
} SFSEMessage;
typedef void (*SFSEMessageEventCallback)(SFSEMessage* msg);
typedef struct SFSEMessagingInterface_t {
uint32_t interfaceVersion;
bool (*RegisterListener)(PluginHandle listener, const char* sender, SFSEMessageEventCallback handler);
bool (*Dispatch)(PluginHandle sender, uint32_t messageType, void* data, uint32_t dataLen, const char* receiver);
}SFSEMessagingInterface;
#endif // !BETTERAPI_SFSE_MINIMAL
#endif
#ifdef BETTERAPI_IMPLEMENTATION
#undef BETTERAPI_IMPLEMENTATION
#ifdef __cplusplus
#define DLLEXPORT extern "C" __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllexport)
#endif // __cplusplus
static int OnBetterConsoleLoad(const struct better_api_t* betterapi);
// In a future API version we need to return a value from this function
// it makes sense to know and log if a mod can't load
DLLEXPORT void BetterConsoleReceiver(const struct better_api_t* api) {
OnBetterConsoleLoad(api);
}
#endif // BETTERAPI_IMPLEMENTATION