Browse Source

Initial commit

Tyler Hallada 3 years ago
commit
87d9f2b882
5 changed files with 480 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 199 0
      Cargo.lock
  3. 14 0
      Cargo.toml
  4. 81 0
      src/README.md
  5. 185 0
      src/lib.rs

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
1
+/target

+ 199 - 0
Cargo.lock

@@ -0,0 +1,199 @@
1
+# This file is automatically @generated by Cargo.
2
+# It is not intended for manual editing.
3
+[[package]]
4
+name = "arrayref"
5
+version = "0.3.6"
6
+source = "registry+https://github.com/rust-lang/crates.io-index"
7
+checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
8
+
9
+[[package]]
10
+name = "arrayvec"
11
+version = "0.5.1"
12
+source = "registry+https://github.com/rust-lang/crates.io-index"
13
+checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
14
+
15
+[[package]]
16
+name = "autocfg"
17
+version = "1.0.0"
18
+source = "registry+https://github.com/rust-lang/crates.io-index"
19
+checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
20
+
21
+[[package]]
22
+name = "base64"
23
+version = "0.11.0"
24
+source = "registry+https://github.com/rust-lang/crates.io-index"
25
+checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
26
+
27
+[[package]]
28
+name = "blake2b_simd"
29
+version = "0.5.10"
30
+source = "registry+https://github.com/rust-lang/crates.io-index"
31
+checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
32
+dependencies = [
33
+ "arrayref",
34
+ "arrayvec",
35
+ "constant_time_eq",
36
+]
37
+
38
+[[package]]
39
+name = "cfg-if"
40
+version = "0.1.10"
41
+source = "registry+https://github.com/rust-lang/crates.io-index"
42
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
43
+
44
+[[package]]
45
+name = "constant_time_eq"
46
+version = "0.1.5"
47
+source = "registry+https://github.com/rust-lang/crates.io-index"
48
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
49
+
50
+[[package]]
51
+name = "crossbeam-utils"
52
+version = "0.7.2"
53
+source = "registry+https://github.com/rust-lang/crates.io-index"
54
+checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
55
+dependencies = [
56
+ "autocfg",
57
+ "cfg-if",
58
+ "lazy_static",
59
+]
60
+
61
+[[package]]
62
+name = "dirs"
63
+version = "3.0.1"
64
+source = "registry+https://github.com/rust-lang/crates.io-index"
65
+checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff"
66
+dependencies = [
67
+ "dirs-sys",
68
+]
69
+
70
+[[package]]
71
+name = "dirs-sys"
72
+version = "0.3.5"
73
+source = "registry+https://github.com/rust-lang/crates.io-index"
74
+checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
75
+dependencies = [
76
+ "libc",
77
+ "redox_users",
78
+ "winapi",
79
+]
80
+
81
+[[package]]
82
+name = "getrandom"
83
+version = "0.1.14"
84
+source = "registry+https://github.com/rust-lang/crates.io-index"
85
+checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
86
+dependencies = [
87
+ "cfg-if",
88
+ "libc",
89
+ "wasi",
90
+]
91
+
92
+[[package]]
93
+name = "lazy_static"
94
+version = "1.4.0"
95
+source = "registry+https://github.com/rust-lang/crates.io-index"
96
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
97
+
98
+[[package]]
99
+name = "libc"
100
+version = "0.2.71"
101
+source = "registry+https://github.com/rust-lang/crates.io-index"
102
+checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
103
+
104
+[[package]]
105
+name = "log"
106
+version = "0.4.8"
107
+source = "registry+https://github.com/rust-lang/crates.io-index"
108
+checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
109
+dependencies = [
110
+ "cfg-if",
111
+]
112
+
113
+[[package]]
114
+name = "redox_syscall"
115
+version = "0.1.56"
116
+source = "registry+https://github.com/rust-lang/crates.io-index"
117
+checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
118
+
119
+[[package]]
120
+name = "redox_users"
121
+version = "0.3.4"
122
+source = "registry+https://github.com/rust-lang/crates.io-index"
123
+checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431"
124
+dependencies = [
125
+ "getrandom",
126
+ "redox_syscall",
127
+ "rust-argon2",
128
+]
129
+
130
+[[package]]
131
+name = "rust-argon2"
132
+version = "0.7.0"
133
+source = "registry+https://github.com/rust-lang/crates.io-index"
134
+checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
135
+dependencies = [
136
+ "base64",
137
+ "blake2b_simd",
138
+ "constant_time_eq",
139
+ "crossbeam-utils",
140
+]
141
+
142
+[[package]]
143
+name = "rust-skse-plugin"
144
+version = "0.1.0"
145
+dependencies = [
146
+ "dirs",
147
+ "log",
148
+ "simple-logging",
149
+]
150
+
151
+[[package]]
152
+name = "simple-logging"
153
+version = "2.0.2"
154
+source = "registry+https://github.com/rust-lang/crates.io-index"
155
+checksum = "b00d48e85675326bb182a2286ea7c1a0b264333ae10f27a937a72be08628b542"
156
+dependencies = [
157
+ "lazy_static",
158
+ "log",
159
+ "thread-id",
160
+]
161
+
162
+[[package]]
163
+name = "thread-id"
164
+version = "3.3.0"
165
+source = "registry+https://github.com/rust-lang/crates.io-index"
166
+checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1"
167
+dependencies = [
168
+ "libc",
169
+ "redox_syscall",
170
+ "winapi",
171
+]
172
+
173
+[[package]]
174
+name = "wasi"
175
+version = "0.9.0+wasi-snapshot-preview1"
176
+source = "registry+https://github.com/rust-lang/crates.io-index"
177
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
178
+
179
+[[package]]
180
+name = "winapi"
181
+version = "0.3.9"
182
+source = "registry+https://github.com/rust-lang/crates.io-index"
183
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
184
+dependencies = [
185
+ "winapi-i686-pc-windows-gnu",
186
+ "winapi-x86_64-pc-windows-gnu",
187
+]
188
+
189
+[[package]]
190
+name = "winapi-i686-pc-windows-gnu"
191
+version = "0.4.0"
192
+source = "registry+https://github.com/rust-lang/crates.io-index"
193
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
194
+
195
+[[package]]
196
+name = "winapi-x86_64-pc-windows-gnu"
197
+version = "0.4.0"
198
+source = "registry+https://github.com/rust-lang/crates.io-index"
199
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

