From 607590b0801607aadd3547e62a5baeb4b1c722e9 Mon Sep 17 00:00:00 2001 From: linuxversion <1660477+SomeCrazyGuy@users.noreply.github.com> Date: Sun, 16 Jun 2024 15:15:00 -0400 Subject: [PATCH] more docimentation, add offsets back to console for now, more constexpr --- betterapi.h | 351 +++++++++++++++++++++++++++++++++++++++++------- src/console.cpp | 15 ++- src/console.h | 2 +- src/main.cpp | 17 +-- src/main.h | 23 +++- src/std_api.cpp | 17 +-- src/std_api.h | 2 +- 7 files changed, 348 insertions(+), 79 deletions(-) diff --git a/betterapi.h b/betterapi.h index 618f4c3..de147f6 100644 --- a/betterapi.h +++ b/betterapi.h @@ -1,19 +1,169 @@ #ifndef BETTERAPI_API_H #define BETTERAPI_API_H -// Always use the latest betterapi.h file from github in your build if the BETTERAPI_VERSION -// number is compatible with the published version of betterconsole on nexusmods. -// sometimes small hacks or fixes are put here to fix issues in the published mod between releases. + +// 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 -// Version number for betterapi, when a new version of betterconsole changes the public API -// this number is incremented. Usually only when bigger feature changes are added. -// I will not break the API in a bugfix release of betterconsole and feature releases -// are not expected to happen more often than starfield updates. -// Usually porting to a new version of the api is as easy as dropping in the new betterapi.h -// into your build and fixing the compiler errors (if any). +/////////////////////////////////////////////////////////////////////////////// +// Table Of Contents +/////////////////////////////////////////////////////////////////////////////// +// 1) The Unlicense +// 2) Release History +// 3) About this file +// 4) Configuring Better API +// 5) Quick Start Guide + + + +/////////////////////////////////////////////////////////////////////////////// +// 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) 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. +// +// Configuration is now done through a separate file "betterapiconfig.h". +// Documentation for all configuration options is also the responsibility +// of that file. You can find an example "betterapiconfig.h" in the "extra" +// folder of the source code repo or distributed with future versions of the +// example plugin. Uncomment the line below to use "betterapiconfig.h". + +//#include "betterapiconfig.h" + + + + + + + + + + + + + + + + + + + + +// 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 V(1,3,1) for version 1.3.1 +// 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) ((((MAJOR)&0xFF)<<16|((MINOR)&0xFF)<<8|((PATCH)&0xFF))>=(BETTERAPI_FEATURE_LEVEL)) + + +// This is the API version of betterconsole, its not +// expected to change often and by the time version +// 2 becomes the default, the released version of +// BetterConsole will have supported both versions +// of the API for more than one release cycle. +#ifndef BETTERAPI_VERSION #define BETTERAPI_VERSION 1 +#endif /////////////////////////////////////////////////////////////////////////////// @@ -721,64 +871,171 @@ typedef struct better_api_t { const struct std_api_t* Stdlib; const struct console_api_t* Console; } BetterAPI; -#endif // !BETTERAPI_API_H +// Dont keep silgle letter macros around outside this file +#ifdef V +#undef V +#endif // V -#ifdef BETTERAPI_ENABLE_SFSE_MINIMAL -#ifndef BETTERAPI_SFSE_MINIMAL -#define BETTERAPI_SFSE_MINIMAL -// 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: -// -// #define BETTERAPI_ENABLE_SFSE_MINIMAL -// #define BETTERAPI_IMPLEMENTATION -// #include "betterapi.h" +/////////////////////////////////////////////////////////////////////////////// +// 5) 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" +#ifdef BETTERAPI_BUILTIN_EXAMPLE + +// Create the implementation glue code, this can only be done once per project +#define BETTERAPI_IMPLEMENTATION -// if you want your dll to be loadable with sfse you need to export two symbols: -// `SFSEPlugin_Version` and `SFSEPlugin_Load` paste this in one translation unit -// and modify to suit your needs: +// 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 -/* -// Step 1) Export this struct so sfse knows your DLL is compatible -DLLEXPORT SFSEPluginVersionData SFSEPlugin_Version = { - 1, // SFSE api version, 1 is current +// Use betterapi, you can include this anywhere you need to access hte features +#include "betterapi.h" - 1, // Plugin api version, 1 is current +// 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; - "BetterConsole", // Mod/Plugin Name (limit: 255 characters) +// Forward declarations of callback functions used in this example +void MyDrawCallback(void*); +void MyConfigCallback(ConfigAction); +void MyHotkeyCallback(uintptr_t); - "Linuxversion", // Mod Author(s) (limit: 255 characters) +// Global plugin state +static uint32_t ButtonClickCounter = 0; + +// Usually for hotkey requests you want your hotkey userdata to be an enum +enum MyHotkeyAction { + MHA_Reset, + MHA_Click, + MHA_ClickTwice +}; + +// This function will be called automaticcally 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 A 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); +} - // Address Independance: - 1, // 0 - hardcoded offsets (game version specific) - // 1 - signature scanning (not version specific) +// The config callback is called when the "BetterConsoleConfig.txt" file is loaded +// (read event), when the file is saved (write event), or when the user is in +// "Mod Menu" > "Settings" and selects your mod to configure it (edit event). +void MyConfigCallback(ConfigAction action) { + // The Config api offers default actions for certain types (like uint32_t) + API->Config->ConfigU32(action, "Button Click Couter", &ButtonClickCounter); +} - // Structure Independance: - 1, // 0 - relies on specific game structs - // 1 - mod does not care if game structs change +// 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 paramiter +// of the callback to tell which action was requested. +void MyHotkeyCallback(uintptr_t userdata) { + enum MyHotkeyAction act = (enum MyHotkeyAction)userdata; - // 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 + 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 + 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 void SFSEPlugin_Load(const SFSEInterface * sfse) {} -*/ +DLLEXPORT void SFSEPlugin_Load(const SFSEInterface* sfse) {} + +#endif // BETTERAPI_BUILTIN_EXAMPLE + +#endif // !BETTERAPI_API_H + +#ifdef BETTERAPI_ENABLE_SFSE_MINIMAL +#ifndef BETTERAPI_SFSE_MINIMAL +#define BETTERAPI_SFSE_MINIMAL + +// 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. + #define MAKE_VERSION(major, minor, build) ((((major)&0xFF)<<24)|(((minor)&0xFF)<<16)|(((build)&0xFFF)<<4)) typedef uint32_t PluginHandle; diff --git a/src/console.cpp b/src/console.cpp index 01b49cf..577ea9f 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -324,12 +324,15 @@ extern void setup_console(const BetterAPI* api) { } -extern const struct console_api_t* GetConsoleAPI() { - static const struct console_api_t Console { - [](char* command) noexcept -> void { - console_run(NULL, command); - } - }; +static void run_comand(char* command) { + console_run(NULL, command); +} + + +static constexpr struct console_api_t Console { + run_comand +}; +extern constexpr const struct console_api_t* GetConsoleAPI() { return &Console; } \ No newline at end of file diff --git a/src/console.h b/src/console.h index 10c6f39..7e9c211 100644 --- a/src/console.h +++ b/src/console.h @@ -7,4 +7,4 @@ extern void setup_console(const BetterAPI* api); // but I still need to send out the console api to other components -extern const struct console_api_t* GetConsoleAPI(); \ No newline at end of file +extern constexpr const struct console_api_t* GetConsoleAPI(); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index c3cb5bd..03a2124 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,7 @@ #include "broadcast_api.h" #include "std_api.h" #include "hotkeys.h" +#include "std_api.h" #include "d3d11on12ui.h" @@ -73,16 +74,6 @@ extern char* GetPathInDllDir(char* path_max_buffer, const char* filename) { } -const char* const filename_only(const char* path) { - const char* p = path; - while (*path) { - if (*path == '\\') p = path; - ++path; - } - return ++p; -} - - #ifdef MODMENU_DEBUG #include std::mutex logging_mutex; @@ -106,7 +97,7 @@ static void write_log(const char* const str) noexcept { logging_mutex.unlock(); } extern void DebugImpl(const char* const filename, const char* const func, int line, const char* const fmt, ...) noexcept { - auto bytes = snprintf(format_buffer, buffer_size, "%s:%s:%d>", filename_only(filename), func, line); + auto bytes = snprintf(format_buffer, buffer_size, "%s:%s:%d>", filename, func, line); ASSERT(bytes > 0); ASSERT(bytes < buffer_size); @@ -132,7 +123,7 @@ extern void AssertImpl [[noreturn]] (const char* const filename, const char* con "In function '%s'\n" "On line '%d'\n" "Message: '%s'", - filename_only(filename), + filename, func, line, text @@ -146,7 +137,7 @@ extern void AssertImpl [[noreturn]] (const char* const filename, const char* con extern void TraceImpl(const char* const filename, const char* const func, int line, const char* const fmt, ...) noexcept { static bool init = false; static char tracebuff[2048]; - const auto bytes = snprintf(tracebuff, sizeof(tracebuff), "%s:%s:%d> ", filename_only(filename), func, line); + const auto bytes = snprintf(tracebuff, sizeof(tracebuff), "%s:%s:%d> ", filename, func, line); ASSERT(bytes > 0 && "trace buffer too small ?"); va_list args; va_start(args, fmt); diff --git a/src/main.h b/src/main.h index 012ac47..92b0032 100644 --- a/src/main.h +++ b/src/main.h @@ -12,12 +12,29 @@ #endif // !MODMENU_DEBUG #ifdef MODMENU_DEBUG + +constexpr const char* next_slash(const char* const path) { + return (!path) + ? nullptr + : (*path == '/' || *path == '\\') + ? path + : (*path == 0) + ? nullptr + : next_slash(path + 1); +} + +constexpr const char* filename_only(const char* const path) { + return (next_slash(path)) + ? filename_only(next_slash(path + 1)) + : path; +} + extern void DebugImpl(const char* const filename, const char* const func, int line, const char* const fmt, ...) noexcept; extern void AssertImpl(const char* const filename, const char* const func, int line, const char* const text) noexcept; extern void TraceImpl(const char* const filename, const char* const func, int line, const char* const fmt, ...) noexcept; -#define DEBUG(...) do { DebugImpl(__FILE__, __func__, __LINE__, " " __VA_ARGS__); } while(0) -#define ASSERT(CONDITION) do { if (!(CONDITION)) { AssertImpl(__FILE__, __func__, __LINE__, " " #CONDITION); } } while(0) -#define TRACE(...) do { TraceImpl(__FILE__, __func__, __LINE__, " " __VA_ARGS__); } while(0) +#define DEBUG(...) do { DebugImpl(filename_only(__FILE__), __func__, __LINE__, " " __VA_ARGS__); } while(0) +#define ASSERT(CONDITION) do { if (!(CONDITION)) { AssertImpl(filename_only(__FILE__), __func__, __LINE__, " " #CONDITION); } } while(0) +#define TRACE(...) do { TraceImpl(filename_only(__FILE__), __func__, __LINE__, " " __VA_ARGS__); } while(0) #define IMGUI_DEBUG_PARANOID #else #define DEBUG(...) do { } while(0) diff --git a/src/std_api.cpp b/src/std_api.cpp index a758bfb..bee3344 100644 --- a/src/std_api.cpp +++ b/src/std_api.cpp @@ -3,13 +3,14 @@ #include -extern const struct std_api_t* GetStdAPI() { - static const struct std_api_t api { - &malloc, - &free, - &snprintf, - &memcpy, - &memset - }; +static constexpr struct std_api_t api { + &malloc, + &free, + &snprintf, + &memcpy, + &memset +}; + +extern constexpr const struct std_api_t* GetStdAPI() { return &api; } \ No newline at end of file diff --git a/src/std_api.h b/src/std_api.h index 44231c0..0012357 100644 --- a/src/std_api.h +++ b/src/std_api.h @@ -1,4 +1,4 @@ #pragma once #include "../betterapi.h" -extern const struct std_api_t* GetStdAPI(); \ No newline at end of file +extern constexpr const struct std_api_t* GetStdAPI(); \ No newline at end of file