initial commit
This commit is contained in:
commit
a2922b8bad
59 changed files with 2684583 additions and 0 deletions
45
.forgejo/workflows/backfill.yml
Normal file
45
.forgejo/workflows/backfill.yml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
name: backfill
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
game:
|
||||
description: "Game key (cs2 or dota2)"
|
||||
required: true
|
||||
default: cs2
|
||||
names:
|
||||
description: 'Names JSON — array of {name, class, slot}'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
backfill:
|
||||
runs-on: s2-runner
|
||||
env:
|
||||
GAME: ${{ github.event.inputs.game }}
|
||||
RELEASE_BASE: ${{ github.server_url }}/${{ github.repository }}/releases/download
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Fetch the model
|
||||
run: |
|
||||
mkdir -p in
|
||||
curl -fsSL -o in/model-$GAME.json "$RELEASE_BASE/$GAME-latest/model-$GAME.json"
|
||||
|
||||
- name: Write the names file
|
||||
run: printf '%s' "$NAMES" > names.json
|
||||
env:
|
||||
NAMES: ${{ github.event.inputs.names }}
|
||||
|
||||
- name: Build the deriver
|
||||
run: cargo build --release
|
||||
|
||||
- name: Backfill
|
||||
run: |
|
||||
./target/release/source2rosetta --game "$GAME" backfill \
|
||||
--corpus-model in/model-$GAME.json \
|
||||
--names names.json \
|
||||
--out history.json
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: backfill-${{ env.GAME }}
|
||||
path: history.json
|
||||
75
.forgejo/workflows/ci.yml
Normal file
75
.forgejo/workflows/ci.yml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
tags: ['gen-v*']
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: s2-runner
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- name: cargo fmt
|
||||
run: cargo fmt --all --check
|
||||
- name: cargo clippy (advisory)
|
||||
run: cargo clippy --all-targets --all-features --message-format=short
|
||||
env:
|
||||
RUSTFLAGS: ""
|
||||
- name: cargo doc (no deps)
|
||||
run: cargo doc --no-deps --document-private-items
|
||||
env:
|
||||
RUSTDOCFLAGS: "-D warnings"
|
||||
|
||||
fuzz:
|
||||
runs-on: s2-runner
|
||||
env:
|
||||
RUSTFLAGS: ""
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- name: Clear artifacts from any earlier run
|
||||
run: rm -rf fuzz/artifacts
|
||||
- name: Fuzz — 5 targets x 2 workers x 30s
|
||||
run: bash fuzz.sh 30 2
|
||||
- name: Fail on any crash / timeout / OOM
|
||||
run: |
|
||||
found=$(find fuzz/artifacts -type f \( -name 'crash-*' -o -name 'timeout-*' -o -name 'oom-*' \) 2>/dev/null || true)
|
||||
if [ -n "$found" ]; then
|
||||
echo "fuzzing produced artifacts:"; echo "$found" | sed 's/^/ /'
|
||||
echo "triage: cargo +nightly fuzz tmin <target> <artifact>"
|
||||
exit 1
|
||||
fi
|
||||
echo "no crashes / timeouts / OOMs — the offline derivation held on every explored input."
|
||||
|
||||
test:
|
||||
runs-on: s2-runner
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- name: cargo test
|
||||
run: cargo test --release
|
||||
- name: Build the gen binary (it lives in the core crate, not the workspace default build)
|
||||
run: cargo build --release -p source2rosetta-core
|
||||
- name: Package the gen binary
|
||||
if: startsWith(github.ref, 'refs/tags/gen-v')
|
||||
run: |
|
||||
mkdir -p dist
|
||||
cp target/release/source2rosetta-gen dist/
|
||||
strip dist/source2rosetta-gen || true
|
||||
(cd dist && sha256sum source2rosetta-gen > source2rosetta-gen.sha256)
|
||||
|
||||
- name: Publish the gen release
|
||||
if: startsWith(github.ref, 'refs/tags/gen-v')
|
||||
uses: https://code.forgejo.org/actions/forgejo-release@v2
|
||||
with:
|
||||
direction: upload
|
||||
url: ${{ github.server_url }}
|
||||
repo: ${{ github.repository }}
|
||||
tag: ${{ github.ref_name }}
|
||||
release-dir: dist
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
override: true
|
||||
release-notes: "`source2rosetta-gen` ${{ github.ref_name }} — renders a published gamedata release into your framework's format: CounterStrikeSharp, Metamod/SourceMod, ModSharp, Swiftly, Plugify, or a typed C# SDK. Download it, `chmod +x`, and point it at the `gamedata-<game>.json` / `netvars-<game>.json` from a per-game release (`cs2-latest`, `dota2-latest`). Usage: see `crates/source2rosetta-core/README.md`. Linux x86-64."
|
||||
132
.forgejo/workflows/contribution.yml
Normal file
132
.forgejo/workflows/contribution.yml
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
name: contribution
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'mappings/contributions/**'
|
||||
|
||||
concurrency:
|
||||
group: contribution-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
contribution:
|
||||
runs-on: s2-runner
|
||||
env:
|
||||
STEAM_APPS: /home/cs2/.steam/SteamApps
|
||||
RELEASE_BASE: ${{ github.server_url }}/${{ github.repository }}/releases/download
|
||||
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Resolve the affected game and its paths
|
||||
run: |
|
||||
changed=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" -- 'mappings/contributions/**')
|
||||
game=""
|
||||
if echo "$changed" | grep -q 'contributions/csgo/'; then game=cs2; fi
|
||||
if echo "$changed" | grep -q 'contributions/dota/'; then
|
||||
if [ -n "$game" ]; then echo "PR touches both games — split it into one PR per game"; exit 1; fi
|
||||
game=dota2
|
||||
fi
|
||||
if [ -z "$game" ]; then echo "no contributions/<game>/ files changed"; exit 1; fi
|
||||
case "$game" in cs2) key=csgo; appid=730 ;; dota2) key=dota; appid=570 ;; esac
|
||||
|
||||
# The manifest carries the installed buildid + the install folder name (spaces and all).
|
||||
manifest="$STEAM_APPS/appmanifest_$appid.acf"
|
||||
[ -f "$manifest" ] || { echo "no manifest at $manifest — check STEAM_APPS"; exit 1; }
|
||||
installdir=$(grep -oP '"installdir"\s+"\K[^"]+' "$manifest")
|
||||
buildid=$(grep -oP '"buildid"\s+"\K[0-9]+' "$manifest")
|
||||
game_dir="$STEAM_APPS/common/$installdir/game"
|
||||
[ -d "$game_dir" ] || { echo "expected a game tree at '$game_dir' — check STEAM_APPS"; exit 1; }
|
||||
{
|
||||
echo "GAME=$game"
|
||||
echo "KEY=$key"
|
||||
echo "APPID=$appid"
|
||||
echo "GAME_DIR=$game_dir"
|
||||
echo "BUILDID=$buildid"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
- name: Next patch number for this build
|
||||
run: |
|
||||
# The game-update run published <game>-<buildid>-0; each contribution on that build bumps the patch.
|
||||
tags=$(curl -fsSL -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "$API/releases?limit=100" | jq -r '.[].tag_name')
|
||||
max=0
|
||||
while read -r t; do
|
||||
case "$t" in
|
||||
"$GAME-$BUILDID-"*) p=${t##*-}; if [ "$p" -gt "$max" ] 2>/dev/null; then max=$p; fi ;;
|
||||
esac
|
||||
done <<< "$tags"
|
||||
echo "PATCH=$((max + 1))" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Fetch current model + seed
|
||||
# Stored gzipped on the release (see derive.yml); the deriver reads plain JSON.
|
||||
run: |
|
||||
mkdir -p in dist work
|
||||
curl -fsSL -o in/model.gz "$RELEASE_BASE/$GAME-latest/model-$GAME.json.gz"
|
||||
gunzip -c in/model.gz > "in/model-$GAME.json"
|
||||
curl -fsSL -o in/seed.gz "$RELEASE_BASE/$GAME-latest/seed-$GAME.json.gz"
|
||||
gunzip -c in/seed.gz > "seed-$GAME.json"
|
||||
|
||||
- name: Fold the PR's contributions into the seed's snapshot
|
||||
run: |
|
||||
python3 - "$KEY" "seed-$GAME.json" "seed-$GAME.patched.json" <<'PY'
|
||||
import json, sys, glob, os
|
||||
key, seed_in, seed_out = sys.argv[1:4]
|
||||
seed = json.load(open(seed_in))
|
||||
contribs = dict(seed.get("contributions") or {})
|
||||
for f in glob.glob(f"mappings/contributions/{key}/*.json"):
|
||||
contribs[os.path.basename(f)] = json.load(open(f))
|
||||
seed["contributions"] = contribs
|
||||
json.dump(seed, open(seed_out, "w"))
|
||||
PY
|
||||
|
||||
- name: Build the deriver
|
||||
run: cargo build --release
|
||||
|
||||
- name: Produce — validate the contribution live (no model fold; the build is unchanged)
|
||||
run: |
|
||||
# The model already contains this build (derive folded it), and the forward-only guard needs the
|
||||
# target to sort strictly after the model's latest. "<buildid>-<patch>" does: it shares the buildid
|
||||
# prefix and is longer, and the next update's buildid still sorts above it.
|
||||
ln -sfn "$GAME_DIR" "work/$BUILDID-$PATCH"
|
||||
./target/release/source2rosetta --game "$GAME" produce \
|
||||
--seed "seed-$GAME.patched.json" \
|
||||
--corpus-model "in/model-$GAME.json" \
|
||||
--target "work/$BUILDID-$PATCH" \
|
||||
--game-dir "$GAME_DIR" \
|
||||
--version "$GAME-$BUILDID-$PATCH" \
|
||||
--out-dir dist
|
||||
cp "seed-$GAME.patched.json" "dist/seed-$GAME.json"
|
||||
|
||||
- name: Drop the re-folded model, compress the seed
|
||||
# A contribution does not change the binary, so the sidecar fold just re-folds a build the model
|
||||
# already has. Publishing that would append a duplicate row and grow the model on every PR — so the
|
||||
# model asset is left alone and `<game>-latest` keeps the one the last derive published.
|
||||
run: |
|
||||
rm -f "dist/model-$GAME.json"
|
||||
gzip -6 "dist/seed-$GAME.json"
|
||||
|
||||
- name: Publish the immutable patch snapshot
|
||||
uses: https://code.forgejo.org/actions/forgejo-release@v2
|
||||
with:
|
||||
direction: upload
|
||||
url: ${{ github.server_url }}
|
||||
repo: ${{ github.repository }}
|
||||
tag: ${{ env.GAME }}-${{ env.BUILDID }}-${{ env.PATCH }}
|
||||
release-dir: dist
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
override: true
|
||||
release-notes: "${{ env.GAME }} gamedata — build ${{ env.BUILDID }}, patch ${{ env.PATCH }} (contribution PR #${{ github.event.pull_request.number }})."
|
||||
|
||||
- name: Move <game>-latest to this patch
|
||||
uses: https://code.forgejo.org/actions/forgejo-release@v2
|
||||
with:
|
||||
direction: upload
|
||||
url: ${{ github.server_url }}
|
||||
repo: ${{ github.repository }}
|
||||
tag: ${{ env.GAME }}-latest
|
||||
release-dir: dist
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
override: true
|
||||
release-notes: "Rolling ${{ env.GAME }} gamedata — currently build ${{ env.BUILDID }} patch ${{ env.PATCH }}. Stable URL; assets overwritten on each update."
|
||||
118
.forgejo/workflows/derive.yml
Normal file
118
.forgejo/workflows/derive.yml
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
name: derive
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
game:
|
||||
description: "Game key (cs2 or dota2)"
|
||||
required: true
|
||||
default: cs2
|
||||
|
||||
concurrency:
|
||||
group: derive-${{ github.event.inputs.game }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
derive:
|
||||
runs-on: s2-runner
|
||||
env:
|
||||
GAME: ${{ github.event.inputs.game }}
|
||||
STEAM_APPS: /home/cs2/.steam/SteamApps
|
||||
STEAM_USER: source2rosetta
|
||||
RELEASE_BASE: ${{ github.server_url }}/${{ github.repository }}/releases/download
|
||||
BOOTSTRAP_DIR: /home/cs2/rosetta-bootstrap
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Update the install to the current build
|
||||
run: |
|
||||
case "$GAME" in
|
||||
cs2) APPID=730 ;;
|
||||
dota2) APPID=570 ;;
|
||||
*) echo "unknown game '$GAME' (expected cs2 or dota2)"; exit 1 ;;
|
||||
esac
|
||||
echo "APPID=$APPID" >> "$GITHUB_ENV"
|
||||
# No password here: the runner holds a cached refresh token from a one-time interactive login, so
|
||||
# this is non-interactive. If it ever fails with a login error the token has lapsed — re-run
|
||||
# `steamcmd +login $STEAM_USER` once on the runner, as the runner user.
|
||||
steamcmd +login "$STEAM_USER" +app_update "$APPID" +quit
|
||||
|
||||
- name: Resolve the game paths + the new buildid
|
||||
run: |
|
||||
MANIFEST="$STEAM_APPS/appmanifest_$APPID.acf"
|
||||
[ -f "$MANIFEST" ] || { echo "no manifest at $MANIFEST — check STEAM_APPS"; exit 1; }
|
||||
INSTALLDIR=$(grep -oP '"installdir"\s+"\K[^"]+' "$MANIFEST")
|
||||
BUILDID=$(grep -oP '"buildid"\s+"\K[0-9]+' "$MANIFEST")
|
||||
GAME_DIR="$STEAM_APPS/common/$INSTALLDIR/game"
|
||||
[ -d "$GAME_DIR" ] || { echo "expected a game tree at '$GAME_DIR' — check STEAM_APPS"; exit 1; }
|
||||
{ echo "BUILDID=$BUILDID"; echo "GAME_DIR=$GAME_DIR"; } >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build the deriver
|
||||
run: cargo build --release
|
||||
|
||||
- name: Fetch the previous model + seed (the two non-user-facing release artifacts)
|
||||
run: |
|
||||
mkdir -p in dist work
|
||||
code=$(curl -sSL -o in/model.gz -w '%{http_code}' "$RELEASE_BASE/$GAME-latest/model-$GAME.json.gz" || echo 000)
|
||||
if [ "$code" = "200" ]; then
|
||||
gunzip -c in/model.gz > "in/model-$GAME.json"
|
||||
echo "model: from the $GAME-latest release"
|
||||
elif [ "$code" = "404" ] && [ -f "${BOOTSTRAP_DIR:-}/model-$GAME.json" ]; then
|
||||
# First run only. ONLY a genuine 404 falls back: on a transient failure we must NOT quietly
|
||||
# re-derive from a stale on-disk model and then overwrite `latest` with the result.
|
||||
cp "${BOOTSTRAP_DIR}/model-$GAME.json" "in/model-$GAME.json"
|
||||
echo "model: BOOTSTRAP from $BOOTSTRAP_DIR — clear BOOTSTRAP_DIR once this run has published"
|
||||
else
|
||||
echo "model fetch failed (HTTP $code) and no bootstrap copy at '${BOOTSTRAP_DIR:-<unset>}/model-$GAME.json' — refusing to derive"
|
||||
exit 1
|
||||
fi
|
||||
if curl -fsSL -o in/seed.gz "$RELEASE_BASE/$GAME-latest/seed-$GAME.json.gz"; then
|
||||
gunzip -c in/seed.gz > "mappings/seed-$GAME.json"
|
||||
echo "seed: from the $GAME-latest release"
|
||||
else
|
||||
echo "seed: none published yet — using the repo's mappings/seed-$GAME.json"
|
||||
fi
|
||||
|
||||
- name: Produce — derive + validate-live + typed netvars + fold model N -> N+1
|
||||
run: |
|
||||
# --target is a buildid-named SYMLINK to the install, not the install path itself: the deriver labels
|
||||
# a build by its directory NAME, and the forward-only guard demands each target sort strictly after
|
||||
# the model's latest build. Passing ".../game" would label every build "game", so the SECOND run
|
||||
# would be rejected as not-newer. Buildids are monotonic and sort after the corpus's date labels.
|
||||
ln -sfn "$GAME_DIR" "work/$BUILDID"
|
||||
./target/release/source2rosetta --game "$GAME" produce \
|
||||
--seed "mappings/seed-$GAME.json" \
|
||||
--corpus-model "in/model-$GAME.json" \
|
||||
--target "work/$BUILDID" \
|
||||
--game-dir "$GAME_DIR" \
|
||||
--version "$GAME-$BUILDID-0" \
|
||||
--out-dir dist
|
||||
cp "mappings/seed-$GAME.json" "dist/seed-$GAME.json"
|
||||
|
||||
- name: Compress the internal artifacts for release
|
||||
run: |
|
||||
gzip -6 "dist/model-$GAME.json"
|
||||
gzip -6 "dist/seed-$GAME.json"
|
||||
|
||||
- name: Publish the immutable per-build snapshot
|
||||
uses: https://code.forgejo.org/actions/forgejo-release@v2
|
||||
with:
|
||||
direction: upload
|
||||
url: ${{ github.server_url }}
|
||||
repo: ${{ github.repository }}
|
||||
tag: ${{ env.GAME }}-${{ env.BUILDID }}-0
|
||||
release-dir: dist
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
override: true
|
||||
release-notes: "${{ env.GAME }} gamedata for Steam build ${{ env.BUILDID }}, derived by source2rosetta. Patch 0 = the game-update build; later patches on this buildid come from contribution PRs."
|
||||
|
||||
- name: Move <game>-latest to this build
|
||||
uses: https://code.forgejo.org/actions/forgejo-release@v2
|
||||
with:
|
||||
direction: upload
|
||||
url: ${{ github.server_url }}
|
||||
repo: ${{ github.repository }}
|
||||
tag: ${{ env.GAME }}-latest
|
||||
release-dir: dist
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
override: true
|
||||
release-notes: "Rolling ${{ env.GAME }} gamedata — always the newest build (currently ${{ env.BUILDID }}). Stable URL; assets overwritten each update."
|
||||
Loading…
Add table
Add a link
Reference in a new issue