+ 14 - 0
Cargo.toml

@@ -0,0 +1,14 @@
1
+[package]
2
+name = "rust-skse-plugin"
3
+version = "0.1.0"
4
+authors = ["Tyler Hallada <tyler@hallada.net>"]
5
+edition = "2018"
6
+
7
+[dependencies]
8
+log = "0.4"
9
+simple-logging = "2.0"
10
+dirs = "3.0"
11
+
12
+[lib]
13
+name = "RustSKSEPlugin"
14
+crate-type = ["cdylib"]

+ 81 - 0
src/README.md

@@ -0,0 +1,81 @@
1
+# Rust SKSE Plugin
2
+
3
+This is my mostly failed attempt at creating a fully-Rust [SKSE (Skyrim Script Extender)](https://skse.silverlock.org/) plugin.
4
+
5
+## Build and Install
6
+
7
+1. Have Skyrim Special Edition version 1.5.97 or later installed with SKSE build 2.0.17 installed.
8
+2. Checkout the repo and `cd rust-skse-plugin`.
9
+3. Run `cargo build`.
10
+4. Copy `target/debug/RustSKSEPlugin.dll` to `<Skyrim Special Edition install folder>\Data\SKSE\Plugins\`.
11
+5. Start Skyrim Special Edition by running `skse_loader.exe`.
12
+6. Open `<Your Documents Directory>\My Games\Skyrim Special Edition\SKSE\skse64.log`, and you should see this plugin being loaded:
13
+    ```
14
+    checking plugin E:\Program Files (x86)\Steam\steamapps\common\Skyrim Special Edition\Data\SKSE\Plugins\\RustSKSEPlugin.dll
15
+    plugin E:\Program Files (x86)\Steam\steamapps\common\Skyrim Special Edition\Data\SKSE\Plugins\\RustSKSEPlugin.dll (00000001 My Rust SKSE Plugin 00000001) loaded correctly
16
+    ```
17
+7. Open `<Your Documents Directory>\My Games\Skyrim Special Edition\SKSE\RustSKSEPlugin.log`, and your should see some logs from the Rust SKSE plugin:
18
+    ```
19
+    [00:00:00.000] (8c4) INFO   SKSEPlugin_Query begin
20
+    [00:00:00.000] (8c4) INFO   SKSEPlugin_Query successful
21
+    [00:00:00.000] (8c4) INFO   SKSEPlugin_Load begin
22
+    [00:00:00.000] (8c4) INFO   queryInterfaceFunc: 0x7ffea09c695a
23
+    [00:00:00.000] (8c4) INFO   queryInterface: 0x7ffea0e4eba8
24
+    [00:00:00.000] (8c4) INFO   papyrusInterface: 0x7ffea0e4eba8
25
+    [00:00:00.000] (8c4) INFO   papyrusRegister: 0x7ffea09d2011
26
+    [00:00:00.000] (8c4) INFO   SKSEPlugin_Load successful
27
+    [00:00:01.740] (7838) INFO   RegisterFuncs begin
28
+    [00:00:01.740] (7838) INFO   a_registry: 0x19510f93780
29
+    [00:00:01.740] (7838) INFO   registerFunction: 0x7ff7c85384f0
30
+    [00:00:01.740] (7838) INFO   nativeFunction: 0x26ebebf108
31
+    [00:00:01.740] (7838) INFO   RegisterFuncs successful
32
+    ```
33
+
34
+## Where I got stuck
35
+
36
+I was trying to replicate [Ryan-rsm-McKenzie's Native SKSE64 Papyrus Interface Implementation example](https://gist.github.com/Ryan-rsm-McKenzie/cabb89a80abb09663a1288cafddd21e6) in Rust via it's FFI to C. I was able to successfully register the plugin with SKSE and even acquire a reference to the `PapyrusInterface`.
37
+
38
+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++](https://stackoverflow.com/a/45540511).
39
+
40
+## Failed attempt at using cpp crate
41
+
42
+The [`cpp`](https://docs.rs/cpp/0.5.4/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`.
43
+
44
+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](https://github.com/Ryan-rsm-McKenzie/CommonLibSSE/wiki/Getting-Started#building-your-first-plugin) and make sure everything builds in Visual Studio first.
45
+
46
+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:
47
+
48
+```rust
49
+extern crate cpp_build;
50
+
51
+fn main() {
52
+    let include_path = r#"<path to skse src>\skse64\CommonLibSSE\include"#;
53
+    let lib_path = r#"<path to skse src>\skse64\x64\Debug"#;
54
+    let vcpkg_include_path = r#"<path to vcpkg folder>\installed\x64-windows-custom\include"#;
55
+    cpp_build::Config::new().include(include_path).include(vcpkg_include_path).flag("/std:c++17").build("src/lib.rs");
56
+    println!("cargo:rustc-link-search={}", lib_path);
57
+    println!("cargo:rustc-link-lib=CommonLibSSE");
58
+}
59
+```
60
+
61
+And, add this to `src/lib.rs`:
62
+
63
+```rust
64
+#[macro_use]
65
+extern crate cpp;
66
+
67
+cpp!{{
68
+    #include "RE/Skyrim.h"
69
+    #include "REL/Relocation.h"
70
+    #include "SKSE/SKSE.h"
71
+}}
72
+```
73
+
74
+Now you can replace the `SKSEPapyrusInterface` struct with something like:
75
+```rust
76
+cpp_class!(pub unsafe struct PapyrusInterface as "SKSE::PapyrusInterface");
77
+```
78
+
79
+## What I'm probably going to do instead
80
+
81
+Abandon my dream of a pure Rust SKSE plugin and just write a normal C++ one (with [CommonLibSSE](https://github.com/Ryan-rsm-McKenzie/CommonLibSSE)) which will execute functions exported from a Rust dll (with [cbindgen](https://crates.io/crates/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.

+ 185 - 0
src/lib.rs

@@ -0,0 +1,185 @@
1
+#![allow(non_snake_case)]
2
+extern crate simple_logging;
3
+extern crate log;
4
+extern crate dirs;
5
+
6
+use std::ffi::CString;
7
+use std::os::raw::{c_char, c_void};
8
+use std::path::Path;
9
+use log::{info, error, LevelFilter};
10
+
11
+type PluginHandle = u32;
12
+#[repr(C)]
13
+pub struct SKSEInterface {
14
+    pub skseVersion: u32,
15
+    pub runtimeVersion: u32,
16
+    pub editorVersion: u32,
17
+    pub isEditor: u32,
18
+    pub QueryInterface: fn(KInterface) -> *const c_void,
19
+    pub GetPluginHandle: fn() -> PluginHandle,
20
+    pub GetReleaseIndex: fn() -> u32,
21
+}
22
+
23
+#[repr(C)]
24
+pub struct PluginInfo {
25
+    pub infoVersion: u32,
26
+    pub name: *mut c_char,
27
+    pub version: u32,
28
+}
29
+
30
+#[repr(C)]
31
+pub struct VMValue;
32
+
33
+#[repr(C)]
34
+pub struct VMState;
35
+
36
+#[repr(C)]
37
+pub struct NativeFunction {
38
+    InitParams: fn(*const VMClassRegistry) -> (),
39
+    Run: fn(*const VMValue, *const VMClassRegistry, u32, *const VMValue, *const VMState) -> bool,
40
+}
41
+
42
+impl NativeFunction {
43
+    fn new() -> NativeFunction {
44
+        NativeFunction {
45
+            InitParams: |registry| {
46
+                info!("NativeFunction InitParams begin");
47
+                info!("registry: {:?}", registry);
48
+                info!("NativeFunction InitParams successful");
49
+            },
50
+            Run: |baseValue, registry, stackId, resultValue, state| {
51
+                info!("NativeFunction Run begin");
52
+                info!("baseValue: {:?}", baseValue);
53
+                info!("registry: {:?}", registry);
54
+                info!("stackId: {:?}", stackId);
55
+                info!("resultValue: {:?}", resultValue);
56
+                info!("state: {:?}", state);
57
+                info!("NativeFunction Run successful");
58
+                true
59
+            },
60
+        }
61
+    }
62
+}
63
+
64
+
65
+#[repr(C)]
66
+pub struct VMClassInfo;
67
+
68
+#[repr(C)]
69
+pub struct VMIdentifier;
70
+
71
+#[repr(C)]
72
+pub struct StringCacheRef;
73
+
74
+#[repr(C)]
75
+pub struct VMClassRegistry {
76
+    pub Unk_01: fn(c_void) -> c_void,
77
+    pub PrintToDebugLog: fn(*const c_char, u32, u32) -> c_void,
78
+    pub Unk_03: fn(c_void) -> c_void,
79
+    pub Unk_04: fn(c_void) -> c_void,
80
+    pub Unk_05: fn(c_void) -> c_void,
81
+    pub Unk_06: fn(c_void) -> c_void,
82
+    pub Unk_07: fn(c_void) -> c_void,
83
+    pub RegisterForm: fn(u32, *const c_char) -> c_void,
84
+    pub Unk_09: fn(c_void) -> c_void,
85
+    pub GetFormTypeClass: fn(u32, *const VMClassInfo) -> bool,
86
+    pub Unk_0B: fn(c_void) -> c_void,
87
+    pub Unk_0C: fn(c_void) -> c_void,
88
+    pub Unk_0D: fn(*const StringCacheRef, *const u32) -> bool,
89
+    pub Unk_0E: fn(c_void) -> c_void,
90
+    pub Unk_0F: fn(c_void) -> c_void,
91
+    pub Unk_10: fn(c_void) -> c_void,
92
+    pub Unk_11: fn(c_void) -> c_void,
93
+    pub Unk_12: fn(c_void) -> c_void,
94
+    pub Unk_13: fn(c_void) -> c_void,
95
+    pub Unk_14: fn(c_void) -> c_void,
96
+    pub Unk_15: fn(*const StringCacheRef, *const VMIdentifier) -> bool,
97
+    pub CreateArray: fn(*const VMValue, u32, *const VMValue) -> bool,
98
+    pub Unk_17: fn(c_void) -> c_void,
99
+    pub RegisterFunction: fn(*const NativeFunction) -> c_void,
100
+    // more...
101
+}
102
+
103
+type RegisterFunctions = fn(*const VMClassRegistry) -> bool;
104
+
105
+#[repr(C)]
106
+pub struct SKSEPapyrusInterface {
107
+    pub interfaceVersion: u32,
108
+    pub Register: fn(*const RegisterFunctions) -> bool,
109
+}
110
+
111
+#[repr(u32)]
112
+pub enum KInterface {
113
+	Invalid = 0,
114
+	Scaleform,
115
+	Papyrus,
116
+	Serialization,
117
+	Task,
118
+	Messaging,
119
+	Object,
120
+	Max,
121
+}
122
+
123
+const fn make_exe_version(major: u32, minor: u32, build: u32, sub: u32) -> u32 {
124
+    (((major) & 0xFF) << 24) | (((minor) & 0xFF) << 16) | (((build) & 0xFFF) << 4) | ((sub) & 0xF)
125
+}
126
+
127
+const RUNTIME_VERSION_1_5_97: u32 = make_exe_version(1, 5, 97, 0);
128
+
129
+unsafe fn RegisterFuncs(a_registry: *const VMClassRegistry) -> bool {
130
+    info!("RegisterFuncs begin");
131
+    info!("a_registry: {:?}", a_registry);
132
+    let registerFunction = (*a_registry).RegisterFunction;
133
+    info!("registerFunction: {:?}", registerFunction);
134
+    let nativeFunction: *const NativeFunction = &NativeFunction::new();
135
+    info!("nativeFunction: {:?}", nativeFunction);
136
+    // This is as far as I can get. I have no idea how to register a native papyrus function.
137
+    // Doesn't work, throws exception "Access violation executing location"
138
+    // registerFunction(nativeFunction);
139
+    info!("RegisterFuncs successful");
140
+    true
141
+}
142
+
143
+#[no_mangle]
144
+pub unsafe extern "C" fn SKSEPlugin_Query(a_skse: *const SKSEInterface, a_info: *mut PluginInfo) -> bool {
145
+    let mut log_dir = dirs::document_dir().expect("could not get Documents directory");
146
+    log_dir.push(Path::new(r#"My Games\Skyrim Special Edition\SKSE\RustSKSEPlugin.log"#));
147
+    simple_logging::log_to_file(log_dir, LevelFilter::Info).unwrap();
148
+    info!("SKSEPlugin_Query begin");
149
+
150
+    (*a_info).infoVersion = 1;
151
+    (*a_info).name = CString::new("My Rust SKSE Plugin").expect("could not create CString").into_raw();
152
+    (*a_info).version = 1;
153
+
154
+    if (*a_skse).isEditor != 0 {
155
+        error!("Loaded in editor, marking as incompatible!");
156
+        return false;
157
+    } else if (*a_skse).runtimeVersion != RUNTIME_VERSION_1_5_97 {
158
+        error!("Unsupported runtime version {}!", (*a_skse).runtimeVersion);
159
+        return false;
160
+    }
161
+    
162
+    info!("SKSEPlugin_Query successful");
163
+    true
164
+}
165
+
166
+#[no_mangle]
167
+pub unsafe extern "C" fn SKSEPlugin_Load(a_skse: *const SKSEInterface) -> bool {
168
+    info!("SKSEPlugin_Load begin");
169
+    
170
+    let queryInterfaceFunc = (*a_skse).QueryInterface;
171
+    info!("queryInterfaceFunc: {:?}", queryInterfaceFunc);
172
+    let queryInterface = queryInterfaceFunc(KInterface::Papyrus);
173
+    info!("queryInterface: {:?}", queryInterface);
174
+    let papyrusInterface: *const SKSEPapyrusInterface = queryInterface as *const SKSEPapyrusInterface;
175
+    info!("papyrusInterface: {:?}", papyrusInterface);
176
+    let papyrusRegister = (*papyrusInterface).Register;
177
+    info!("papyrusRegister: {:?}", papyrusRegister);
178
+    if !papyrusRegister(RegisterFuncs as *const RegisterFunctions) {
179
+        error!("RegisterFuncs returned false!");
180
+        return false;
181
+    }
182
+
183
+    info!("SKSEPlugin_Load successful");
184
+    true
185
+}