Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f65cd2faa | ||
|
|
01abc78c0f | ||
|
|
63daa89bcb | ||
|
|
748c65d592 | ||
|
|
91ad350543 | ||
|
|
e281e4024a | ||
|
|
3a84022d8f | ||
|
|
c16b061fe7 | ||
|
|
bc8fc88849 | ||
|
|
e349124fc4 | ||
|
|
93a328a007 | ||
|
|
597e112e2b | ||
|
|
b0406b82f1 | ||
|
|
b10af101ed | ||
|
|
635cdd6c2c | ||
|
|
91b1cef4b2 | ||
|
|
1858aa143c | ||
|
|
93fbabcb60 | ||
|
|
aff9324c5c | ||
|
|
da49d87193 | ||
|
|
83886de13c | ||
|
|
df3e843284 | ||
|
|
5798ba209e | ||
|
|
8984945062 | ||
|
|
b8778d8a89 | ||
|
|
5b2524fe4d | ||
|
|
6f3babddd3 | ||
|
|
6134bd3735 | ||
|
|
b44db64e1e | ||
|
|
9d9bfd868d | ||
|
|
fb19e0fa84 | ||
|
|
88ab8d85cb | ||
|
|
e49f05cd1d | ||
|
|
573e129dee | ||
|
|
c01152b3fc | ||
|
|
62ef8d3be8 | ||
|
|
29bdf93a39 | ||
|
|
0f0f7be986 | ||
|
|
81d6eba98b | ||
|
|
08ba86bf79 | ||
|
|
8e9427d775 | ||
|
|
54c59cfd8a | ||
|
|
4d5cb3f2a4 | ||
|
|
77d41657aa | ||
|
|
5a5b8d2bd2 | ||
|
|
d2de4c18de | ||
|
|
037298b5a7 | ||
|
|
68bd653d9c | ||
|
|
c7432500fc | ||
|
|
d0d3e68ec4 | ||
|
|
0da4e7d52e | ||
|
|
ef6ef9e6a2 | ||
|
|
aad3b35a3a | ||
|
|
dc20a5e020 | ||
|
|
d0cf2b0811 | ||
|
|
6e79b7f740 | ||
|
|
e736186084 | ||
|
|
56930b532a | ||
|
|
1bf7fa9614 | ||
|
|
25ab2d7ea1 | ||
|
|
a1198c687e | ||
|
|
aa6d96e803 | ||
|
|
b49e1fd97d | ||
|
|
8a6980d33b | ||
|
|
8514f5774b | ||
|
|
32d977bf99 | ||
|
|
d0bd59f735 | ||
|
|
21155c06fb | ||
|
|
e0ac4551c2 | ||
|
|
e799b04b82 | ||
|
|
c2d42eba1b | ||
|
|
070bddc417 | ||
|
|
f3d5967ae6 | ||
|
|
72c0912c7b | ||
|
|
89020e2062 | ||
|
|
a617775941 | ||
|
|
b9e03dbb7b | ||
|
|
96d7f7673b | ||
|
|
a2a18e41c3 | ||
|
|
e2fcb0f5c2 | ||
|
|
ed1a281c9c | ||
|
|
9844193547 | ||
|
|
e9d402b60c | ||
|
|
71323383d8 | ||
|
|
3b34bef27e | ||
|
|
23402771cf | ||
|
|
6480bf8b8d | ||
|
|
f64587952e | ||
|
|
ad5e3b12b7 | ||
|
|
f1cdfcb3e8 | ||
|
|
4931f3a819 | ||
|
|
7b74fe93b1 | ||
|
|
4ccc44d612 | ||
|
|
08e63434d8 | ||
|
|
40dc440cbb | ||
|
|
7581c129bb | ||
|
|
259d022945 | ||
|
|
e44816b65a | ||
|
|
b300e7efd4 | ||
|
|
78853c19a6 | ||
|
|
e53ad8220b | ||
|
|
43fb2513a2 | ||
|
|
c909eea98f | ||
|
|
1f1240298a | ||
|
|
dfd2102274 | ||
|
|
35ce93db53 | ||
|
|
3f3103d070 | ||
|
|
ed4aa85442 | ||
|
|
43010d3eb2 | ||
|
|
546deac561 | ||
|
|
2eca4bc2ec | ||
|
|
a2ae910123 | ||
|
|
88b20af247 | ||
|
|
6a3454953c | ||
|
|
ceb5b2275d | ||
|
|
e980583b96 | ||
|
|
5c094254e5 | ||
|
|
d8a3327ee3 | ||
|
|
239ce420fa | ||
|
|
13d6b9fbaa | ||
|
|
ee1620f438 | ||
|
|
a0dba26666 | ||
|
|
82e29462a9 | ||
|
|
725ff7a410 | ||
|
|
64b5237f7c | ||
|
|
7d17c4d63a | ||
|
|
aab9aa4c22 | ||
|
|
6d9a7492e2 |
No files matched your search
@@ -4,3 +4,9 @@
|
||||
* @ludovic111 @horizon-rust/horizon-team
|
||||
/.github/ @ludovic111
|
||||
/revoked.toml @ludovic111
|
||||
# What decides which builds the native-builds environment signs, and under which key (native-build.yml,
|
||||
# horizon#11859): the signing key's owner reviews it.
|
||||
/scripts/native_build.py @ludovic111
|
||||
/scripts/test_native_build.py @ludovic111
|
||||
/prebuilt.toml @ludovic111
|
||||
/.github/workflows/native-build.yml @ludovic111
|
||||
@@ -29,13 +29,9 @@ jobs:
|
||||
with:
|
||||
path: head
|
||||
persist-credentials: false
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin
|
||||
~/.cargo/horizon-rev
|
||||
key: cargo-horizon-mod-${{ env.HORIZON_REV }}
|
||||
# main's scripts, not the pull request's.
|
||||
# main's scripts, not the pull request's. No actions/cache: this job decides whether a submission passes,
|
||||
# and main's cache entries can be written by runs that build other people's code (native-build.yml), so
|
||||
# the tool is built fresh and the files are checked against their SHA-256 (fetch.sh).
|
||||
- run: base/scripts/install-tool.sh
|
||||
- name: Who submitted it
|
||||
id: author
|
||||
@@ -60,13 +56,8 @@ jobs:
|
||||
| tee -a "$GITHUB_STEP_SUMMARY" || status=1
|
||||
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
||||
exit $status
|
||||
# main's files, kept by publish: each download from a release counts as a
|
||||
# player's.
|
||||
- uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: files
|
||||
key: files-${{ hashFiles('head/mods/*.toml') }}
|
||||
restore-keys: files-
|
||||
# Published files come from the registry-files release publish keeps: downloads from a mod's own
|
||||
# release would count as a player's.
|
||||
- name: Download its files
|
||||
run: base/scripts/fetch.sh head
|
||||
- name: Check each new file
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
# Prebuilt native builds (horizon#11859): the game compiled for one exact set of registry native mods, signed, so
|
||||
# players' launchers download it instead of compiling for 30 to 60 minutes.
|
||||
# - Every day after the Nightly, `dispatch` starts one run per set of prebuilt.toml on the newest Nightly. A run
|
||||
# builds ONE set, and only a set prebuilt.toml lists (`plan` and `sign` both refuse any other).
|
||||
# - On GitHub's runners only:
|
||||
# plan (no secrets): resolves the Nightly and each mod against the published index (newest unrevoked version,
|
||||
# not broken_on that commit), computes each target's request key as the launcher does, leaves out what is
|
||||
# published, downloads the mods' files and the launcher release named by vars.NATIVE_BUILD_LAUNCHER and
|
||||
# checks that release's signed SHA256SUMS against Horizon's release keys (pinned below, by SHA-256).
|
||||
# build (no secrets, a token that can only read, no cache): runs that launcher headless
|
||||
# (`native-build --prebuilt-out`): its pinned Rust, the Nightly's source, the release profile, the same
|
||||
# pins, hook points and left-out mods as a player's build. A mod crate's build script runs here, so this
|
||||
# job is UNTRUSTED: with the runner's own token it can replace any artifact of the run (`plan` included)
|
||||
# and write cache entries on main.
|
||||
# sign (environment `native-builds`, main only): trusts nothing a build job could touch. What it signs comes
|
||||
# from the plan job's OUTPUTS only (no later job can change them); it recomputes each key from the base,
|
||||
# the mods and the target, derives every file name from key and target, downloads `build-<target>` by
|
||||
# its exact name and refuses anything in it but that one archive. It runs only Horizon's
|
||||
# tools/ci/sign_native_build.sh (sha256sum and OpenSSL) at a pinned commit, checked by SHA-256 before
|
||||
# the key is in reach. A build can only ever be signed as its own set's.
|
||||
# Caches: no job here restores one, and no job of horizon-mods that holds a write token may restore one either
|
||||
# (publish.yml, native-compat.yml): the build job above can write cache entries on main, and restoring an entry
|
||||
# unpacks a tar anywhere on the runner.
|
||||
name: native-build
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 19 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
mods:
|
||||
description: "One set of prebuilt.toml, exactly as listed (`id` or `id@version`, space-separated). Empty: every set (one run each)"
|
||||
default: ""
|
||||
base:
|
||||
description: "The Nightly: `latest` or nightly-<date>-<short>"
|
||||
default: latest
|
||||
targets:
|
||||
description: "`all`, or target triples separated by commas"
|
||||
default: all
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
group: native-build-${{ inputs.mods || 'dispatch' }}-${{ inputs.base || 'latest' }}
|
||||
cancel-in-progress: false
|
||||
env:
|
||||
# A launcher release with `native-build --prebuilt-out` (Horizon #11859), e.g. [email protected].
|
||||
LAUNCHER: ${{ vars.NATIVE_BUILD_LAUNCHER }}
|
||||
# The Horizon commit this workflow takes its signer and keys from: its own pin, never vars.HORIZON_REV (shared with
|
||||
# publish and the checks) and never a branch. Each file is checked against its SHA-256 here before it is used.
|
||||
# Moving the pin means reviewing the files' diff and updating the three hashes (`sha256sum`).
|
||||
SIGN_REV: c794b87841b58f72ade9842b46757d843246054c
|
||||
SIGN_SCRIPT_SHA256: 95ebefe4b0beeb1b4282a09352260c48f1793b13e370414e91175aa789fc2278 # tools/ci/sign_native_build.sh
|
||||
SIGN_KEYS_SHA256: f75dc6fe23343a4e97fe2453853e67f20ddcce1bc1f2c240a56b8b6d6681f611 # crates/horizon-release-sign/native-build-keys.txt
|
||||
RELEASE_KEYS_SHA256: bb1f43a3eb14a2c0d89d7d823a3352dd4b7ac01f50d55451e282ea178fb61f6f # crates/horizon-release-sign/release-keys.txt
|
||||
# The largest archive signed: the launcher refuses bigger ones before checking (launcher/src/native_prebuilt.rs).
|
||||
MAX_ARCHIVE: "536870912"
|
||||
jobs:
|
||||
dispatch:
|
||||
# Idle until a launcher with native-build --prebuilt-out is released and named.
|
||||
if: (github.event_name == 'schedule' || inputs.mods == '') && vars.NATIVE_BUILD_LAUNCHER != ''
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: One run per set
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
BASE: ${{ inputs.base || 'latest' }}
|
||||
run: |
|
||||
python3 scripts/native_build.py sets | while read -r set; do
|
||||
echo "native-build: $set"
|
||||
gh workflow run native-build.yml --repo "$GITHUB_REPOSITORY" --ref main -f mods="$set" -f base="$BASE"
|
||||
done
|
||||
plan:
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.mods != ''
|
||||
runs-on: ubuntu-latest
|
||||
# The sign job reads these, never the `plan` artifact: build jobs can replace an artifact, not a job's outputs.
|
||||
outputs:
|
||||
matrix: ${{ steps.plan.outputs.matrix }}
|
||||
any: ${{ steps.plan.outputs.any }}
|
||||
tag: ${{ steps.plan.outputs.tag }}
|
||||
base: ${{ steps.plan.outputs.base }}
|
||||
mods: ${{ steps.plan.outputs.mods }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- run: python3 -m unittest scripts/test_native_build.py
|
||||
- id: plan
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
MODS: ${{ inputs.mods }}
|
||||
BASE: ${{ inputs.base }}
|
||||
TARGETS: ${{ inputs.targets }}
|
||||
run: python3 scripts/native_build.py plan --mods "$MODS" --base "$BASE" --targets "$TARGETS" --out plan
|
||||
- name: The launcher, checked against Horizon's release keys
|
||||
if: steps.plan.outputs.any == 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[ -n "$LAUNCHER" ] || { echo "::error::vars.NATIVE_BUILD_LAUNCHER names no launcher release"; exit 1; }
|
||||
mkdir -p plan/launcher && cd plan/launcher
|
||||
base="https://github.com/horizon-rust/horizon/releases/download/$LAUNCHER"
|
||||
curl -sSfLO "$base/SHA256SUMS" && curl -sSfLO "$base/SHA256SUMS.sig"
|
||||
for asset in $(jq -r '.builds[].launcher' ../plan.json | sort -u); do curl -sSfLO "$base/$asset"; done
|
||||
curl -sSfL -o release-keys.txt \
|
||||
"https://raw.githubusercontent.com/horizon-rust/horizon/$SIGN_REV/crates/horizon-release-sign/release-keys.txt"
|
||||
echo "$RELEASE_KEYS_SHA256 release-keys.txt" | sha256sum -c
|
||||
grep -v '^#' release-keys.txt | grep . > keys
|
||||
{ printf 'horizon release %s\n' "$LAUNCHER"; cat SHA256SUMS; } > message
|
||||
ok=
|
||||
while read -r key; do
|
||||
{ printf '\x30\x2a\x30\x05\x06\x03\x2b\x65\x70\x03\x21\x00'; echo "$key" | base64 -d; } > pub.der
|
||||
while read -r sig; do
|
||||
echo "$sig" | base64 -d > sig.bin
|
||||
openssl pkeyutl -verify -rawin -pubin -keyform DER -inkey pub.der -in message -sigfile sig.bin >/dev/null 2>&1 && ok=1
|
||||
done < SHA256SUMS.sig
|
||||
done < keys
|
||||
[ -n "$ok" ] || { echo "::error::$LAUNCHER: SHA256SUMS isn't signed by a Horizon release key"; exit 1; }
|
||||
sha256sum --ignore-missing -c SHA256SUMS
|
||||
rm -f message pub.der sig.bin keys release-keys.txt SHA256SUMS SHA256SUMS.sig
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: plan
|
||||
path: plan
|
||||
build:
|
||||
needs: plan
|
||||
if: needs.plan.outputs.any == 'true'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include: ${{ fromJson(needs.plan.outputs.matrix) }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 180
|
||||
permissions:
|
||||
contents: read
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: plan
|
||||
path: plan
|
||||
- if: runner.os == 'Linux'
|
||||
# The game's ALSA and udev headers; the launcher links xkbcommon and xcb even headless.
|
||||
run: sudo apt-get update && sudo apt-get install -y libasound2-dev libudev-dev pkg-config libxkbcommon-x11-0 libxcb1
|
||||
- name: Build the set as a player's launcher would
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
KEY: ${{ matrix.key }}
|
||||
LAUNCHER_ASSET: ${{ matrix.launcher }}
|
||||
ARCHIVE: ${{ matrix.archive }}
|
||||
TAG: ${{ needs.plan.outputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
scratch="$RUNNER_TEMP/native"
|
||||
mkdir -p "$scratch/launcher" "$scratch/out"
|
||||
case "$LAUNCHER_ASSET" in
|
||||
*.tar.gz) tar xzf "plan/launcher/$LAUNCHER_ASSET" -C "$scratch/launcher"; launcher="$scratch/launcher/horizon-launcher" ;;
|
||||
*macos*) unzip -q "plan/launcher/$LAUNCHER_ASSET" -d "$scratch/launcher"; launcher="$scratch/launcher/Horizon.app/Contents/MacOS/horizon-launcher" ;;
|
||||
*) unzip -q "plan/launcher/$LAUNCHER_ASSET" -d "$scratch/launcher"; launcher="$scratch/launcher/horizon-launcher.exe" ;;
|
||||
esac
|
||||
export HORIZON_LAUNCHER_HOME="$scratch/home" HORIZON_HOME="$scratch/game" XDG_DATA_HOME="$scratch/data" \
|
||||
XDG_CONFIG_HOME="$scratch/config" HORIZON_LAUNCHER_NO_SELF_UPDATE=1 HORIZON_NO_AUDIO=1
|
||||
py=$(command -v python3 || command -v python) # Windows runners name it python
|
||||
"$launcher" install "$TAG"
|
||||
"$launcher" native-build --prebuilt-out "$scratch/out/build" --version "$TAG" --profile release \
|
||||
--trust-native-code plan/hnmods
|
||||
got=$("$py" -c 'import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],"rb").read()).hexdigest())' "$scratch/out/build/request.txt")
|
||||
[ "$got" = "$KEY" ] || { echo "::error::the launcher built request $got, the plan asked for $KEY"; exit 1; }
|
||||
mkdir -p dist
|
||||
tar czf "dist/$ARCHIVE" -C "$scratch/out/build" .
|
||||
ls -l dist
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: dist/${{ matrix.archive }}
|
||||
if-no-files-found: error
|
||||
sign:
|
||||
needs: [plan, build]
|
||||
if: always() && needs.plan.outputs.any == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
environment: native-builds
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
env:
|
||||
# Job outputs of plan, the only input of this job besides the archives themselves.
|
||||
BASE: ${{ needs.plan.outputs.base }}
|
||||
TAG: ${{ needs.plan.outputs.tag }}
|
||||
MODS: ${{ needs.plan.outputs.mods }}
|
||||
MATRIX: ${{ needs.plan.outputs.matrix }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
scripts/native_build.py
|
||||
prebuilt.toml
|
||||
sparse-checkout-cone-mode: false
|
||||
persist-credentials: false
|
||||
path: registry
|
||||
- name: The signer, at its pinned commit and hashes
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p "$RUNNER_TEMP/signer" && cd "$RUNNER_TEMP/signer"
|
||||
raw="https://raw.githubusercontent.com/horizon-rust/horizon/$SIGN_REV"
|
||||
curl -sSfL -o sign_native_build.sh "$raw/tools/ci/sign_native_build.sh"
|
||||
curl -sSfL -o native-build-keys.txt "$raw/crates/horizon-release-sign/native-build-keys.txt"
|
||||
printf '%s %s\n' "$SIGN_SCRIPT_SHA256" sign_native_build.sh "$SIGN_KEYS_SHA256" native-build-keys.txt | sha256sum -c
|
||||
- name: What to sign, recomputed from the plan job's outputs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Checks the mods are a prebuilt.toml set and every row's key is that set's for its target; prints
|
||||
# "<key> <target>". Fails the job on any mismatch.
|
||||
python3 -I registry/scripts/native_build.py signable > "$RUNNER_TEMP/rows"
|
||||
cat "$RUNNER_TEMP/rows"
|
||||
- name: Each target's build, by its exact artifact name
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
while read -r key target <&3; do
|
||||
name="${key:0:16}-$target.tar.gz"
|
||||
dir="$RUNNER_TEMP/builds/$target"
|
||||
mkdir -p "$dir"
|
||||
if ! gh run download "$GITHUB_RUN_ID" --name "build-$target" --dir "$dir"; then
|
||||
echo "::warning::$target: no build"
|
||||
continue
|
||||
fi
|
||||
# Exactly one regular file, named from the key and target; anything else in the artifact is refused.
|
||||
listing=$(cd "$dir" && find . -mindepth 1 -printf '%y %P\n')
|
||||
if [ "$listing" != "f $name" ]; then
|
||||
echo "::error::$target: build-$target holds something other than $name alone:"; echo "$listing"; exit 1
|
||||
fi
|
||||
size=$(stat -c %s "$dir/$name")
|
||||
[ "$size" -le "$MAX_ARCHIVE" ] || { echo "::error::$target: $name is $size bytes, over $MAX_ARCHIVE"; exit 1; }
|
||||
echo "$target: $name, $size bytes, sha256 $(sha256sum < "$dir/$name" | cut -d' ' -f1)"
|
||||
done 3< "$RUNNER_TEMP/rows"
|
||||
- name: Sign, then publish
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
NATIVE_BUILD_SIGNING_KEY: ${{ secrets.NATIVE_BUILD_SIGNING_KEY }}
|
||||
NATIVE_BUILD_SIGNING_KEY_NEXT: ${{ secrets.NATIVE_BUILD_SIGNING_KEY_NEXT }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
signer="$RUNNER_TEMP/signer"
|
||||
# Checked again in the step that holds the key: nothing between the steps may have changed them.
|
||||
(cd "$signer" && printf '%s %s\n' "$SIGN_SCRIPT_SHA256" sign_native_build.sh "$SIGN_KEYS_SHA256" native-build-keys.txt \
|
||||
| sha256sum -c --quiet)
|
||||
release="native-builds-${BASE:0:12}"
|
||||
if ! gh release view "$release" >/dev/null 2>&1; then
|
||||
gh release create "$release" --prerelease --latest=false --title "Prebuilt native builds on $TAG" \
|
||||
--notes "The game built with registry native mods on Horizon $BASE, signed by native-build.yml (horizon#11859). The Horizon launcher downloads and checks these; nothing here is for downloading by hand."
|
||||
fi
|
||||
missing=0
|
||||
while read -r key target <&3; do
|
||||
dir="$RUNNER_TEMP/builds/$target"
|
||||
name="${key:0:16}-$target.tar.gz"
|
||||
sig="${key:0:16}-$target.sig"
|
||||
if [ ! -f "$dir/$name" ]; then missing=1; continue; fi
|
||||
NATIVE_BUILD_KEYS="$signer/native-build-keys.txt" bash "$signer/sign_native_build.sh" "$key" "$target" "$dir/$name"
|
||||
# The signature last: the launcher looks for it first, so a half-uploaded build is never offered.
|
||||
gh release upload "$release" "$dir/$name"
|
||||
gh release upload "$release" "$dir/$sig"
|
||||
done 3< "$RUNNER_TEMP/rows"
|
||||
exit $missing
|
||||
@@ -0,0 +1,130 @@
|
||||
# Native mods on the newest Nightly, as players' launchers build them (scripts/native_compat.py):
|
||||
# - on a pull request, each new native-mod version, applied together with the most downloaded native mods
|
||||
# and compiled (`cargo check`); its support modules' names against every registry mod's;
|
||||
# - every day after the Nightly, every native mod's newest version, applied, then the rest compiled
|
||||
# (`cargo check`): the ones horizon-mixins refuses alone and the ones whose own code doesn't compile go
|
||||
# to the `native-health` release, which publish adds to the index (`broken_on`) so the launcher warns
|
||||
# before an install.
|
||||
# Both compile other people's crates, whose build scripts run: those jobs hold a token that can only read and
|
||||
# run main's script, never the pull request's. The daily result goes to the release from a separate job that
|
||||
# runs nothing of theirs and restores no cache (a build can write cache entries on main).
|
||||
name: native-compat
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths: ["mods/*.toml"]
|
||||
schedule:
|
||||
- cron: "40 18 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
horizon:
|
||||
description: "Horizon commit, or `latest` (the newest Nightly)"
|
||||
default: latest
|
||||
upload:
|
||||
description: "Publish the result (native-health release); `false` for a trial run"
|
||||
default: "true"
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
group: native-compat-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
env:
|
||||
# The Rust the launcher builds native mods with (Horizon's launcher/src/rust_toolchain.rs).
|
||||
RUST: "1.99.1"
|
||||
jobs:
|
||||
check:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
path: base
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: head
|
||||
persist-credentials: false
|
||||
- name: Does it change a native mod?
|
||||
id: native
|
||||
run: |
|
||||
if [ -n "$(python3 base/scripts/native_compat.py new base head)" ]; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- if: steps.native.outputs.changed == 'true'
|
||||
run: rustup toolchain install $RUST --profile minimal && rustup default $RUST
|
||||
- if: steps.native.outputs.changed == 'true'
|
||||
run: sudo apt-get update && sudo apt-get install -y libasound2-dev libudev-dev pkg-config
|
||||
- if: steps.native.outputs.changed == 'true'
|
||||
name: On the newest Nightly, with the popular native mods
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
python3 base/scripts/native_compat.py check base head | tee -a "$GITHUB_STEP_SUMMARY"
|
||||
health:
|
||||
if: github.event_name != 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
# Each mod horizon-mixins refuses costs one more apply of the whole set.
|
||||
timeout-minutes: 300
|
||||
# Read only: every registry native mod's build script runs in this job.
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- run: python3 -m unittest discover -s scripts -p 'test_*.py'
|
||||
- run: rustup toolchain install $RUST --profile minimal && rustup default $RUST
|
||||
- run: sudo apt-get update && sudo apt-get install -y libasound2-dev libudev-dev pkg-config
|
||||
# Yesterday's dependencies, checked, and horizon-mixins: only Horizon's own crates and the mods compile again.
|
||||
# Safe only because this job holds nothing: it builds untrusted code anyway.
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
native/target/compat/cargo
|
||||
native/target/horizon-mixins
|
||||
key: native-health-${{ env.RUST }}-${{ github.run_id }}
|
||||
restore-keys: native-health-${{ env.RUST }}-
|
||||
- name: Every native mod's newest version on the newest Nightly
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
HORIZON: ${{ inputs.horizon || 'latest' }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
python3 scripts/native_compat.py health --horizon "$HORIZON" --out native-health.json | tee -a "$GITHUB_STEP_SUMMARY"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: native-health
|
||||
path: native-health.json
|
||||
if-no-files-found: error
|
||||
health-publish:
|
||||
needs: health
|
||||
if: inputs.upload != 'false'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: native-health
|
||||
path: health
|
||||
- name: Keep it for publish
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Data from a job that ran other people's code: only the one file, only the shape publish reads.
|
||||
[ "$(cd health && find . -mindepth 1 -printf '%y %P\n')" = "f native-health.json" ]
|
||||
jq -e '(.horizon | type == "string" and test("^[0-9a-f]{40}$"))
|
||||
and (.broken | type == "object")
|
||||
and ([.broken | to_entries[] | (.key | test("^[a-z0-9_-]+$")) and (.value.version | type == "string")] | all)' \
|
||||
health/native-health.json > /dev/null
|
||||
gh release view native-health >/dev/null 2>&1 \
|
||||
|| gh release create native-health --prerelease --title "Native mods' health" \
|
||||
--notes "native-health.json: the native mods that don't apply on the newest Nightly (native-compat.yml). publish adds them to the index as broken_on."
|
||||
gh release upload native-health health/native-health.json --clobber
|
||||
@@ -1,9 +1,9 @@
|
||||
# Merging publishes: each new version's file becomes a release asset
|
||||
# (<id>-<version>/<file>), then index.json and the icons go to GitHub Pages
|
||||
# (https://horizon-rust.github.io/horizon-mods/index.json), which the launcher and
|
||||
# (<id>-<version>/<file>), then index.json, the icons and the screenshots go to GitHub
|
||||
# Pages (https://horizon-rust.github.io/horizon-mods/index.json), which the launcher and
|
||||
# playhorizon.dev/mods read. Nothing is published if any entry fails its checks.
|
||||
# Every hour it runs again to refresh each project's `downloads`: GitHub's
|
||||
# download counts of its release assets.
|
||||
# Every hour it runs again to refresh each project's `downloads` (GitHub's download
|
||||
# counts of its release assets) and `stars` (its source repository's GitHub stars).
|
||||
name: publish
|
||||
on:
|
||||
push:
|
||||
@@ -30,22 +30,17 @@ jobs:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
HORIZON_REV: ${{ vars.HORIZON_REV || 'main' }}
|
||||
steps:
|
||||
# No actions/cache in this job, ever: it holds a write token (releases, Pages), and any run on main can
|
||||
# write cache entries, including native-build.yml's and native-compat.yml's builds of other people's
|
||||
# code. Restoring an entry unpacks a tar anywhere on the runner, so the tool is built fresh (about two
|
||||
# minutes) and the files come from the registry-files release, each checked against its SHA-256.
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin
|
||||
~/.cargo/horizon-rev
|
||||
key: cargo-horizon-mod-${{ env.HORIZON_REV }}
|
||||
persist-credentials: false
|
||||
- run: scripts/install-tool.sh
|
||||
# Files kept from the last run: GitHub counts every download of a release
|
||||
# asset, so fetching them all again each hour would count as players.
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: files
|
||||
key: files-${{ hashFiles('mods/*.toml') }}
|
||||
restore-keys: files-
|
||||
- run: scripts/fetch.sh .
|
||||
# FILES_MIRROR_UPLOAD: what isn't in registry-files yet is fetched from its own release once, then kept
|
||||
# there, so these hourly runs don't count as players' downloads.
|
||||
- run: FILES_MIRROR_UPLOAD=1 scripts/fetch.sh .
|
||||
- name: Build the index
|
||||
run: |
|
||||
mkdir -p site
|
||||
@@ -73,9 +68,48 @@ jobs:
|
||||
| if $n > 0 then .downloads = $n else . end)' site/index.json > index.counted.json
|
||||
mv index.counted.json site/index.json
|
||||
jq -r '.projects[] | "\(.id) \(.downloads // 0)"' site/index.json
|
||||
- name: GitHub stars
|
||||
run: |
|
||||
# A project's stars are its source repository's (owner/repo of a
|
||||
# https://github.com/ source, a folder of it or .git included), asked
|
||||
# once per repository. One that can't be read is left out, not zero.
|
||||
jq -r '.projects[].source // empty' site/index.json \
|
||||
| sed -nE 's#^https://github\.com/([A-Za-z0-9-]+)/([A-Za-z0-9._-]+).*#\1/\2#p' \
|
||||
| sed -E 's/\.git$//' | tr 'A-Z' 'a-z' | sort -u \
|
||||
| while read -r repo; do
|
||||
n="$(gh api "repos/$repo" --jq .stargazers_count 2>/dev/null)" || continue
|
||||
echo "{\"key\": \"$repo\", \"value\": $n}"
|
||||
done | jq -s from_entries > stars.json
|
||||
jq --slurpfile stars stars.json '
|
||||
.projects |= map(
|
||||
(first((.source // "") | ascii_downcase
|
||||
| capture("^https://github[.]com/(?<r>[a-z0-9-]+/[a-z0-9._-]+)").r
|
||||
| sub("[.]git$"; "")) // "") as $r
|
||||
| ($stars[0][$r] // 0) as $n
|
||||
| if $n > 0 then .stars = $n else . end)' site/index.json > index.starred.json
|
||||
mv index.starred.json site/index.json
|
||||
jq -r '.projects[] | "\(.id) \(.stars // 0)"' site/index.json
|
||||
- name: Native mods that don't apply on the newest Nightly
|
||||
run: |
|
||||
# native-compat.yml's daily health: the version each names gets `broken_on`, the Horizon
|
||||
# commit horizon-mixins refused it on and its code, so the launcher warns before an install.
|
||||
if gh release download native-health --pattern native-health.json --dir health 2>/dev/null; then
|
||||
jq --slurpfile h health/native-health.json '
|
||||
$h[0] as $health
|
||||
| .projects |= map(. as $p | .versions |= map(
|
||||
($health.broken[$p.id] // null) as $b
|
||||
| if $b != null and $b.version == .version
|
||||
then .broken_on = [{horizon: $health.horizon, code: $b.code}] else . end))' \
|
||||
site/index.json > index.health.json
|
||||
mv index.health.json site/index.json
|
||||
jq -r '.projects[] | select(.versions[0].broken_on) | "\(.id) \(.versions[0].version): \(.versions[0].broken_on[0].code)"' site/index.json
|
||||
fi
|
||||
- name: The site
|
||||
run: |
|
||||
cp -R icons site/icons
|
||||
# Screenshots are served beside the icons: the index lists them at
|
||||
# <site>screenshots/<id>/<name>.
|
||||
if [ -d screenshots ]; then cp -R screenshots site/screenshots; fi
|
||||
cat > site/index.html <<'HTML'
|
||||
<!doctype html><meta charset="utf-8"><title>Horizon mods</title>
|
||||
<meta http-equiv="refresh" content="0; url=https://playhorizon.dev/mods">
|
||||
|
||||
@@ -11,9 +11,10 @@ with no file, network or clock access, and can only do what their page lists.
|
||||
Native source mods (`.hnmod`, kind `native-mod`) are built into an isolated game by
|
||||
a launcher with native-mod support. They run with the player’s account permissions.
|
||||
Their bundles pin the exact Horizon and Horizon Mixins revisions used for the build.
|
||||
Other revisions have not been validated. Registry checks inspect the source bundle without
|
||||
executing native code; reviewers must also examine the source and pinned-build
|
||||
validation. Publishing these requires the native-mod changes to `cargo-horizon-mod`
|
||||
Other revisions have not been validated. Registry checks inspect the source bundle and compile it
|
||||
on the newest Nightly with the most downloaded native mods (in a job whose token
|
||||
can only read) without running it; reviewers must also examine the source and
|
||||
pinned-build validation. Publishing these requires the native-mod changes to `cargo-horizon-mod`
|
||||
and the launcher to have shipped first.
|
||||
|
||||
**Who is responsible.** Except the projects marked Official (made by the Horizon project), everything here is made and
|
||||
@@ -65,6 +66,7 @@ why.
|
||||
| `.github/workflows/check.yml` | A pull request's checks |
|
||||
| `.github/workflows/publish.yml` | On merge: releases, then `index.json` on Pages |
|
||||
| `.github/workflows/cleanup.yml` | Weekly: staging files nobody needs any more |
|
||||
| `.github/workflows/native-compat.yml` | Native mods on the newest Nightly, as the launcher builds them: a pull request's new version applied and compiled with the most downloaded native mods, its module names against every mod's; daily, every mod's newest version, the ones that don't apply marked `broken_on` in the index (`scripts/native_compat.py`) |
|
||||
| `.github/workflows/native.yml` | Horizon's native mods: each changed one's tests, `.hnmod` and game check, all of them together, and weekly on the newest Nightly |
|
||||
| `native/<name>/` | Horizon's own native mods (Zoom, FPS Counter, Keystrokes, Minimap, Recipes, Coordinates HUD): the source of their `.hnmod` files |
|
||||
| `native/tools/` | Shared by them: apply on Horizon, check, build, package, prove (`native/tools/native.py`) |
|
||||
|
||||
|
After Width: | Height: | Size: 782 B |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 805 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 687 B |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 245 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 660 B |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 195 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 212 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 221 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 13 KiB |
@@ -18,9 +18,12 @@ Some servers see fast, accurate placing as a cheat: check their rules, or turn "
|
||||
A port of [Accurate Block Placement: Reborn](https://modrinth.com/mod/accurate-block-placement-reborn) (MIT) by Clayborn, Flourick, schwar and KadTheHunter, rewritten in Rust for Horizon."""
|
||||
authors = ["Lutziano888"]
|
||||
owners = ["Lutziano888"]
|
||||
owner_ids = [129792568]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Lutziano888/horizon-accurate-block-placement"
|
||||
icon = "https://raw.githubusercontent.com/Lutziano888/horizon-accurate-block-placement/main/icon.png"
|
||||
icon = "icons/accurate_block_placement.png"
|
||||
categories = ["utility", "game-mechanics"]
|
||||
tags = ["building", "block-placement", "bridging", "port"]
|
||||
|
||||
[[versions]]
|
||||
version = "1.0.0"
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
id = "ae2"
|
||||
kind = "native-mod"
|
||||
name = "Applied Energistics 2"
|
||||
summary = "Unofficial port of Applied Energistics 2 15.4.12: ME networks, autocrafting, spatial storage and more."
|
||||
description = """
|
||||
An unofficial port of Applied Energistics 2 15.4.12 to Horizon, not made or endorsed by its authors. Please report problems to this port, not to them.
|
||||
|
||||
Rewritten in Rust: ME networks, storage cells, autocrafting, the cable bus with facades, P2P tunnels, spatial storage, the guide and the Fluix Researcher.
|
||||
|
||||
**One file, no separate content mod.**
|
||||
|
||||
### Optional integrations
|
||||
Each is compiled in only when its mod is in the same build, so adding or removing JEI or Jade takes effect at the next build.
|
||||
- **With JEI:** AE2's recipes and information pages, "+" transfer into terminals, and dragging items onto config slots.
|
||||
- **With Jade:** network state, channels, P2P links, charge, crafting and stored power.
|
||||
|
||||
### Missing or not the same
|
||||
- **No spatial dimension.** Horizon mods can't add one. A player in a spatial cell goes to a walled plot far out in the End, so `/tp` and F3 say `minecraft:the_end`. A swap with a player takes up to about a second.
|
||||
- **No Java addons.** AE2's Java API isn't available, and the integrations work only with the Horizon ports of JEI and Jade.
|
||||
- **Two storage buses on one inventory:** only the first reaches it.
|
||||
- **Moving items** costs power through terminals, interfaces, export buses and wireless terminals. An IO port and the crafting terminal's refills pay nothing yet.
|
||||
- **Power** is judged once a second, so a network with too little goes down at the next look and comes back after about 40 ticks, where AE2's is at once and 32.
|
||||
- **Unformed crafting, quantum and spatial blocks** join a network before they're formed.
|
||||
- **Autocrafting gaps:** a pattern input of more than one is a single template, so some plans report items missing that AE2 would find. Blocking mode looks at all six sides, provider priority is ignored, and a machine's request for more than it can make crafts nothing.
|
||||
- **Other differences:** machines have 6 or 4 orientations rather than 24, menus are in English, fluix tools are diamond tools, and guide scenes have no annotations.
|
||||
- **Crafting ETA is an estimate:** 1.1% off on average on the test rig, 2.0% at worst.
|
||||
- **Nested P2P error** is an approximation.
|
||||
- **World height:** parts on cables assume -64 to 319.
|
||||
|
||||
The Fluix Researcher and its trades rely on mixins, so they are the part most likely to break on a Horizon update.
|
||||
|
||||
### Tested
|
||||
Tested on Horizon commit 6475f62, on Windows, in single player, on LAN with one guest, and on a dedicated server with one client, including attacks by a non-operator with a modified client. This version is built for Horizon commit 66f3981, where AE2 alone compiles but hasn't been run, and the JEI and Jade integrations haven't been compiled. Other platforms, more than one guest, pipes from other mods and speed against the Java mod were not tested.
|
||||
|
||||
### Known issue
|
||||
In some test runs, placing a part into a cable cell did nothing. It isn't one specific part, and it's probably lost test clicks, but that isn't proven.
|
||||
|
||||
Textures and models are CC BY-NC-SA 3.0: noncommercial use only."""
|
||||
authors = ["AlgorithmX2", "TeamAppliedEnergistics", "Joeypc6"]
|
||||
owners = ["Joeypc6"]
|
||||
owner_ids = [35495484]
|
||||
license = "LGPL-3.0-only AND CC-BY-NC-SA-3.0"
|
||||
source = "https://github.com/Joeypc6/AE2-Horizon"
|
||||
icon = "icons/ae2.png"
|
||||
categories = ["technology", "utility", "storage"]
|
||||
tags = ["ae2", "applied-energistics", "storage", "autocrafting", "automation"]
|
||||
|
||||
[[versions]]
|
||||
version = "15.4.14"
|
||||
published = "2026-11-06"
|
||||
game = "26.3"
|
||||
file = "ae2-15.4.14.hnmod"
|
||||
sha256 = "584ec574b2620ccb9810a61f30b8bc4e639b5bf819bef7c3612835efbbdc278f"
|
||||
changelog = """
|
||||
First release on the registry, ported from AE2 15.4.12 (Fabric). 15.4.14 is this port's own version number, not an AE2 release.
|
||||
|
||||
Built for Horizon commit 66f3981. It compiles on that Horizon but wasn't run there; it was last tested on 6475f62.
|
||||
|
||||
One file: the blocks, items and textures, and the JEI and Jade integrations, are all in this mod. This replaces the earlier submissions, and fixes the `ae2:debug` vulnerability reported on them, along with others found by further reviews, and many defects from an outside review (PLAN.md, Rounds 24 to 26)."""
|
||||