My mostly failed attempt at creating a fully-Rust SKSE plugin

Tyler Hallada 9969fa2841 Typo fix 2 years ago
src Move README.md to correct folder 2 years ago
.gitignore Initial commit 2 years ago
Cargo.lock Initial commit 2 years ago
Cargo.toml Initial commit 2 years ago
README.md Typo fix 2 years ago

README.md

Rust SKSE Plugin

This is my mostly failed attempt at creating a fully-Rust SKSE (Skyrim Script Extender) plugin.

Build and Install

  1. Have Skyrim Special Edition version 1.5.97 or later installed with SKSE build 2.0.17 installed.
  2. Checkout the repo and cd rust-skse-plugin.
  3. Run cargo build.
  4. Copy target/debug/RustSKSEPlugin.dll to <Skyrim Special Edition install folder>\Data\SKSE\Plugins\.
  5. Start Skyrim Special Edition by running skse_loader.exe.
  6. Open <Your Documents Directory>\My Games\Skyrim Special Edition\SKSE\skse64.log, and you should see this plugin being loaded: checking plugin E:\Program Files (x86)\Steam\steamapps\common\Skyrim Special Edition\Data\SKSE\Plugins\\RustSKSEPlugin.dll plugin E:\Program Files (x86)\Steam\steamapps\common\Skyrim Special Edition\Data\SKSE\Plugins\\RustSKSEPlugin.dll (00000001 My Rust SKSE Plugin 00000001) loaded correctly
  7. Open <Your Documents Directory>\My Games\Skyrim Special Edition\SKSE\RustSKSEPlugin.log, and you should see some logs from the Rust SKSE plugin: [00:00:00.000] (8c4) INFO SKSEPlugin_Query begin [00:00:00.000] (8c4) INFO SKSEPlugin_Query successful [00:00:00.000] (8c4) INFO SKSEPlugin_Load begin [00:00:00.000] (8c4) INFO queryInterfaceFunc: 0x7ffea09c695a [00:00:00.000] (8c4) INFO queryInterface: 0x7ffea0e4eba8 [00:00:00.000] (8c4) INFO papyrusInterface: 0x7ffea0e4eba8 [00:00:00.000] (8c4) INFO papyrusRegister: 0x7ffea09d2011 [00:00:00.000] (8c4) INFO SKSEPlugin_Load successful [00:00:01.740] (7838) INFO RegisterFuncs begin [00:00:01.740] (7838) INFO a_registry: 0x19510f93780 [00:00:01.740] (7838) INFO registerFunction: 0x7ff7c85384f0 [00:00:01.740] (7838) INFO nativeFunction: 0x26ebebf108 [00:00:01.740] (7838) INFO RegisterFuncs successful

Where I got stuck

I was trying to replicate Ryan-rsm-McKenzie's Native SKSE64 Papyrus Interface Implementation example in Rust via its FFI to C. I was able to successfully register the plugin with SKSE and even acquire a reference to the PapyrusInterface.

However, it seems like the code needs to call a C++ constructor (e.g. new NativeFunction0<StaticFunctionTag, BSFixedString>("HelloWorld", "MyClass", HelloWorld, a_registry)) in order to register a new native Papyrus function. Unfortunately, calling a C++ constructor requires FFI with C++, and Rust does not support FFI with C++.

Failed attempt at using cpp crate

The cpp crate may provide a way to interface with the SKSE C++ classes, but I'm too inexperienced with C++ to figure out how to compile skse64 in the build.rs.

I was able to get CommonLibSSE to compile, but I could not figure out how to convert the abstract RE::BSScript::IVirtualMachine class that the RegisterFuncs function recieves into something useable on the Rust side. That work is not included in the source, but to get that to work you will need to first follow the CommonLibSSE setup and make sure everything builds in Visual Studio first.

Then checkout this repo alongside the CommonLibSSE folder inside skse64. Add the cpp and cpp_build crates to Cargo.toml and create this build.rs file at the root of the project:

extern crate cpp_build;

fn main() {
    let include_path = r#"<path to skse src>\skse64\CommonLibSSE\include"#;
    let lib_path = r#"<path to skse src>\skse64\x64\Debug"#;
    let vcpkg_include_path = r#"<path to vcpkg folder>\installed\x64-windows-custom\include"#;
    cpp_build::Config::new().include(include_path).include(vcpkg_include_path).flag("/std:c++17").build("src/lib.rs");
    println!("cargo:rustc-link-search={}", lib_path);
    println!("cargo:rustc-link-lib=CommonLibSSE");
}

And, add this to src/lib.rs:

#[macro_use]
extern crate cpp;

cpp!{{
    #include "RE/Skyrim.h"
    #include "REL/Relocation.h"
    #include "SKSE/SKSE.h"
}}

Now you can replace the SKSEPapyrusInterface struct with something like:

cpp_class!(pub unsafe struct PapyrusInterface as "SKSE::PapyrusInterface");

What I'm probably going to do instead

Abandon my dream of a pure Rust SKSE plugin and just write a normal C++ one (with CommonLibSSE) which will execute functions exported from a Rust dll (with cbindgen) inside a native Papyrus function callback. This requires the user to place the Rust-generated dll file in their Skyrim install directory in addition to placing the C++ generated SKSE plugin dll in the SKSE plugins directory.