#!/bin/bash # The staging drill's static half (docs/runicnpc/PLAN.md D308). Never shipped. # # tools/drill/static.sh (WORK=drill-work BRANCH=staging by default) # # Needs no server. It downloads only RustDedicated_Data/Managed/*.dll from a Steam branch, and # Oxide's build for that branch, and: # # 1. asks whether Oxide's build has caught up with Rust's (tools/fieldlist --oxide-behind). Until it # has, an Oxide server on that branch dies at boot, so the drill waits for it (D317); # 2. regenerates the swap's field list (tools/fieldlist, D232) from Rust's own Assembly-CSharp.dll # and compares it with the one plugin/RunicNPC.cs carries; # 3. compiles plugin/RunicNPC.cs against those assemblies with Oxide's laid over them, as an Oxide # server would. Oxide's patched Assembly-CSharp.dll is needed: the plugin uses members Rust # ships non-public (IAISenses, for one). # # It writes $WORK/static.json and, beside it, oxide-behind.txt, fields.diff and compile.txt. In # static.json, oxideBehind is a count (-1: not measured), fields "same"/"differs", compile # "ok"/"errors", and either is "error" when its step could not run. The script exits 0 whenever it # ran; what it found is in static.json. DEPOTDOWNLOADER and DOTNET point at other copies (on # Windows, DEPOTDOWNLOADER=…/DepotDownloader.exe from Git Bash). set -uo pipefail ROOT=$(cd "$(dirname "$0")/../.." && pwd) WORK=${WORK:-$ROOT/drill-work} BRANCH=${BRANCH:-staging} if [ "$BRANCH" = public ]; then OXIDE_URL=${OXIDE_URL:-https://github.com/OxideMod/Oxide.Rust/releases/latest/download/Oxide.Rust-linux.zip} else OXIDE_URL=${OXIDE_URL:-https://downloads.oxidemod.com/artifacts/Oxide.Rust/$BRANCH/Oxide.Rust-linux.zip} fi DD_URL=https://github.com/SteamRE/DepotDownloader/releases/download/DepotDownloader_3.4.0/DepotDownloader-linux-x64.zip DOTNET=${DOTNET:-dotnet} mkdir -p "$WORK" rm -rf "$WORK/depot" "$WORK/oxide" "$WORK/refs" "$WORK/fields" rm -f "$WORK"/static.json "$WORK"/oxide-behind.txt "$WORK"/fields.diff "$WORK"/compile.txt managed=error behind=-1 fields=error compile=error note="" oxide_modified="" manifest="" manifest_date="" finish() { printf '{"branch":"%s","manifest":"%s","manifestDate":"%s","managed":"%s","oxideBehind":%s,"fields":"%s","compile":"%s","oxideModified":"%s","note":"%s"}\n' \ "$BRANCH" "$manifest" "$manifest_date" "$managed" "$behind" "$fields" "$compile" "$oxide_modified" "$note" > "$WORK/static.json" cat "$WORK/static.json" exit 0 } # Git Bash does not translate paths inside a response file; elsewhere this is the path as it is. native() { if command -v cygpath > /dev/null; then cygpath -m "$1"; else printf '%s' "$1"; fi; } # ---- Rust's managed assemblies, from Steam (no login: the dedicated server is anonymous) ---- DD=${DEPOTDOWNLOADER:-} if [ -z "$DD" ]; then curl -sSL --fail -o "$WORK/dd.zip" "$DD_URL" && unzip -oq "$WORK/dd.zip" -d "$WORK/dd" && chmod +x "$WORK/dd/DepotDownloader" \ || { note="DepotDownloader could not be fetched"; finish; } DD=$WORK/dd/DepotDownloader fi printf 'regex:^RustDedicated_Data/Managed/[^/]+\\.dll$\n' > "$WORK/files.txt" "$DD" -app 258550 -depot 258552 -branch "$BRANCH" -filelist "$WORK/files.txt" -dir "$WORK/depot" -os linux \ > "$WORK/depot.txt" 2>&1 MANAGED=$WORK/depot/RustDedicated_Data/Managed # Which build this is, from Steam itself: the Linux depot's manifest and its date. (api.steamcmd.net # answered a build id a day stale on 2026-10-06, so the drill does not ask it.) manifest=$(tr -d '\r' < "$WORK/depot.txt" | sed -n 's/^Manifest \([0-9]*\) (\(.*\))$/\1/p' | head -1) manifest_date=$(tr -d '\r' < "$WORK/depot.txt" | sed -n 's/^Manifest \([0-9]*\) (\(.*\))$/\2/p' | head -1) if [ ! -f "$MANAGED/Assembly-CSharp.dll" ]; then note="no Assembly-CSharp.dll from Steam's $BRANCH branch: $(tail -3 "$WORK/depot.txt" | tr '\n"' ' ')" finish fi managed=ok # ---- Oxide's build for the branch ---- oxide_modified=$(curl -sSIL "$OXIDE_URL" | tr -d '\r' | sed -n 's/^[Ll]ast-[Mm]odified: //p' | tail -1) if ! { curl -sSL --fail -o "$WORK/oxide.zip" "$OXIDE_URL" && unzip -oq "$WORK/oxide.zip" -d "$WORK/oxide"; }; then note="Oxide's $BRANCH build could not be downloaded" finish fi OXIDE_MANAGED=$WORK/oxide/RustDedicated_Data/Managed # The tool is built once, here, so a build failure is not mistaken for anything it reports. if ! "$DOTNET" build "$ROOT/tools/fieldlist" -c Release -o "$WORK/fieldlist" > "$WORK/fieldlist-build.txt" 2>&1; then note="tools/fieldlist did not build: $(grep -m1 'error' "$WORK/fieldlist-build.txt" | tr '"' ' ')" finish fi FIELDLIST=("$DOTNET" "$WORK/fieldlist/FieldList.dll") # ---- 1. Has Oxide caught up? ---- if "${FIELDLIST[@]}" --oxide-behind "$MANAGED" "$OXIDE_MANAGED/Assembly-CSharp.dll" > "$WORK/oxide-behind.txt" 2>&1; then behind=$(sed -n 's/^behind \([0-9]*\)$/\1/p' "$WORK/oxide-behind.txt" | head -1) behind=${behind:--1} fi # ---- 2. The swap's field list (D232) ---- mkdir -p "$WORK/fields" cp "$ROOT/plugin/RunicNPC.cs" "$WORK/fields/RunicNPC.cs" block() { sed -n '/\/\/ /,/\/\/ <\/fieldlist>/p' "$1" | grep -v "Generated by tools/fieldlist" | tr -d '\r'; } if "${FIELDLIST[@]}" "$MANAGED" "$WORK/fields/RunicNPC.cs" > "$WORK/fields/run.txt" 2>&1; then if diff -u --label carried <(block "$ROOT/plugin/RunicNPC.cs") --label "$BRANCH" <(block "$WORK/fields/RunicNPC.cs") > "$WORK/fields.diff"; then fields=same else fields=differs fi else cp "$WORK/fields/run.txt" "$WORK/fields.diff" fi # ---- 3. Compile against the branch with Oxide's build laid over ---- mkdir -p "$WORK/refs" cp "$MANAGED"/*.dll "$WORK/refs/" cp "$OXIDE_MANAGED"/*.dll "$WORK/refs/" # Oxide.References.dll bundles its own older copy of Newtonsoft.Json, which Oxide's compiler never # references; with it, every JObject is ambiguous. rm -f "$WORK/refs/Oxide.References.dll" DOTNET_DIR=$(dirname "$(readlink -f "$(command -v "$DOTNET")")") CSC=$(ls "$DOTNET_DIR"/sdk/*/Roslyn/bincore/csc.dll 2>/dev/null | tail -1) if [ -z "$CSC" ]; then note="no csc.dll under $DOTNET_DIR/sdk" finish fi # A response file: some 250 references outgrow a Windows command line. for f in "$WORK/refs"/*.dll; do printf '"-r:%s"\n' "$(native "$f")"; done > "$WORK/refs.rsp" if "$DOTNET" "$CSC" -nologo -noconfig -nostdlib -target:library -nowarn:1701,1702 -warn:0 \ -out:"$WORK/RunicNPC.dll" "@$WORK/refs.rsp" "$ROOT/plugin/RunicNPC.cs" > "$WORK/compile.txt" 2>&1; then compile=ok else compile=errors fi finish