From 5bd6ae7df04a914761b72b0db9ca8c1be4cd2e85 Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Thu, 25 May 2023 22:51:04 +0200 Subject: [PATCH] WIP --- .ci/image/Dockerfile | 108 +++++++++++++++++++++++++++++++++ Cargo.toml | 10 +++ Dockerfile | 35 +++++++++++ Justfile | 12 ++++ lib/dt_p2p/Cargo.toml | 1 + lib/dt_p2p/build.rs | 7 +++ lib/dt_p2p/src/lib.rs | 53 +++++++++++++++- lib/dt_p2p/src/stingray_sdk.rs | 78 +++++++++++++++++++++++- 8 files changed, 300 insertions(+), 4 deletions(-) create mode 100644 .ci/image/Dockerfile create mode 100644 Dockerfile create mode 100644 Justfile diff --git a/.ci/image/Dockerfile b/.ci/image/Dockerfile new file mode 100644 index 0000000..735a09c --- /dev/null +++ b/.ci/image/Dockerfile @@ -0,0 +1,108 @@ +FROM rust:slim-bullseye + +RUN set -eux; \ + apt-get update; \ + apt-get install --no-install-recommends -y \ + build-essential \ + curl \ + gpg \ + jq \ + libatk1.0-dev \ + libclang-13-dev \ + libglib2.0-dev \ + libgtk-3-dev \ + libpango1.0-dev \ + libssl-dev \ + libzstd-dev \ + pkg-config; \ + apt-get remove -y --auto-remove; \ + rm -rf /var/lib/apt/lists/*; \ + rustup default nightly + +# https://jake-shadle.github.io/xwin/ + +ENV KEYRINGS /usr/local/share/keyrings +ARG XWIN_VERSION=0.2.11 +ARG XWIN_PREFIX="xwin-$XWIN_VERSION-x86_64-unknown-linux-musl" +ARG LLVM_VERSION=16 + +ADD https://apt.llvm.org/llvm-snapshot.gpg.key /root/llvm-snapshot.gpg.key +ADD https://dl.winehq.org/wine-builds/winehq.key /root/winehq.key +ADD https://github.com/Jake-Shadle/xwin/releases/download/$XWIN_VERSION/$XWIN_PREFIX.tar.gz /root/$XWIN_PREFIX.tar.gz + +RUN set -eux; \ + mkdir -p $KEYRINGS; \ + # clang/lld/llvm + gpg --dearmor > $KEYRINGS/llvm.gpg < /root/llvm-snapshot.gpg.key; \ + # wine + gpg --dearmor > $KEYRINGS/winehq.gpg < /root/winehq.key; \ + echo "deb [signed-by=$KEYRINGS/llvm.gpg] http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-${LLVM_VERSION} main" > /etc/apt/sources.list.d/llvm.list; \ + echo "deb [signed-by=$KEYRINGS/winehq.gpg] https://dl.winehq.org/wine-builds/debian/ bullseye main" > /etc/apt/sources.list.d/winehq.list; \ + dpkg --add-architecture i386; \ + apt-get update; \ + apt-get install --no-install-recommends -y \ + libclang-${LLVM_VERSION}-dev \ + gcc-mingw-w64-x86-64 \ + clang-${LLVM_VERSION} \ + llvm-${LLVM_VERSION} \ + lld-${LLVM_VERSION} \ + winehq-staging \ + tar; \ + # ensure that clang/clang++ are callable directly + ln -s clang-${LLVM_VERSION} /usr/bin/clang && ln -s clang /usr/bin/clang++ && ln -s lld-${LLVM_VERSION} /usr/bin/ld.lld; \ + # We also need to setup symlinks ourselves for the MSVC shims because they aren't in the debian packages + ln -s clang-${LLVM_VERSION} /usr/bin/clang-cl && ln -s llvm-ar-${LLVM_VERSION} /usr/bin/llvm-lib && ln -s lld-link-${LLVM_VERSION} /usr/bin/lld-link; \ + # Verify the symlinks are correct + clang++ -v; \ + ld.lld -v; \ + # Doesn't have an actual -v/--version flag, but it still exits with 0 + llvm-lib -v; \ + clang-cl -v; \ + lld-link --version; \ + # Use clang instead of gcc when compiling and linking binaries targeting the host (eg proc macros, build files) + update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100; \ + 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 component add rust-src; \ + # Install xwin to cargo/bin via github release. Note you could also just use `cargo install xwin`. + tar -xzv -f /root/$XWIN_PREFIX.tar.gz -C /usr/local/cargo/bin --strip-components=1 $XWIN_PREFIX/xwin; \ + # Splat the CRT and SDK files to /xwin/crt and /xwin/sdk respectively + xwin --accept-license splat --include-debug-libs --output /xwin; \ + # Remove unneeded files to reduce image size + apt-get remove -y --auto-remove; \ + rm -rf \ + .xwin-cache \ + /usr/local/cargo/bin/xwin \ + /root/$XWIN_PREFIX.tar.gz \ + /var/lib/apt/lists/* \ + /root/*.key; + +# Note that we're using the full target triple for each variable instead of the +# simple CC/CXX/AR shorthands to avoid issues when compiling any C/C++ code for +# build dependencies that need to compile and execute in the host environment +ENV CC_x86_64_pc_windows_msvc="clang-cl" \ + CXX_x86_64_pc_windows_msvc="clang-cl" \ + AR_x86_64_pc_windows_msvc="llvm-lib" \ + # wine can be quite spammy with log messages and they're generally uninteresting + WINEDEBUG="-all" \ + # Use wine to run test executables + CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUNNER="wine" \ + # Note that we only disable unused-command-line-argument here since clang-cl + # doesn't implement all of the options supported by cl, but the ones it doesn't + # are _generally_ not interesting. + CL_FLAGS="-Wno-unused-command-line-argument -fuse-ld=lld-link /imsvc/xwin/crt/include /imsvc/xwin/sdk/include/ucrt /imsvc/xwin/sdk/include/um /imsvc/xwin/sdk/include/shared" \ + # Let cargo know what linker to invoke if you haven't already specified it + # in a .cargo/config.toml file + CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER="lld-link" \ + CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS="-Lnative=/xwin/crt/lib/x86_64 -Lnative=/xwin/sdk/lib/um/x86_64 -Lnative=/xwin/sdk/lib/ucrt/x86_64" + +# These are separate since docker/podman won't transform environment variables defined in the same ENV block +ENV CFLAGS_x86_64_pc_windows_msvc="$CL_FLAGS" \ + CXXFLAGS_x86_64_pc_windows_msvc="$CL_FLAGS" + +# Run wineboot just to setup the default WINEPREFIX so we don't do it every +# container run +RUN wine wineboot --init + +WORKDIR /src/dt_p2p diff --git a/Cargo.toml b/Cargo.toml index d157fa5..5d0d097 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,13 @@ members = [ # [profile.dev.package.backtrace] # opt-level = 3 + +[profile.release] +strip = "debuginfo" + +# The MSVC toolchain cannot handle LTO properly. Some symbol related to +# panic unwind would always be missing. +# So we use a separate profile for when we can compile with LTO. +[profile.release-lto] +inherits = "release" +lto = true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0d0a546 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +FROM dtmt-ci-base-msvc + +# Create dummy crates and copy their Cargo.toml, so that dependencies can be cached +RUN set -e; \ + cargo new --bin crates/dtmt; \ + cargo new --bin crates/dtmm; \ + cargo new --lib lib/dtmt-shared; \ + cargo new --lib lib/nexusmods; \ + cargo new --lib lib/sdk; \ + cargo new --lib lib/serde_sjson; \ + cargo new --lib lib/steamlocate-rs + +COPY Cargo.toml Cargo.lock /src/dtmt/ +COPY crates/dtmt/Cargo.toml /src/dtmt/crates/dtmt/ +COPY crates/dtmm/Cargo.toml /src/dtmt/crates/dtmm/ +COPY lib/dtmt-shared/Cargo.toml /src/dtmt/lib/dtmt-shared/ +COPY lib/nexusmods/Cargo.toml /src/dtmt/lib/nexusmods/ +COPY lib/sdk/Cargo.toml /src/dtmt/lib/sdk/ +COPY lib/serde_sjson/Cargo.toml /src/dtmt/lib/serde_sjson/ +COPY lib/steamlocate-rs/Cargo.toml /src/dtmt/lib/steamlocate-rs/ + +# Crates with build scripts cannot be split that way, but they shouldn't change too often +COPY lib/luajit2-sys /src/dtmt/lib/luajit2-sys +COPY lib/oodle /src/dtmt/lib/oodle +# color-eyre needs to be copied, too, then, as it's used by `oodle` +COPY lib/color-eyre /src/dtmt/lib/color-eyre +COPY --from=dtmt-ci-base-msvc /src/*.lib /src/dtmt/lib/oodle/ + +RUN cargo build --release --target x86_64-pc-windows-msvc --locked -Zbuild-std +RUN rm -r crates lib + +COPY . /src/dtmt +COPY --from=dtmt-ci-base-msvc /src/*.lib /src/dtmt/lib/oodle/ + +RUN cargo build --release --target x86_64-pc-windows-msvc --frozen -Zbuild-std diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..3374390 --- /dev/null +++ b/Justfile @@ -0,0 +1,12 @@ +image_name := 'dt_p2p-ci-base' + +ci-build: + docker run --rm -ti --user $(id -u) -v ./:/src/dt_p2p {{ image_name }} cargo --color always build --target x86_64-pc-windows --locked -Zbuild-std + +build-image: + docker build -f .ci/Dockerfile . + +ci-image: + docker build -t {{ image_name }} .ci/image + docker tag {{ image_name }} registry.sclu1034.dev/{{ image_name }} + docker push registry.sclu1034.dev/{{ image_name }} diff --git a/lib/dt_p2p/Cargo.toml b/lib/dt_p2p/Cargo.toml index 97ff094..e3197e6 100644 --- a/lib/dt_p2p/Cargo.toml +++ b/lib/dt_p2p/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +libc = "0.2.144" [lib] crate-type = ["cdylib", "lib"] diff --git a/lib/dt_p2p/build.rs b/lib/dt_p2p/build.rs index 9cfda19..29e2022 100644 --- a/lib/dt_p2p/build.rs +++ b/lib/dt_p2p/build.rs @@ -10,6 +10,7 @@ fn main() { let bindings = bindgen::Builder::default() .header(HEADER_NAME) + .rustified_enum("PluginApiID") .clang_arg("-Istingray_sdk/") .parse_callbacks(Box::new(bindgen::CargoCallbacks)) .generate() @@ -19,4 +20,10 @@ fn main() { bindings .write_to_file(out_path.join("bindings.rs")) .expect("Couldn't write bindings!"); + + if cfg!(debug_assertions) { + bindings + .write_to_file(out_path.join("../../../bindings.rs")) + .expect("Couldn't write bindings to debug path"); + } } diff --git a/lib/dt_p2p/src/lib.rs b/lib/dt_p2p/src/lib.rs index 9ebdc78..148bb9f 100644 --- a/lib/dt_p2p/src/lib.rs +++ b/lib/dt_p2p/src/lib.rs @@ -1,6 +1,53 @@ mod stingray_sdk; -#[cfg(test)] -mod tests { - use super::*; +use std::ffi::c_char; +use std::ffi::CString; + +use stingray_sdk::GetApiFunction; +use stingray_sdk::PluginApi; +use stingray_sdk::PluginApiID; + +use crate::stingray_sdk::LoggingApi; + +const PLUGIN_NAME: &str = "dt_p2p"; + +#[no_mangle] +pub extern "C" fn get_name() -> *const c_char { + let s = CString::new(PLUGIN_NAME).expect("Failed to create CString from plugin name"); + s.as_ptr() +} + +#[no_mangle] +pub extern "C" fn setup_game(get_engine_api: GetApiFunction) { + println!("setup_game"); + + let log = LoggingApi::get(get_engine_api); + + log.info( + PLUGIN_NAME, + format!("Hello, world! This is {}!", PLUGIN_NAME), + ); +} + +#[no_mangle] +pub extern "C" fn shutdown_game() { + println!("shutdown_game"); + + // log.info(PLUGIN_NAME, format!("Goodbye, world!", PLUGIN_NAME)); +} + +#[no_mangle] +pub extern "C" fn get_plugin_api(id: PluginApiID) -> *mut PluginApi { + if id == PluginApiID::PLUGIN_API_ID { + let api = PluginApi { + get_name: Some(get_name), + setup_game: Some(setup_game), + shutdown_game: Some(shutdown_game), + ..Default::default() + }; + + Box::into_raw(Box::new(api)) + } else { + std::ptr::null_mut() + } } diff --git a/lib/dt_p2p/src/stingray_sdk.rs b/lib/dt_p2p/src/stingray_sdk.rs index b6ccf39..841c092 100644 --- a/lib/dt_p2p/src/stingray_sdk.rs +++ b/lib/dt_p2p/src/stingray_sdk.rs @@ -4,4 +4,80 @@ #![allow(clippy::type_complexity)] #![allow(unused)] -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +mod bindings { + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} + +use std::ffi::CString; +use std::os::raw::c_char; +use std::os::raw::c_void; + +pub use bindings::GetApiFunction; +pub use bindings::PluginApi; +pub use bindings::PluginApiID; + +impl std::default::Default for PluginApi { + fn default() -> Self { + Self { + version: 65, + flags: 3, + fn_0: std::ptr::null_mut(), + fn_1: std::ptr::null_mut(), + fn_2: std::ptr::null_mut(), + fn_3: std::ptr::null_mut(), + fn_4: std::ptr::null_mut(), + fn_5: std::ptr::null_mut(), + setup_game: None, + update_game: None, + shutdown_game: None, + fn_9: std::ptr::null_mut(), + fn_10: std::ptr::null_mut(), + fn_11: std::ptr::null_mut(), + get_name: None, + } + } +} + +fn get_engine_api(f: GetApiFunction, id: PluginApiID) -> *mut c_void { + let f = if cfg!(debug_assertions) { + f.expect("'GetApiFunction' is always passed by the engine") + } else { + // `Option::unwrap` still generates several instructions in + // optimized code. + unsafe { f.unwrap_unchecked() } + }; + + unsafe { f(id as u32) } +} + +pub struct LoggingApi { + info: unsafe extern "C" fn(*const c_char, *const c_char), + warning: unsafe extern "C" fn(*const c_char, *const c_char), + error: unsafe extern "C" fn(*const c_char, *const c_char), +} + +impl LoggingApi { + pub fn get(f: GetApiFunction) -> Self { + let api = unsafe { + let api = get_engine_api(f, PluginApiID::LOGGING_API_ID); + api as *mut bindings::LoggingApi + }; + + unsafe { + Self { + info: (*api).info.unwrap_unchecked(), + warning: (*api).warning.unwrap_unchecked(), + error: (*api).error.unwrap_unchecked(), + } + } + } + + pub fn info(&self, system: impl Into>, message: impl Into>) { + let f = self.info; + let system = CString::new(system).expect("Invalid CString"); + let message = CString::new(message).expect("Invalid CString"); + unsafe { + f(system.as_ptr(), message.as_ptr()); + } + } +}