diff --git a/.ci/pipelines/pr.yml b/.ci/pipelines/pr.yml index 4deb271..f21409c 100644 --- a/.ci/pipelines/pr.yml +++ b/.ci/pipelines/pr.yml @@ -4,16 +4,12 @@ resource_types: - name: gitea-package type: registry-image source: - repository: registry.sclu1034.dev/gitea-package - username: ((registry_user)) - password: ((registry_password)) + repository: registry.local:5000/gitea-package - name: gitea-status type: registry-image source: - repository: registry.sclu1034.dev/gitea-status - username: ((registry_user)) - password: ((registry_password)) + repository: registry.local:5000/gitea-status resources: - name: repo @@ -88,8 +84,6 @@ jobs: file: repo/.ci/tasks/clippy.yml vars: forgejo_api_key: ((forgejo_api_key)) - registry_user: ((registry_user)) - registry_password: ((registry_password)) - name: build @@ -130,8 +124,6 @@ jobs: pr: ((pr)) forgejo_url: ((forgejo_url)) forgejo_api_key: ((forgejo_api_key)) - registry_user: ((registry_user)) - registry_password: ((registry_password)) - load_var: version_number reveal: true diff --git a/.ci/pipelines/set-pr-pipelines.yml b/.ci/pipelines/set-pr-pipelines.yml index 792faad..0712b03 100644 --- a/.ci/pipelines/set-pr-pipelines.yml +++ b/.ci/pipelines/set-pr-pipelines.yml @@ -5,23 +5,17 @@ resource_types: - name: gitea-package type: registry-image source: - repository: registry.sclu1034.dev/gitea-package - username: ((registry_user)) - password: ((registry_password)) + repository: registry.local:5000/gitea-package - name: gitea-status type: registry-image source: - repository: registry.sclu1034.dev/gitea-status - username: ((registry_user)) - password: ((registry_password)) + repository: registry.local:5000/gitea-status - name: gitea-pr type: registry-image source: - repository: registry.sclu1034.dev/gitea-pr - username: ((registry_user)) - password: ((registry_password)) + repository: registry.local:5000/gitea-pr resources: @@ -81,8 +75,6 @@ jobs: forgejo_url: ((forgejo_url)) owner: ((owner)) repo: ((repo)) - registry_user: ((registry_user)) - registry_password: ((registry_password)) instance_vars: number: ((.:pr.number)) @@ -123,8 +115,6 @@ jobs: pr: "" forgejo_url: ((forgejo_url)) forgejo_api_key: ((forgejo_api_key)) - registry_user: ((registry_user)) - registry_password: ((registry_password)) - load_var: version_number reveal: true diff --git a/.ci/tasks/build.yml b/.ci/tasks/build.yml index d0afe6a..e8652c4 100644 --- a/.ci/tasks/build.yml +++ b/.ci/tasks/build.yml @@ -6,10 +6,8 @@ image_resource: name: rust-xwin type: registry-image source: - repository: registry.sclu1034.dev/rust-xwin-ci + repository: registry.local:5000/rust-xwin-ci tag: latest - username: ((registry_user)) - password: ((registry_password)) inputs: - name: repo diff --git a/.ci/tasks/clippy.yml b/.ci/tasks/clippy.yml index 690954b..149ce54 100644 --- a/.ci/tasks/clippy.yml +++ b/.ci/tasks/clippy.yml @@ -6,10 +6,8 @@ image_resource: name: rust-xwin-ci type: registry-image source: - repository: registry.sclu1034.dev/rust-xwin-ci + repository: registry.local:5000/rust-xwin-ci tag: latest - username: ((registry_user)) - password: ((registry_password)) inputs: - name: repo diff --git a/.renovaterc b/.renovaterc index 4cf1302..b36f3b4 100644 --- a/.renovaterc +++ b/.renovaterc @@ -1,10 +1,11 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended", - ":combinePatchMinorReleases", - ":enableVulnerabilityAlerts" - ], - "prConcurrentLimit": 10, - "branchPrefix": "renovate/" + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":combinePatchMinorReleases", + ":enableVulnerabilityAlerts", + ":rebaseStalePrs" + ], + "prConcurrentLimit": 10, + "branchPrefix": "renovate/" } diff --git a/Cargo.lock b/Cargo.lock index 229a6fb..8ea5eb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.72.0" +version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ "bitflags", "cexpr", @@ -69,7 +69,6 @@ version = "0.1.0" dependencies = [ "bindgen", "libc", - "log", ] [[package]] @@ -95,9 +94,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" @@ -111,9 +110,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" diff --git a/Cargo.toml b/Cargo.toml index 1eda32c..36d3a21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,10 +7,9 @@ edition = "2021" [dependencies] libc = "0.2.144" -log = { version = "0.4.27", features = ["release_max_level_info"] } [build-dependencies] -bindgen = "0.72.0" +bindgen = "0.71.0" [lib] crate-type = ["cdylib", "lib"] diff --git a/Dockerfile b/Dockerfile index 59e2799..1a5a968 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ RUN set -eux; \ # And to keep that to a minimum, we still delete the stuff we don't need. rm -rf /root/.xwin-cache; -FROM rust:1.87.0-slim-bullseye AS final +FROM rust:slim-bullseye AS final ARG LLVM_VERSION=18 ENV KEYRINGS=/usr/local/share/keyrings @@ -37,7 +37,6 @@ RUN set -eux; \ apt-get install -y --no-install-recommends \ ca-certificates \ gpg \ - make \ ; \ mkdir -p $KEYRINGS; \ gpg --dearmor > $KEYRINGS/llvm.gpg < /root/llvm-snapshot.gpg.key; \ @@ -70,6 +69,7 @@ RUN set -eux; \ update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100; \ update-alternatives --install /usr/bin/ld ld /usr/bin/ld.lld 100; \ rustup target add x86_64-pc-windows-msvc; \ + rustup default stable-x86_64-pc-windows-msvc; \ rustup component add rust-src; \ rustup update; \ apt-get remove -y --auto-remove \ diff --git a/Justfile b/Justfile index 723421a..4ce14c9 100644 --- a/Justfile +++ b/Justfile @@ -1,111 +1,11 @@ -set dotenv-load -set dotenv-filename := '.envrc' -set dotenv-required -set allow-duplicate-variables - -fly_target := "main" -image_name := "dt-plugin-builder" - -steam_library := env("steam_library") -game_dir := steam_library / "steamapps" / "common" / "Warhammer 40,000 DARKTIDE" -log_dir := env("APPDATA") / "Fatshark" / "Darktide" / "console_logs" - -game_dir := if path_exists(game_dir) == true { - game_dir -} else { - error("Game directory does not exist, please set correct library path: " + game_dir) -} - -proton_dir := home_directory() / ".steam" / "steam" - -export STEAM_COMPAT_DATA_PATH := steam_library / "steamapps" / "compatdata" / "1361210" -export SteamAppId := "1361210" - -release := env("RELEASE", "false") - image: - docker build -t {{image_name}} . + docker build -t dt-plugin-builder . -# Compile the Windows DLL through a Docker image -[unix] -compile: +build: docker run \ --rm \ -t \ --user "$(id -u):$(id -g)" \ - -v $HOME/.cargo/registry:/usr/local/cargo/registry \ - -v $HOME/.cargo/git:/usr/local/cargo/git \ - -v $HOME/.rustup/toolchains:/usr/local/rustup/toolchains \ -v ./:/src/plugin \ - {{image_name}} \ - cargo build -Zbuild-std --target x86_64-pc-windows-msvc {{ if release != "false" { "--release" } else { "" } }} - -# Compile the DLL and install it to the game's plugin directory -[unix] -build: compile - cp target/x86_64-pc-windows-msvc/{{ if release != "false" { "release" } else { "debug" } }}/dt_p2p.dll "{{game_dir}}/binaries/plugins/dt_p2p_pluginw64_release.dll" - -# Open the newest game log file -[unix] -log: - #!/bin/sh - set -e - file="$(\ls "{{log_dir}}" | tail -1)" - echo "$(tput bold)Opening log file $file$(tput sgr0)" - exec bat "{{log_dir}}/$file" - -# Run the game through Proton -[unix] -game: - #!/bin/sh - set -e - cd "{{game_dir / "binaries"}}" - proton waitforexitandrun ./Darktide.exe \ - -eac-untrusted \ - --bundle-dir ../bundle \ - --ini settings \ - --backend-auth-service-url https://bsp-auth-prod.atoma.cloud \ - --backend-title-service-url https://bsp-td-prod.atoma.cloud \ - -game \ - -launcher_verification_passed_crashify_property false - -# Run the game -# -# Untested! -[windows] -game: - #!cmd.exe - cd "{{game_dir / "binaries"}}" - ./Darktide.exe \ - -eac-untrusted \ - --bundle-dir ../bundle \ - --ini settings \ - --backend-auth-service-url https://bsp-auth-prod.atoma.cloud \ - --backend-title-service-url https://bsp-td-prod.atoma.cloud \ - -game \ - -launcher_verification_passed_crashify_property false - -# Compile the plugin through a Docker image -# -# Make sure to run `just image` first, to build the image. -# Untested: I don't know, if this works with Windows' Docker Desktop. -# I also don't know if/where cargo and rustup keep their caches on Windows. -[windows] -compile-docker: - docker run \ - --rm \ - -t \ - -v ./:/src/plugin \ - {{image_name}} \ - cargo build -Zbuild-std --target x86_64-pc-windows-msvc {{ if release != "false" { "--release" } else { "" } }} - -set-base-pipeline: - fly -t {{fly_target}} set-pipeline \ - --pipeline dt-plugin-template-pr \ - --config .ci/pipelines/set-pr-pipelines.yml \ - -v forgejo_url=https://git.sclu1034.dev \ - -v forgejo_api_key=${FORGEJO_API_KEY} \ - -v registry_user=${REGISTRY_USER} \ - -v registry_password=${REGISTRY_PASSWORD} \ - -v owner=bitsquid_dt \ - -v repo=dt-plugin-template + dt-plugin-builder \ + cargo build -Zbuild-std --target x86_64-pc-windows-msvc diff --git a/src/lib.rs b/src/lib.rs index 9a6f298..9cdc2b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,11 @@ use std::ffi::{c_char, CString}; use std::sync::OnceLock; -mod lua; mod plugin; mod stingray_sdk; use plugin::Plugin; -use stingray_sdk::{GetApiFunction, LoggingApi, LuaApi, PluginApi, PluginApiID}; +use stingray_sdk::{GetApiFunction, PluginApi, PluginApiID}; // TODO: Change these /// The name that the plugin is registered to the engine as. @@ -16,20 +15,6 @@ pub const PLUGIN_NAME: &str = "dt-plugin-template"; pub const MODULE_NAME: &str = "DTPluginTemplate"; static PLUGIN: OnceLock = OnceLock::new(); -static LOGGER: OnceLock = OnceLock::new(); -static LUA: OnceLock = OnceLock::new(); - -/// A macro to make accessing global statics a little more convenient. -#[macro_export] -macro_rules! global { - ($name:ident) => {{ - if cfg!(debug_assertions) { - $name.get().expect("global has not been initialized") - } else { - unsafe { $name.get().unwrap_unchecked() } - } - }}; -} #[no_mangle] pub extern "C" fn get_name() -> *const c_char { @@ -39,25 +24,26 @@ pub extern "C" fn get_name() -> *const c_char { #[no_mangle] pub extern "C" fn setup_game(get_engine_api: GetApiFunction) { - let logger = LOGGER.get_or_init(|| LoggingApi::get(get_engine_api)); - log::set_logger(logger).expect("Failed to set global logger."); - log::set_max_level(log::LevelFilter::Info); - - let _ = LUA.get_or_init(|| LuaApi::get(get_engine_api)); - - let plugin = PLUGIN.get_or_init(Plugin::new); + let plugin = Plugin::new(get_engine_api); plugin.setup_game(); + PLUGIN + .set(plugin) + .expect("Failed to initalize global plugin object."); } #[no_mangle] pub extern "C" fn shutdown_game() { - let plugin = global!(PLUGIN); + // Safety: The engine ensures that `setup_game` was called before this, so `PLUGIN` has been + // initialized. + let plugin = unsafe { PLUGIN.get().unwrap_unchecked() }; plugin.shutdown_game(); } #[no_mangle] pub extern "C" fn update_game(dt: f32) { - let plugin = global!(PLUGIN); + // Safety: The engine ensures that `setup_game` was called before this, so `PLUGIN` has been + // initialized. + let plugin = unsafe { PLUGIN.get().unwrap_unchecked() }; plugin.update_game(dt); } diff --git a/src/lua.rs b/src/lua.rs deleted file mode 100644 index b4ad22a..0000000 --- a/src/lua.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::stingray_sdk::lua_State; -use crate::{LUA, global}; - -pub extern "C" fn do_something(l: *mut lua_State) -> i32 { - let lua = global!(LUA); - - if let Some(name) = lua.tolstring(l, 1) { - lua.pushstring( - l, - format!("[do_something] Hello from Rust, {}", name.to_string_lossy()), - ); - 1 - } else { - 0 - } -} diff --git a/src/plugin.rs b/src/plugin.rs index 9f85d92..ec861eb 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,23 +1,47 @@ -use log::info; +use crate::stingray_sdk::{lua_State, GetApiFunction, LoggingApi, LuaApi}; +use crate::{MODULE_NAME, PLUGIN, PLUGIN_NAME}; -use crate::{LUA, MODULE_NAME, PLUGIN_NAME, global, lua}; +pub(crate) struct Plugin { + pub log: LoggingApi, + pub lua: LuaApi, +} -pub(crate) struct Plugin {} +extern "C" fn do_something(l: *mut lua_State) -> i32 { + // Safety: Plugin must have been initialized for this to be registered as module + // function. + let plugin = unsafe { PLUGIN.get().unwrap_unchecked() }; + + if let Some(name) = plugin.lua.tolstring(l, 1) { + plugin.lua.pushstring( + l, + format!("[do_something] Hello from Rust, {}", name.to_string_lossy()), + ); + 1 + } else { + 0 + } +} impl Plugin { - pub fn new() -> Self { - Self {} + pub fn new(get_engine_api: GetApiFunction) -> Self { + let log = LoggingApi::get(get_engine_api); + let lua = LuaApi::get(get_engine_api); + Self { log, lua } } pub fn setup_game(&self) { - info!("[setup_game] Hello, world! This is {}!", PLUGIN_NAME); + self.log.info( + PLUGIN_NAME, + format!("[setup_game] Hello, world! This is {}!", PLUGIN_NAME), + ); - let lua = global!(LUA); - lua.add_module_function(MODULE_NAME, "do_something", lua::do_something); + self.lua + .add_module_function(MODULE_NAME, "do_something", do_something); } pub fn shutdown_game(&self) { - info!("[shutdown_game] Goodbye, world!"); + self.log + .info(PLUGIN_NAME, "[shutdown_game] Goodbye, world!"); } pub fn update_game(&self, _dt: f32) {} diff --git a/src/plugin_api.h b/src/plugin_api.h index 6cb4c3c..331ac6a 100644 --- a/src/plugin_api.h +++ b/src/plugin_api.h @@ -1,5 +1,5 @@ #pragma once -// This is manually updated and minified PluginApi based on the publicly +// This is a manually updated and minified PluginApi based on the publicly // available sources at: https://github.com/AutodeskGames/stingray-plugin // From // https://github.com/thewhitegoatcb/rawray/blob/master/rawray/PluginApi128.h @@ -59,7 +59,7 @@ enum PluginApiID { CAMERA_API_ID = 38, END_OF_ENGINE_RESERVED_RANGE = 65535, - /* API IDs in the range 0--65535 are reserved by the engine. If you want to + /* API IDs in the range 0-65535 are reserved by the engine. If you want to provide your own API in your plugin, we suggest using a hash of the API's name as ID. */ }; @@ -83,19 +83,13 @@ struct PluginApi { loaded. */ void (*loaded)(GetApiFunction get_engine_api); - // Seemingly not called by current versions of the engine, at least in release - // mode. /* Called at the start of a "hot reload" of the plugin DLL. Should return a pointer to a serialized "state" for the plugin.*/ void *(*start_reload)(GetApiFunction get_engine_api); - // Seemingly not called by current versions of the engine, at least in release - // mode. /* Called just before the plugin is unloaded. */ void (*unloaded)(); - // Seemingly not called by current versions of the engine, at least in release - // mode. /* Called to finalized the "hot reload" after the plugin has been reloaded with the new code. Should restore the state from the serialized state data. Note that it is the responsibility of the plugin to free the state data. */ @@ -229,8 +223,9 @@ struct LuaApi { int (*checkstack)(lua_State *L, int sz); void (*xmove)(lua_State *from, lua_State *to, int n); + const void *(*fnx1)(lua_State *L, int idx); + /* Access functions */ - int (*isnil)(lua_State *L, int idx); int (*isnumber)(lua_State *L, int idx); int (*isstring)(lua_State *L, int idx); int (*iscfunction)(lua_State *L, int idx); @@ -318,6 +313,8 @@ struct LuaApi { int (*gethookmask)(lua_State *L); int (*gethookcount)(lua_State *L); + // int (*fnx3) (lua_State* L); + /* Library functions */ void (*lib_openlib)(lua_State *L, const char *libname, const luaL_Reg *l, int nup); @@ -402,8 +399,6 @@ struct LuaApi { /* Gets a Unit at the specified index. */ CApiUnit *(*getunit)(lua_State *L, int i); - void *(*getunknown)(lua_State *L, int i); - /* Gets the Lua state where the main scripts execute. */ lua_State *(*getscriptenvironmentstate)(); @@ -428,10 +423,8 @@ struct LuaApi { /* Returns true if the stack entry is a UnitReference. */ int (*isunitreference)(lua_State *L, int i); - size_t (*objlen2)(lua_State *L, int idx); - /* Reserved for expansion of the API. */ - // void *reserved[30]; + void *reserved[32]; }; struct LoggingApi { diff --git a/src/stingray_sdk.rs b/src/stingray_sdk.rs index 9fde865..e8e2599 100644 --- a/src/stingray_sdk.rs +++ b/src/stingray_sdk.rs @@ -13,18 +13,22 @@ use std::ffi::CString; use std::os::raw::c_char; use std::os::raw::c_void; +use bindings::lua_CFunction; +pub use bindings::lua_State; pub use bindings::GetApiFunction; pub use bindings::PluginApi; pub use bindings::PluginApiID; -use bindings::lua_CFunction; -pub use bindings::lua_State; -use log::Level; + +// The API version. This must match with the game you're building the plugin for. +const STINGRAY_PLUGIN_VERSION: u32 = 69; +// A set of bitflags. The value `3` is known to work for Lua plugins. +const STINGRAY_PLUGIN_FLAGS: u32 = 3; impl std::default::Default for PluginApi { fn default() -> Self { Self { - version: 69, - flags: 3, + version: STINGRAY_PLUGIN_VERSION, + flags: STINGRAY_PLUGIN_FLAGS, setup_game: None, update_game: None, shutdown_game: None, @@ -54,11 +58,12 @@ fn get_engine_api(f: GetApiFunction, id: PluginApiID) -> *mut c_void { #[cfg(not(debug_assertions))] fn get_engine_api(f: GetApiFunction, id: PluginApiID) -> *mut c_void { - // `Option::unwrap` still generates several instructions in - // optimized code. - let f = unsafe { f.unwrap_unchecked() }; - - unsafe { f(id as u32) } + unsafe { + // `Option::unwrap` still generates several instructions in + // optimized code. + let f = f.unwrap_unchecked(); + f(id as u32) + } } pub struct LoggingApi { @@ -108,29 +113,6 @@ impl LoggingApi { } } -impl log::Log for LoggingApi { - fn enabled(&self, metadata: &log::Metadata) -> bool { - true - } - - fn log(&self, record: &log::Record) { - if !self.enabled(record.metadata()) { - return; - } - - let line = if let Some(s) = record.args().as_str() { - format!("[{}] {}", record.level(), s) - } else { - format!("[{}] {}", record.level(), record.args()) - }; - - self.info(record.target(), line); - } - - // The engine does not expose a method to flush the log - fn flush(&self) {} -} - #[repr(i32)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum LuaType {