Skip to content

0xra0/ModernCommandFramework

Repository files navigation

# Modern Command Framework (MCF) **MCF** is the next-generation command framework for Starfield, evolved from Custom Command Framework (CCF). Built with **libxe's updated CommonLibSF**, MCF provides a robust, modern API for registering custom console commands in Starfield. ## Features ✅ **Easy Command Registration** - Simple API for adding custom commands ✅ **Argument Parsing** - Automatic parsing with quote support ✅ **Console Integration** - Full integration with Starfield's console ✅ **Reference Selection** - Access to selected references ✅ **Form Lookup** - Hex string to form conversion ✅ **Modern C++** - Built with C++23 and modern standards ✅ **Updated CommonLibSF** - Using libxe's latest version ## What's New in MCF 2.0? ### From CCF to MCF - **Updated Library**: Built on libxe's CommonLibSF instead of old version - **No DKUtil Dependency**: Uses standard SFSE logging and hooking - **Modern Build System**: Supports both CMake and xmake - **Improved Performance**: Better memory management and faster command execution - **Enhanced API**: Cleaner, more maintainable codebase ### Technical Improvements 1. **CommonLibSF Update**: Using libxe's updated version for better compatibility 2. **Removed DKUtil**: Direct SFSE integration for cleaner dependencies 3. **xmake Support**: Alternative build system for faster builds 4. **Better Logging**: Standard spdlog integration 5. **Code Quality**: Modern C++23 features and best practices ## Installation ### For End Users 1. Download `ModernCommandFramework.dll` 2. Place in `Data/SFSE/Plugins/` 3. Launch Starfield via SFSE ### For Mod Developers To use MCF in your mod: 1. Copy `MCF_API.h` to your project 2. Include it: `#include "MCF_API.h"` 3. Register commands using the API ## API Usage ### Including MCF API ```cpp #include "MCF_API.h" ``` ### Registering a Command ```cpp void MyCommandCallback( const MCF::simple_array& args, const char* fullString, MCF::ConsoleInterface* console) { // Get selected reference auto ref = console->GetSelectedReference(); if (!ref) { console->PrintLn("No reference selected!"); return; } // Print to console console->PrintLn("Command executed successfully!"); // Prevent default command text print console->PreventDefaultPrint(); } // In your plugin initialization MCF::RegisterCommand("mycommand", MyCommandCallback); ``` ### Console Interface The `ConsoleInterface` provides these methods: ```cpp class ConsoleInterface { public: // Get currently selected reference in console virtual RE::NiPointer GetSelectedReference() = 0; // Convert hex string to form (e.g., "00000014" -> Player) virtual RE::TESForm* HexStrToForm(const simple_string_view& str) = 0; // Print a line to console virtual void PrintLn(const simple_string_view& text) = 0; // Prevent default "command text" from being printed virtual void PreventDefaultPrint() = 0; }; ``` ### Argument Handling ```cpp void CommandWithArgs( const MCF::simple_array& args, const char* fullString, MCF::ConsoleInterface* console) { if (args.size() < 1) { console->PrintLn("Usage: mycommand [arg2]"); return; } // Access arguments (0-indexed, command name is removed) std::string_view arg1 = args[0]; if (args.size() > 1) { std::string_view arg2 = args[1]; // Handle second argument } // Arguments support quoted strings with spaces // Example: mycommand "hello world" test // args[0] = "hello world" // args[1] = "test" } ``` ### Form Lookup Example ```cpp void LookupFormCommand( const MCF::simple_array& args, const char* fullString, MCF::ConsoleInterface* console) { if (args.size() < 1) { console->PrintLn("Usage: lookup "); return; } auto* form = console->HexStrToForm(args[0]); if (!form) { console->PrintLn("Invalid form ID!"); return; } auto name = form->GetName(); console->PrintLn(std::format("Found form: {}", name).c_str()); } ``` ## Building from Source ### Prerequisites - Visual Studio 2022 or later - CMake 3.21+ OR xmake 2.7.0+ - libxe's updated CommonLibSF - vcpkg (for CMake) or xrepo (for xmake) ### Building with CMake ```bash # Configure cmake --preset vs2022-windows # Build cmake --build build --config Release ``` ### Building with xmake ```bash # Configure and build xmake # Or for release build xmake f -m release xmake # Install to Starfield xmake install # Create package xmake package ``` ### xmake Advantages - **Faster builds**: Incremental compilation - **Simpler configuration**: No generator needed - **Built-in tasks**: install, package, clean - **Better dependency management**: Integrated xrepo ## Project Structure ``` MCF/ ├── src/ │ ├── MCF_API.h # Public API header │ ├── Commands.h # Command system interface │ ├── Commands.cpp # Command implementation │ ├── PCH.h # Precompiled header │ └── main.cpp # Plugin entry point ├── cmake/ # CMake configuration ├── CMakeLists.txt # CMake build file ├── xmake.lua # xmake build file ├── vcpkg.json # CMake dependencies └── README.md # This file ``` ## Command Registration Flow 1. **Your Plugin** calls `MCF::RegisterCommand(name, callback)` 2. **MCF** loads your command into registry 3. **User** types command in console 4. **MCF** intercepts console execution 5. **MCF** parses arguments and calls your callback 6. **Your Callback** executes with parsed arguments 7. **MCF** handles console output ## Examples ### Simple Command ```cpp void HelloWorld( const MCF::simple_array& args, const char* fullString, MCF::ConsoleInterface* console) { console->PrintLn("Hello, Starfield!"); } // Register MCF::RegisterCommand("hello", HelloWorld); // Use in console > hello Hello, Starfield! ``` ### Command with Selected Reference ```cpp void GetRefInfo( const MCF::simple_array& args, const char* fullString, MCF::ConsoleInterface* console) { auto ref = console->GetSelectedReference(); if (!ref) { console->PrintLn("No reference selected!"); return; } auto* base = ref->GetBaseObject(); if (base) { console->PrintLn(std::format( "Name: {}", base->GetName() ).c_str()); } } // Register MCF::RegisterCommand("refinfo", GetRefInfo); // Use in console (with reference selected) > refinfo Name: Container ``` ### Command with Arguments ```cpp void SetValue( const MCF::simple_array& args, const char* fullString, MCF::ConsoleInterface* console) { if (args.size() < 2) { console->PrintLn("Usage: setvalue "); return; } auto* form = console->HexStrToForm(args[0]); if (!form) { console->PrintLn("Invalid form ID!"); return; } try { int value = std::stoi(std::string{args[1]}); // Use the value... console->PrintLn(std::format( "Set value to {} for {}", value, form->GetName() ).c_str()); } catch (...) { console->PrintLn("Invalid value!"); } } // Register MCF::RegisterCommand("setvalue", SetValue); // Use in console > setvalue 00000014 100 Set value to 100 for Player ``` ## Comparison: CCF vs MCF | Feature | CCF 1.x | MCF 2.0 | |---------|---------|---------| | CommonLibSF | Old version | libxe's updated | | Dependencies | DKUtil | None (SFSE only) | | Build Systems | CMake only | CMake + xmake | | Logging | DKUtil | spdlog | | C++ Standard | C++20 | C++23 | | API Name | CCF::* | MCF::* | | DLL Name | CustomCommandFramework.dll | ModernCommandFramework.dll | ## Migration from CCF ### Code Changes ```cpp // Old (CCF) #include "CCF_API.h" CCF::RegisterCommand("cmd", callback); // New (MCF) #include "MCF_API.h" MCF::RegisterCommand("cmd", callback); ``` ### Build Changes 1. Update include path: `CCF_API.h` → `MCF_API.h` 2. Update namespace: `CCF::` → `MCF::` 3. Link against `ModernCommandFramework.dll` 4. Optional: Switch to xmake for faster builds ### Runtime Changes 1. Remove `CustomCommandFramework.dll` 2. Install `ModernCommandFramework.dll` 3. Your commands work identically! ## FAQ **Q: Is MCF compatible with CCF?** A: At the API level, yes (just change namespaces). DLL-wise, they're separate frameworks. **Q: Can I use both CCF and MCF?** A: No, they both hook the same console function. Use one or the other. **Q: Why xmake instead of CMake?** A: xmake offers faster builds, simpler configuration, and better dependency management. CMake is still fully supported. **Q: Do I need to rebuild my mod?** A: Yes, you need to recompile against MCF headers and link to MCF DLL. **Q: What about DKUtil?** A: MCF doesn't use DKUtil. It uses standard SFSE and spdlog directly. ## Technical Details ### Hooking MCF hooks the console's command execution function: - **Address**: `REL::ID(166307), offset 0xD2` - **Method**: Trampoline hook via SFSE - **Behavior**: Intercepts before vanilla command processing ### Argument Parsing - **Delimiter**: Space character - **Quotes**: Supports `"quoted strings with spaces"` - **Escaping**: Quote character as escape - **Case**: Command names are case-insensitive ### Thread Safety - **Registry**: Protected by `std::mutex` - **Execution**: Single-threaded (console thread) - **Callbacks**: Must be thread-safe if they spawn threads ## Performance - **Registration**: O(1) hash map lookup - **Execution**: ~0.1ms overhead per command - **Memory**: ~1KB per registered command - **Hooks**: Single trampoline hook (14 bytes) ## Limitations - **Console Only**: Commands work only in console, not via scripts - **Single Hook**: Only one framework can hook console at a time - **No Persistence**: Commands must be re-registered each game launch - **String Only**: Arguments are always strings (parse in your callback) ## Contributing Contributions welcome! Please ensure: - Code follows existing style - All CCF references changed to MCF - Works with libxe's CommonLibSF - Tests pass (if applicable) - Documentation updated ## Credits - **Original CCF**: Deweh (Snapdragon) - **MCF Development**: MCF Team - **CommonLibSF**: libxe and contributors - **SFSE**: SFSE Team ## License MCF inherits the license from CCF. See LICENSE file for details. ## Support For issues, questions, or feature requests: - **GitHub Issues**: [Repository Link] - **Discord**: [Community Server] - **Nexus Mods**: [Mod Page] --- **MCF**: Making console commands modern since 2026 🚀 Built with ❤️ and libxe's CommonLibSF # ModernCommandFramework- # ModernCommandFramework- --- ## Building (Linux → Windows cross-compile) This fork adds an [xmake](https://xmake.io) build that cross-compiles the SFSE plugin **from Linux** to a Windows x64 DLL, using `clang-cl` + `lld-link` + `llvm-rc` against an [xwin](https://github.com/Jake-Shadle/xwin)/MSVC Windows SDK tree (referenced here as `~/.vsxwin`). ```sh # Provide the dependencies under extern/ first (see below), then: xmake f -p windows -a x64 -m releasedbg --toolchain=clang-cl --sdk=$HOME/.vsxwin -y xmake build ``` Output: `build/windows/x64/releasedbg/ModernCommandFramework.dll`. Exports: `SFSEPlugin_Version`, `SFSEPlugin_Load`, `RegisterCommand`. ### Dependencies (not committed — `extern/` is git-ignored) - `extern/commonlibsf/` — CommonLibSF (libxse fork, runtime 1.16.244.0) + nested `commonlib-shared`, built from source. `spdlog` (v1.16.0, std_format + wchar) is pulled transitively via xrepo. `modules/detect/tools/find_rc.lua` (included) lets xmake locate an `rc` compiler on Linux. ## Credits Original mod by **mielu91m** — . This repository is a modified fork that adds the Linux cross-compile build system above (and ports a few call sites to the current CommonLibSF API); all mod functionality and design are the original author's work. ## License Distributed under the MIT License — see [LICENSE](LICENSE).

About

Modern Command Framework (MCF) — a replacement for CCF (Custom Command Framework) for Starfield. SFSE plugin with a console-command registration API, cross-compiled from Linux. Fork of mielu91m's mod adding an xmake build.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors