From 6ef5fdd37829b75ab5489d144609c9492356501f Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 9 May 2026 20:59:05 +0200 Subject: [PATCH] Bootstrap Warium NeoForge port scaffold --- .codex/project.md | 76 ++ .gitea/workflows/build.yml | 114 +++ .gitea/workflows/dependency-check.yml | 114 +++ .gitea/workflows/release-dry-run.yml | 133 +++ .gitea/workflows/repo-cleanup.yml | 139 +++ .gitea/workflows/security-scan.yml | 174 ++++ .gitea/workflows/template-compliance.yml | 109 +++ .gitignore | 51 ++ AGENTS.md | 81 ++ CHANGELOG.md | 11 + CONTRIBUTING.md | 48 ++ README.md | 63 ++ SECURITY.md | 22 + build.gradle | 136 +++ ci/required-mods/.gitkeep | 1 + docs/agent-handoff.md | 43 + docs/inventory/original-inventory.json | 793 ++++++++++++++++++ docs/release-checklist.md | 39 + docs/release-notes.md | 33 + docs/security-review.md | 53 ++ gradle.properties | 21 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 251 ++++++ gradlew.bat | 94 +++ settings.gradle | 14 + .../crustychunks/CrustyChunksMod.java | 14 + .../resources/META-INF/neoforge.mods.toml | 67 ++ .../assets/crusty_chunks/lang/en_us.json | 3 + src/main/resources/pack.mcmeta | 6 + tools/check_required_integrations.py | 25 + tools/decompile_original.py | 40 + tools/generate_port_sources.py | 141 ++++ tools/prepare_runtime_mods.py | 50 ++ tools/registry_parity.py | 31 + tools/warium_source.py | 76 ++ 36 files changed, 3073 insertions(+) create mode 100644 .codex/project.md create mode 100644 .gitea/workflows/build.yml create mode 100644 .gitea/workflows/dependency-check.yml create mode 100644 .gitea/workflows/release-dry-run.yml create mode 100644 .gitea/workflows/repo-cleanup.yml create mode 100644 .gitea/workflows/security-scan.yml create mode 100644 .gitea/workflows/template-compliance.yml create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 build.gradle create mode 100644 ci/required-mods/.gitkeep create mode 100644 docs/agent-handoff.md create mode 100644 docs/inventory/original-inventory.json create mode 100644 docs/release-checklist.md create mode 100644 docs/release-notes.md create mode 100644 docs/security-review.md create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/net/mcreator/crustychunks/CrustyChunksMod.java create mode 100644 src/main/resources/META-INF/neoforge.mods.toml create mode 100644 src/main/resources/assets/crusty_chunks/lang/en_us.json create mode 100644 src/main/resources/pack.mcmeta create mode 100644 tools/check_required_integrations.py create mode 100644 tools/decompile_original.py create mode 100644 tools/generate_port_sources.py create mode 100644 tools/prepare_runtime_mods.py create mode 100644 tools/registry_parity.py create mode 100644 tools/warium_source.py diff --git a/.codex/project.md b/.codex/project.md new file mode 100644 index 0000000..bc8209f --- /dev/null +++ b/.codex/project.md @@ -0,0 +1,76 @@ +# Codex Project Notes + +## Project + +`Warium NeoForge 1.21.1 Port` is a private internal NeoForge 21.1.225 port scaffold for Warium 1.2.7. + +Repository: + +```text +MrSphay/Warium-NeoForge-1.21.1 +``` + +## Commands + +Use these commands as the source of truth: + +```text +python tools/generate_port_sources.py +python tools/registry_parity.py +./gradlew --no-daemon build +./gradlew --no-daemon runData +python tools/check_required_integrations.py +``` + +If a command does not exist, document the closest safe alternative. Do not invent commands that cannot run. + +## Stack + +```text +Java 21, Gradle, NeoGradle, NeoForge 21.1.225, Minecraft 1.21.1 +``` + +Package manager or build tool: + +```text +Gradle wrapper +``` + +## Build Artifacts + +Release artifacts are produced in: + +```text +build/libs +``` + +Expected files: + +```text +warium-neoforge-1.21.1-1.2.7+neo.21.1.225.jar +``` + +## Security Rules + +- Do not commit secrets, tokens, `.env` files, certificates, or private keys. +- Treat generated credentials as sensitive. +- Prefer local generation and local processing for user data. +- Keep dependency audit results visible in CI where possible. +- Do not add external network calls unless the feature explicitly requires them. +- Do not commit the original Warium jar, generated extracted assets, decompiled sources, or private integration jars. +- The source jar is downloaded from Modrinth in CI and verified against SHA1 `528d81630a23fb4004e3abdd99b16bd225cd1e92`. +- Keep the repository private until the Warium ARR/AFL license conflict is clarified. + +## Release Rules + +Before a release: + +1. run the release checklist, +2. verify CI is green, +3. verify download links, +4. update README and changelog, +5. verify the private Gitea package URL, +6. create a tag only if explicitly requested, +7. create the release only if explicitly requested. + +Do not create releases unless the user explicitly asks for a release. diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..fbd5c6a --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,114 @@ +name: Build + +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + env: + REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + PACKAGE_OWNER: MrSphay + PACKAGE_NAME: warium-neoforge-1.21.1 + APP_VERSION: 1.2.7-neo.21.1.225 + LATEST_FILE: warium-neoforge-1.21.1-latest.jar + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Java 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '21' + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Generate port sources + run: python tools/generate_port_sources.py + + - name: Decompile original for audit artifact + run: python tools/decompile_original.py + + - name: Check required integrations + run: python tools/check_required_integrations.py + + - name: Registry parity + run: python tools/registry_parity.py + + - name: Build + run: ./gradlew --no-daemon build + + - name: Run data generation + run: ./gradlew --no-daemon runData + + - name: Prepare runtime dependency mods + run: python tools/prepare_runtime_mods.py + + - name: Dedicated server smoke test + shell: bash + run: | + set -euo pipefail + mkdir -p run/server/mods + cp build/libs/*.jar run/server/mods/ + echo "eula=true" > run/server/eula.txt + timeout 120s ./gradlew --no-daemon runServer || code=$? + code="${code:-0}" + if [ "$code" != "0" ] && [ "$code" != "124" ]; then + exit "$code" + fi + + - name: Upload build artifacts + uses: actions/upload-artifact@v3 + with: + name: warium-neoforge-1.21.1-artifacts + path: | + build/libs/*.jar + docs/inventory/*.json + docs/inventory/*.md + build/decompiled/** + + - name: Publish private latest package + if: ${{ env.REGISTRY_TOKEN != '' }} + shell: bash + run: | + set -euo pipefail + package_version="${APP_VERSION}-${GITHUB_SHA::7}" + package_dir="package-registry" + mkdir -p "${package_dir}/versioned" "${package_dir}/latest" + + artifact="$(find build/libs -maxdepth 1 -type f -name '*.jar' ! -name '*-sources.jar' | head -n 1)" + if [ -z "${artifact}" ]; then + echo "No jar artifact found" + exit 1 + fi + + versioned_file="warium-neoforge-1.21.1-${package_version}.jar" + cp "${artifact}" "${package_dir}/versioned/${versioned_file}" + cp "${artifact}" "${package_dir}/latest/${LATEST_FILE}" + + curl --fail-with-body \ + --user "${PACKAGE_OWNER}:${REGISTRY_TOKEN}" \ + --upload-file "${package_dir}/versioned/${versioned_file}" \ + "https://git.wilkensxl.de/api/packages/${PACKAGE_OWNER}/generic/${PACKAGE_NAME}/${package_version}/${versioned_file}" + + curl --silent --show-error \ + --user "${PACKAGE_OWNER}:${REGISTRY_TOKEN}" \ + --request DELETE \ + "https://git.wilkensxl.de/api/packages/${PACKAGE_OWNER}/generic/${PACKAGE_NAME}/latest" || true + + curl --fail-with-body \ + --user "${PACKAGE_OWNER}:${REGISTRY_TOKEN}" \ + --upload-file "${package_dir}/latest/${LATEST_FILE}" \ + "https://git.wilkensxl.de/api/packages/${PACKAGE_OWNER}/generic/${PACKAGE_NAME}/latest/${LATEST_FILE}" + + curl --fail --head \ + --user "${PACKAGE_OWNER}:${REGISTRY_TOKEN}" \ + "https://git.wilkensxl.de/api/packages/${PACKAGE_OWNER}/generic/${PACKAGE_NAME}/latest/${LATEST_FILE}" diff --git a/.gitea/workflows/dependency-check.yml b/.gitea/workflows/dependency-check.yml new file mode 100644 index 0000000..f601304 --- /dev/null +++ b/.gitea/workflows/dependency-check.yml @@ -0,0 +1,114 @@ +name: Scheduled Dependency Check + +on: + schedule: + - cron: "29 3 * * 2" + workflow_dispatch: + +jobs: + dependency-check: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Detect project stack + id: detect + shell: bash + run: | + stacks="" + + [ -f package.json ] && stacks="${stacks} node" + { [ -f pyproject.toml ] || [ -f requirements.txt ]; } && stacks="${stacks} python" + [ -f Cargo.toml ] && stacks="${stacks} rust" + [ -f go.mod ] && stacks="${stacks} go" + { [ -f Dockerfile ] || [ -f compose.yml ] || [ -f docker-compose.yml ]; } && stacks="${stacks} docker" + + echo "stacks=${stacks:-generic}" >> "$GITHUB_OUTPUT" + echo "Detected stacks:${stacks:- generic}" + + - name: Node dependency report + if: contains(steps.detect.outputs.stacks, 'node') + shell: bash + run: | + if [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then + npm ci + else + npm install --package-lock-only --ignore-scripts + fi + + echo "Security audit:" + npm audit --omit=dev --audit-level=high + + echo + echo "Outdated dependencies:" + npm outdated || true + + - name: Python dependency report + if: contains(steps.detect.outputs.stacks, 'python') + shell: bash + run: | + python -m pip install --upgrade pip pip-audit + + echo "Security audit:" + if [ -f requirements.txt ]; then + pip-audit -r requirements.txt + else + pip-audit + fi + + echo + echo "Outdated packages:" + python -m pip list --outdated || true + + - name: Rust dependency report + if: contains(steps.detect.outputs.stacks, 'rust') + shell: bash + run: | + cargo install cargo-audit cargo-outdated --locked + + echo "Security audit:" + cargo audit + + echo + echo "Outdated crates:" + cargo outdated || true + + - name: Go dependency report + if: contains(steps.detect.outputs.stacks, 'go') + shell: bash + run: | + go install golang.org/x/vuln/cmd/govulncheck@latest + + echo "Security audit:" + govulncheck ./... + + echo + echo "Available dependency updates:" + go list -u -m all || true + + - name: Docker base image report + if: contains(steps.detect.outputs.stacks, 'docker') + shell: bash + run: | + echo "Docker image references:" + grep -RInE --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build '^\s*FROM\s+' Dockerfile* . 2>/dev/null || true + + echo + echo "Review Docker base images manually for pinned versions, official sources, and current security status." + + - name: Dependency guidance + shell: bash + run: | + cat <<'EOF' + Dependency check completed. + + This workflow reports vulnerabilities and available updates. It does + not modify dependency files, create pull requests, or publish packages. + + Recommended manual follow-up: + - update dependencies in a focused branch, + - run the project test/build commands, + - review lockfile diffs carefully, + - document intentionally held versions. + EOF diff --git a/.gitea/workflows/release-dry-run.yml b/.gitea/workflows/release-dry-run.yml new file mode 100644 index 0000000..25e61ab --- /dev/null +++ b/.gitea/workflows/release-dry-run.yml @@ -0,0 +1,133 @@ +name: Release Dry Run + +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + release-dry-run: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Inspect release metadata + shell: bash + run: | + missing=0 + + required_docs=( + "README.md" + "CHANGELOG.md" + "SECURITY.md" + "docs/release-checklist.md" + ) + + for file in "${required_docs[@]}"; do + if [ ! -f "$file" ]; then + echo "Missing release document: $file" + missing=1 + fi + done + + placeholder_paths=(README.md AGENTS.md .codex docs) + placeholder_pattern='PROJECT_NAME|PROJECT_DESCRIPTION|REPOSITORY_OWNER|REPOSITORY_NAME|PACKAGE_NAME|ARTIFACT_NAME|ARTIFACT_OUTPUT_DIRECTORY|DOWNLOAD_URL|BUILD_COMMAND|TEST_COMMAND|LINT_COMMAND|AUDIT_COMMAND' + + for path in "${placeholder_paths[@]}"; do + [ -e "$path" ] || continue + if grep -RInE --exclude-dir=.git "$placeholder_pattern" "$path"; then + echo "Unresolved template placeholders found." + missing=1 + fi + done + + if [ "$missing" -eq 1 ]; then + exit 1 + fi + + - name: Detect project stack + id: detect + shell: bash + run: | + stacks="" + + [ -f package.json ] && stacks="${stacks} node" + { [ -f pyproject.toml ] || [ -f requirements.txt ]; } && stacks="${stacks} python" + [ -f Cargo.toml ] && stacks="${stacks} rust" + [ -f go.mod ] && stacks="${stacks} go" + + echo "stacks=${stacks:-generic}" >> "$GITHUB_OUTPUT" + echo "Detected stacks:${stacks:- generic}" + + - name: Node release checks + if: contains(steps.detect.outputs.stacks, 'node') + shell: bash + run: | + if [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then + npm ci + else + npm install + fi + + node -e "const p=require('./package.json'); if(!p.name||!p.version){throw new Error('package.json needs name and version')}; console.log(p.name+'@'+p.version)" + + npm run lint --if-present + npm test --if-present + npm run build --if-present + npm run release:check --if-present + + - name: Python release checks + if: contains(steps.detect.outputs.stacks, 'python') + shell: bash + run: | + python -m pip install --upgrade pip + + if [ -f requirements.txt ]; then + python -m pip install -r requirements.txt + fi + + if [ -f pyproject.toml ]; then + python -m pip install build + python -m build + else + echo "No pyproject.toml found; skipped Python package build." + fi + + - name: Rust release checks + if: contains(steps.detect.outputs.stacks, 'rust') + shell: bash + run: | + cargo test + cargo build --release + + - name: Go release checks + if: contains(steps.detect.outputs.stacks, 'go') + shell: bash + run: | + go test ./... + go build ./... + + - name: Artifact report + shell: bash + run: | + echo "Potential release artifacts:" + find . \ + -path ./.git -prune -o \ + -path ./node_modules -prune -o \ + -path './dist/*' -type f -print -o \ + -path './build/*' -type f -print -o \ + -path './release/*' -type f -print -o \ + -path './target/release/*' -type f -print \ + | sed 's#^\./##' \ + | head -200 + + cat <<'EOF' + + Release dry run completed. + + This workflow verifies release readiness. It does not create tags, + releases, packages, or upload artifacts. + EOF diff --git a/.gitea/workflows/repo-cleanup.yml b/.gitea/workflows/repo-cleanup.yml new file mode 100644 index 0000000..a4b7156 --- /dev/null +++ b/.gitea/workflows/repo-cleanup.yml @@ -0,0 +1,139 @@ +name: Scheduled Repository Cleanup Check + +on: + schedule: + - cron: "43 3 * * 1" + workflow_dispatch: + +jobs: + cleanup-check: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check ignored and untracked generated files + shell: bash + run: | + echo "Ignored files that would be skipped by git:" + git status --ignored --short || true + + echo + echo "Tracked generated files check:" + generated_patterns=( + '(^|/)node_modules/' + '(^|/)dist/' + '(^|/)build/' + '(^|/)out/' + '(^|/)release/' + '(^|/)target/' + '(^|/)coverage/' + '\.log$' + '\.tmp$' + '\.temp$' + ) + + found=0 + tracked_files="$(git ls-files)" + for pattern in "${generated_patterns[@]}"; do + if echo "$tracked_files" | grep -Ei "$pattern"; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Generated files appear to be tracked. Review .gitignore and remove generated outputs from version control if appropriate." + exit 1 + fi + + - name: Check large tracked files + shell: bash + run: | + limit_bytes="${LARGE_FILE_LIMIT_BYTES:-5242880}" + found=0 + + while IFS= read -r file; do + [ -f "$file" ] || continue + size="$(wc -c < "$file")" + if [ "$size" -gt "$limit_bytes" ]; then + echo "${file} is ${size} bytes, above limit ${limit_bytes}." + found=1 + fi + done < <(git ls-files) + + if [ "$found" -eq 1 ]; then + echo "Large tracked files found. Move release artifacts to packages/releases or document why they belong in git." + exit 1 + fi + + - name: Check local config and secret-prone files + shell: bash + run: | + found=0 + + risky_patterns=( + '^\.env$' + '^\.env\.' + '\.pfx$' + '\.p12$' + '\.pem$' + '\.key$' + '\.token$' + '(^|/)secrets/' + ) + + tracked_files="$(git ls-files)" + for pattern in "${risky_patterns[@]}"; do + if echo "$tracked_files" | grep -Ei "$pattern" | grep -vE '^\.env\.example$'; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Secret-prone local config files are tracked. Review immediately." + exit 1 + fi + + - name: Check stale branches + shell: bash + run: | + git fetch --all --prune + + protected='^(main|master|develop|dev|release|staging|production)$' + cutoff="$(date -u -d '90 days ago' +%s)" + found=0 + + while IFS='|' read -r branch timestamp; do + branch="${branch#origin/}" + [ "$branch" = "HEAD" ] && continue + echo "$branch" | grep -Eq "$protected" && continue + + if [ "$timestamp" -lt "$cutoff" ]; then + echo "Stale remote branch candidate: ${branch}" + found=1 + fi + done < <(git for-each-ref refs/remotes/origin --format='%(refname:short)|%(committerdate:unix)') + + if [ "$found" -eq 1 ]; then + echo "Stale branch candidates found. Review manually before deleting anything." + exit 1 + fi + + - name: Cleanup guidance + shell: bash + run: | + cat <<'EOF' + Repository cleanup check completed. + + This workflow reports cleanup candidates. It does not delete branches, + packages, releases, or files automatically. + + Recommended manual follow-up: + - remove generated files from git, + - update .gitignore, + - move large artifacts to releases or package registry, + - review stale branches, + - document intentional exceptions. + EOF diff --git a/.gitea/workflows/security-scan.yml b/.gitea/workflows/security-scan.yml new file mode 100644 index 0000000..d514dcc --- /dev/null +++ b/.gitea/workflows/security-scan.yml @@ -0,0 +1,174 @@ +name: Scheduled Security Scan + +on: + schedule: + - cron: "17 3 * * 1" + workflow_dispatch: + +jobs: + security-scan: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Detect project stack + id: detect + shell: bash + run: | + stacks="" + + [ -f package.json ] && stacks="${stacks} node" + { [ -f pyproject.toml ] || [ -f requirements.txt ]; } && stacks="${stacks} python" + [ -f Cargo.toml ] && stacks="${stacks} rust" + [ -f go.mod ] && stacks="${stacks} go" + { [ -f Dockerfile ] || [ -f compose.yml ] || [ -f docker-compose.yml ]; } && stacks="${stacks} docker" + + echo "stacks=${stacks:-generic}" >> "$GITHUB_OUTPUT" + echo "Detected stacks:${stacks:- generic}" + + - name: Node production dependency audit + if: contains(steps.detect.outputs.stacks, 'node') + run: npm audit --omit=dev --audit-level=high + + - name: Python dependency audit + if: contains(steps.detect.outputs.stacks, 'python') + shell: bash + run: | + python -m pip install --upgrade pip pip-audit + if [ -f requirements.txt ]; then + pip-audit -r requirements.txt + else + pip-audit + fi + + - name: Rust dependency audit + if: contains(steps.detect.outputs.stacks, 'rust') + shell: bash + run: | + cargo install cargo-audit --locked + cargo audit + + - name: Go vulnerability scan + if: contains(steps.detect.outputs.stacks, 'go') + shell: bash + run: | + go install golang.org/x/vuln/cmd/govulncheck@latest + govulncheck ./... + + - name: Suspicious code pattern scan + shell: bash + run: | + grep_excludes=( + --exclude-dir=.git + --exclude-dir=node_modules + --exclude-dir=dist + --exclude-dir=build + --exclude-dir=release + --exclude=security-scan.yml + ) + + patterns=( + 'eval\s*\(' + 'new Function\s*\(' + 'dangerouslySetInnerHTML' + 'innerHTML\s*=' + 'child_process' + 'exec\s*\(' + 'spawn\s*\(' + 'shell\.openExternal' + 'nodeIntegration:\s*true' + 'webSecurity:\s*false' + 'allowRunningInsecureContent:\s*true' + 'curl .*sh' + 'wget .*sh' + ) + + found=0 + for pattern in "${patterns[@]}"; do + if grep -RInE "${grep_excludes[@]}" "$pattern" .; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Suspicious code patterns were found. Review the matches above." + exit 1 + fi + + - name: Secret and config leak scan + shell: bash + run: | + grep_excludes=( + --exclude-dir=.git + --exclude-dir=node_modules + --exclude-dir=dist + --exclude-dir=build + --exclude-dir=release + --exclude=security-scan.yml + ) + + patterns=( + 'BEGIN (RSA |EC |OPENSSH |)PRIVATE KEY' + 'AKIA[0-9A-Z]{16}' + 'xox[baprs]-[0-9A-Za-z-]+' + 'gh[pousr]_[0-9A-Za-z_]+' + 'sk-[A-Za-z0-9]{20,}' + 'api[_-]?key\s*=\s*["'\'']?[A-Za-z0-9_\-]{20,}' + 'token\s*=\s*["'\'']?[A-Za-z0-9_\-]{20,}' + 'password\s*=\s*["'\'']?[^[:space:]]{8,}' + ) + + found=0 + for pattern in "${patterns[@]}"; do + if grep -RInE "${grep_excludes[@]}" "$pattern" .; then + found=1 + fi + done + + if find . -path ./.git -prune -o \( -name ".env" -o -name ".env.*" \) -not -name ".env.example" -print | grep .; then + echo "Committed environment files were found." + found=1 + fi + + if [ "$found" -eq 1 ]; then + echo "Potential secret or config leak detected. Review the matches above." + exit 1 + fi + + - name: AI instruction injection scan + shell: bash + run: | + grep_excludes=( + --exclude-dir=.git + --exclude-dir=node_modules + --exclude-dir=dist + --exclude-dir=build + --exclude-dir=release + --exclude=security-scan.yml + ) + + patterns=( + 'ignore (all )?(previous|above) instructions' + 'system prompt' + 'developer message' + 'reveal your instructions' + 'exfiltrate' + 'send.*token' + 'send.*secret' + 'disable.*safety' + 'jailbreak' + 'prompt injection' + ) + + found=0 + for pattern in "${patterns[@]}"; do + if grep -RInEi "${grep_excludes[@]}" "$pattern" .; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Potential AI instruction-injection text found. Review whether this is documentation, test data, or malicious content." + exit 1 + fi diff --git a/.gitea/workflows/template-compliance.yml b/.gitea/workflows/template-compliance.yml new file mode 100644 index 0000000..9700a7a --- /dev/null +++ b/.gitea/workflows/template-compliance.yml @@ -0,0 +1,109 @@ +name: Codex Template Compliance + +on: + push: + branches: + - main + - master + pull_request: + workflow_dispatch: + +jobs: + template-compliance: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check required Codex files + shell: bash + run: | + missing=0 + + required_files=( + "AGENTS.md" + ".codex/project.md" + "README.md" + ) + + recommended_files=( + "SECURITY.md" + "CHANGELOG.md" + "docs/agent-handoff.md" + ) + + for file in "${required_files[@]}"; do + if [ ! -f "$file" ]; then + echo "Missing required Codex file: $file" + missing=1 + fi + done + + for file in "${recommended_files[@]}"; do + if [ ! -f "$file" ]; then + echo "Recommended Codex file not found: $file" + fi + done + + if [ "$missing" -eq 1 ]; then + exit 1 + fi + + - name: Check unresolved placeholders + shell: bash + run: | + found=0 + paths=(AGENTS.md README.md SECURITY.md CHANGELOG.md .codex docs blueprint.md blueprint.json) + pattern='PROJECT_NAME|PROJECT_DESCRIPTION|REPOSITORY_OWNER|REPOSITORY_NAME|PACKAGE_NAME|ARTIFACT_NAME|ARTIFACT_OUTPUT_DIRECTORY|AUTHOR_NAME|PROJECT_STACK|DOWNLOAD_URL|BUILD_COMMAND|TEST_COMMAND|LINT_COMMAND|AUDIT_COMMAND|README_COMMAND|INSTALL_COMMAND|DEV_COMMAND|PACKAGE_MANAGER|PROJECT_VERSION' + + for path in "${paths[@]}"; do + [ -e "$path" ] || continue + if grep -RInE --exclude-dir=.git "$pattern" "$path"; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Unresolved template placeholders found. Replace real values or mark genuinely unknown values as PENDING." + exit 1 + fi + + - name: Check README divider convention + shell: bash + run: | + if [ -f blueprint.md ] || [ -f blueprint.json ]; then + if ! grep -q 'template:section-line' blueprint.md 2>/dev/null; then + echo "README blueprint exists but does not use {{ template:section-line }}." + exit 1 + fi + fi + + - name: Check workflow baseline + shell: bash + run: | + echo "Detected Gitea workflows:" + find .gitea/workflows -maxdepth 1 -type f -name '*.yml' -print 2>/dev/null || true + + if [ ! -f ".gitea/workflows/security-scan.yml" ]; then + echo "Recommended workflow missing: .gitea/workflows/security-scan.yml" + fi + + if [ ! -f ".gitea/workflows/repo-cleanup.yml" ]; then + echo "Recommended workflow missing: .gitea/workflows/repo-cleanup.yml" + fi + + - name: Compliance guidance + shell: bash + run: | + cat <<'EOF' + Codex template compliance check completed. + + This workflow verifies agent context and template hygiene. It does + not change files automatically. + + Recommended manual follow-up: + - add missing required Codex context files, + - replace unresolved placeholders, + - keep README blueprint and README output aligned, + - document intentional exceptions in .codex/project.md. + EOF diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67af881 --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +# Dependencies +node_modules/ +vendor/ +.venv/ +venv/ +__pycache__/ + +# Build outputs +dist/ +build/ +out/ +release/ +target/ +bin/ +obj/ +run/ +src/generated/ +repo/ +*.jar +!gradle/wrapper/gradle-wrapper.jar + +# Logs and temporary files +*.log +*.tmp +*.temp +.cache/ +.turbo/ +.vite/ +.pytest_cache/ + +# Local environment and secrets +.env +.env.* +!.env.example +*.pem +*.key +*.pfx +*.p12 +*.crt +*.cer +*.token +secrets/ + +# OS and editor files +.DS_Store +Thumbs.db +.idea/ +.vscode/ +*.swp +*.swo + diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..51f66dc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,81 @@ +# Agent Instructions + +## Project + +Warium NeoForge 1.21.1 Port: private internal NeoForge 21.1.225 port scaffold for Warium 1.2.7. + +## Repository Rules + +- This repository was bootstrapped from `codex-agent-repository-kit`; preserve the kit-maintenance conventions in `.codex/project.md`, `docs/`, and `.gitea/workflows/`. +- Use NeoForge/Gradle conventions for Java 21 Minecraft mod work. +- Prefer existing project patterns over new abstractions. +- Keep changes scoped to the user's request. +- Do not commit secrets, `.env` files, private keys, certificates, or tokens. +- Do not rewrite history or run destructive git commands unless explicitly requested. +- Do not create a release unless explicitly requested. +- Check `git status --short` before editing and before finishing. Preserve unrelated user changes. +- Replace all applicable placeholders. Remove non-applicable placeholder sections instead of leaving fake values. +- If `GITEA_TOKEN` is available locally, use it only for read-only Gitea API checks such as private repository metadata, package-read visibility, and Actions run status. Never print, commit, or store the token. +- After pushing commits that trigger a Gitea workflow, poll the workflow run until it succeeds. If it fails or is cancelled, inspect the failing job/logs, fix the issue when in scope, push again, and repeat the workflow check loop. Fixing and pushing a workflow failure is not a stopping point. +- When the project uses `blueprint.md` and `blueprint.json` for README generation, keep the rainbow `{{ template:section-line }}` divider between major README sections. Do not replace it with plain `---` unless the target renderer cannot display inline images. +- If README blueprint files are changed, regenerate or update `README.md` in the same change and verify the generated output renders reasonably. +- For releasable projects, add or preserve `.gitea/workflows/security-scan.yml` using `files/security-scan-gitea.yml` unless the repository already has equivalent scheduled security automation. +- For active projects, add or preserve `.gitea/workflows/repo-cleanup.yml` using `files/repo-cleanup-gitea.yml` unless the repository already has equivalent cleanup checks. +- Add or preserve `.gitea/workflows/dependency-check.yml`, `.gitea/workflows/release-dry-run.yml`, and `.gitea/workflows/template-compliance.yml` when the repository is active, releasable, or intended as a Codex-maintained project. +- Repository cleanup automation must be non-destructive. Do not delete branches, packages, releases, or tracked files without explicit user approval. +- Dependency, compliance, and release dry-run automation must report findings only. Do not auto-update dependencies, auto-open PRs, create tags, publish packages, or create releases without explicit user approval. +- Gitea Actions artifacts are not Gitea Package Registry packages. If the user expects a package/download entry, add an explicit registry publish step and verify the package URL after the workflow succeeds. + +## Commands + +Use these commands when available: + +```bash +python tools/generate_port_sources.py +python tools/registry_parity.py +./gradlew --no-daemon build +./gradlew --no-daemon runData +``` + +If a command is missing, inspect the project and document the closest safe alternative in `.codex/project.md`. + +Keep `.codex/project.md` and this `AGENTS.md` aligned when commands, artifact paths, or release rules change. + +## Artifacts + +Expected artifact output: + +```text +build/libs +``` + +Expected artifact names: + +```text +warium-neoforge-1.21.1-1.2.7+neo.21.1.225.jar +``` + +## Security Notes + +- Review `docs/security-review.md` before release work. +- Fill `docs/security-review.md` with actual checked commands and results when performing release-readiness work. +- Review scheduled security workflow failures before changing code. Treat matches as leads: they may be true positives, documentation examples, or test fixtures. +- Review repository cleanup workflow failures as maintenance leads. Document intentional exceptions instead of blindly deleting files. +- Review dependency and template compliance workflow failures as maintenance leads. Preserve project-specific conventions when they are documented. +- Treat generated credentials and config files as sensitive. +- Keep external network calls documented. +- Prefer local processing for user data. +- Keep CI publishing secrets in repository or organization secrets, not in tracked files. `REGISTRY_TOKEN` is the default package publishing secret name for the Gitea workflow template. +- Use URL-safe package filenames when publishing to a registry. Do not put raw artifact names with spaces or punctuation directly into upload URLs. +- Ensure `.gitignore` covers local config, build outputs, logs, temporary files, and secret material for the detected stack. +- The original Warium distribution has conflicting public license signals: Modrinth reports ARR while the jar metadata reports AFL-3.0. Keep this repository and packages private until rights are explicitly clarified. +- Do not commit the original Warium jar, decompiled source output, generated original assets, or private dependency jars. + +## Finish Checklist + +- `git diff --check` passes. +- The cheapest reliable verification command has been run, or the reason it could not be run is documented. +- README, changelog, security review, and release checklist are updated when the change touches release behavior. +- `docs/agent-handoff.md` is updated when work is interrupted, risky, or spans multiple sessions. +- Any pushed Gitea workflow has been polled to success or a concrete blocker has been reported. + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..00724f3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project are documented here. + +## Unreleased + +- Bootstrapped private Warium NeoForge 1.21.1 port repository. +- Added NeoForge 21.1.225 Gradle build scaffold. +- Added runner scripts to download, verify, inspect, decompile, and generate registry stubs from Warium 1.2.7. +- Added private Gitea artifact/package workflow with latest package URL. + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e177c25 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,48 @@ +# Contributing + +## Working Rules + +- Keep changes scoped to the issue or user request. +- Prefer existing project patterns. +- Do not commit secrets, generated credentials, local `.env` files, or private keys. +- Do not create releases unless explicitly requested. +- Preserve unrelated user changes. + +## Before Committing + +Run the cheapest reliable verification commands for this project: + +```bash +LINT_COMMAND +TEST_COMMAND +BUILD_COMMAND +``` + +Also run: + +```bash +git diff --check +``` + +If a command cannot run, document why in the final response or handoff notes. + +## Pull Requests + +Pull requests should include: + +- summary of changes, +- verification performed, +- known risks or skipped checks, +- artifact/download notes when relevant. + +## Releases + +Before release work, update: + +```text +CHANGELOG.md +docs/release-checklist.md +docs/security-review.md +README.md +``` + diff --git a/README.md b/README.md new file mode 100644 index 0000000..f908511 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# Warium NeoForge 1.21.1 + +A private internal NeoForge `21.1.225` port workspace for Warium `1.2.7`. + +

-----------------------------------------------------

+ +## Status + +This repository is private until rights are clarified. Modrinth reports Warium as ARR, while the original jar metadata reports `Academic Free License v3.0`; do not make this repository, decompiled source, or generated jar public until that conflict is resolved. + +The current implementation is a reproducible NeoForge port scaffold. CI downloads and verifies the original Warium jar, extracts resources, generates 1.21.1 registry stubs, builds a NeoForge jar, and records inventory/decompile artifacts for the remaining manual behavior port. + +

-----------------------------------------------------

+ +## Download + +Private latest package after a successful Gitea build: + +`https://git.wilkensxl.de/api/packages/MrSphay/generic/warium-neoforge-1.21.1/latest/warium-neoforge-1.21.1-latest.jar` + +The link requires access to the private Gitea package. + +

-----------------------------------------------------

+ +## Build + +```bash +./gradlew --no-daemon build +``` + +Important supporting commands: + +```bash +python tools/generate_port_sources.py +python tools/decompile_original.py +python tools/registry_parity.py +python tools/check_required_integrations.py +./gradlew --no-daemon runData +``` + +Local Java is not required for orchestration, but the build itself requires Java 21. The Gitea runner is the intended build environment. + +

-----------------------------------------------------

+ +## Porting Order + +1. Preserve registry IDs under `crusty_chunks`. +2. Port metadata, resources, tags, recipes, loot, worldgen, creative tabs, blocks, items, fluids, sounds, and particles. +3. Port machines, block entities, menus, networking, energy, fluids, reactors, bombs, and warheads. +4. Port weapons, ordnance, projectiles, GeckoLib animations, Ritchie's Projectile Library behavior, and AI. +5. Replace WariumAPI/WariumVS compatibility shims with real NeoForge 1.21.1 integrations when available. + +

-----------------------------------------------------

+ +## Source + +Original source artifact: [Warium 1.2.7 on Modrinth](https://modrinth.com/mod/warium/version/1.2.7) + +Expected SHA1: + +```text +528d81630a23fb4004e3abdd99b16bd225cd1e92 +``` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..b01b725 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,22 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| --- | --- | +| Latest | Yes | + +## Reporting A Vulnerability + +Please report security issues privately to the project owner. + +Do not include secrets, production data, or private credentials in public issues. + +## Project Security Principles + +- Keep secrets out of the repository. +- Prefer local processing for user data. +- Document external network calls. +- Keep release artifacts reproducible through CI. +- Run dependency audits before releases. + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..f9c0008 --- /dev/null +++ b/build.gradle @@ -0,0 +1,136 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'net.neoforged.gradle.userdev' version '7.1.26' +} + +version = mod_version +group = mod_group_id + +base { + archivesName = 'warium-neoforge-1.21.1' +} + +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + +sourceSets { + main { + java { + srcDir 'src/generated/java' + } + resources { + srcDir 'src/generated/resources' + exclude '**/*.bbmodel' + exclude 'src/generated/**/.cache' + } + } +} + +repositories { + maven { + name = 'NeoForge' + url = 'https://maven.neoforged.net/releases' + } +} + +configurations { + runtimeClasspath.extendsFrom localRuntime +} + +dependencies { + implementation "net.neoforged:neoforge:${neo_version}" +} + +runs { + configureEach { + systemProperty 'forge.logging.markers', 'REGISTRIES' + systemProperty 'forge.logging.console.level', 'debug' + workingDirectory project.layout.projectDirectory.dir('run').dir(name) + modSource project.sourceSets.main + } + + client { + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + server { + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + argument '--nogui' + } + + gameTestServer { + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + data { + arguments.addAll '--mod', project.mod_id, '--all', + '--output', file('src/generated/resources/').getAbsolutePath(), + '--existing', file('src/main/resources/').getAbsolutePath() + } +} + +tasks.register('generatePortSources', Exec) { + group = 'warium port' + description = 'Downloads Warium 1.2.7, verifies it, extracts resources, and generates NeoForge registry stubs.' + commandLine 'python', 'tools/generate_port_sources.py' +} + +tasks.register('decompileOriginal', Exec) { + group = 'warium port' + description = 'Downloads and decompiles the original Warium jar for manual porting work.' + commandLine 'python', 'tools/decompile_original.py' +} + +tasks.register('checkRequiredIntegrations', Exec) { + group = 'verification' + description = 'Checks required Warium ecosystem integration availability.' + commandLine 'python', 'tools/check_required_integrations.py' +} + +tasks.register('registryParity', Exec) { + group = 'verification' + description = 'Checks generated registry stubs against the original jar inventory.' + commandLine 'python', 'tools/registry_parity.py' + dependsOn tasks.named('generatePortSources') +} + +compileJava.dependsOn tasks.named('generatePortSources') +processResources.dependsOn tasks.named('generatePortSources') +build.dependsOn tasks.named('registryParity') + +tasks.withType(ProcessResources).configureEach { + var replaceProperties = [ + minecraft_version : minecraft_version, + minecraft_version_range: minecraft_version_range, + neo_version : neo_version, + neo_version_range : neo_version_range, + loader_version_range : loader_version_range, + mod_id : mod_id, + mod_name : mod_name, + mod_license : mod_license, + mod_version : mod_version, + mod_authors : mod_authors, + mod_description : mod_description + ] + inputs.properties replaceProperties + filesMatching(['META-INF/neoforge.mods.toml']) { + expand replaceProperties + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +publishing { + publications { + register('mavenJava', MavenPublication) { + from components.java + } + } + repositories { + maven { + url "file://${project.projectDir}/repo" + } + } +} diff --git a/ci/required-mods/.gitkeep b/ci/required-mods/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ci/required-mods/.gitkeep @@ -0,0 +1 @@ + diff --git a/docs/agent-handoff.md b/docs/agent-handoff.md new file mode 100644 index 0000000..a8e2422 --- /dev/null +++ b/docs/agent-handoff.md @@ -0,0 +1,43 @@ +# Agent Handoff + +Use this file when a task spans multiple sessions, has unresolved follow-up work, or changes release behavior. + +## Current State + +```text +Private NeoForge 21.1.225 port scaffold is implemented. It generates registry stubs and extracted resources from the verified Warium 1.2.7 jar in CI, but original behavior systems still need manual porting from the decompiled output. +``` + +## Changes Made + +- Bootstrapped repository from `codex-agent-repository-kit`. +- Added NeoForge/Gradle Java 21 project files. +- Added scripts for Warium jar download, SHA1 verification, inventory extraction, source/resource generation, decompilation, runtime dependency download, and registry parity checks. +- Added Gitea build workflow with artifact upload, private package publishing, and server smoke-test step. + +## Verification + +| Check | Result | +| --- | --- | +| `python tools/generate_port_sources.py` | Passed locally with bundled Python | +| `python tools/registry_parity.py` | Passed locally: 448 blocks, 326 standalone items | +| `python tools/prepare_runtime_mods.py` | Passed locally for GeckoLib and RPL | +| `./gradlew --no-daemon build` | Pending Gitea runner; local Java is not installed | +| `./gradlew --no-daemon runData` | Pending Gitea runner; local Java is not installed | + +## Open Questions + +- Warium license conflict remains unresolved: Modrinth shows ARR, jar metadata shows AFL-3.0. +- Real NeoForge 1.21.1 WariumAPI/WariumVS artifacts were not found during planning; the repo currently declares compatibility shim mod metadata. + +## Next Steps + +- Push to `MrSphay/Warium-NeoForge-1.21.1` and poll Gitea Actions. +- Fix any Gradle/NeoForge compile errors found by the runner. +- Port original MCreator behavior systems from `build/decompiled/warium-1.2.7`. +- Replace WariumAPI/WariumVS shims with real dependencies when available. + +## Risks + +- Current jar is not a complete gameplay port; it preserves many registry/resource IDs but not the original machine, weapon, projectile, AI, GUI, networking, or nuclear behavior. +- Public release must wait for rights clearance. diff --git a/docs/inventory/original-inventory.json b/docs/inventory/original-inventory.json new file mode 100644 index 0000000..1c52034 --- /dev/null +++ b/docs/inventory/original-inventory.json @@ -0,0 +1,793 @@ +{ + "blocks_from_blockstates": [ + "active_robot_chute", + "advanced_alloy_block", + "after_burner", + "ai_mine", + "aimer_node", + "aluminum_ac_barrel", + "aluminum_ac_barrel_black", + "aluminum_ac_barrel_blue", + "aluminum_ac_barrel_brown", + "aluminum_ac_barrel_cyan", + "aluminum_ac_barrel_dark_gray", + "aluminum_ac_barrel_gray", + "aluminum_ac_barrel_green", + "aluminum_ac_barrel_light_blue", + "aluminum_ac_barrel_light_gray", + "aluminum_ac_barrel_lime", + "aluminum_ac_barrel_magenta", + "aluminum_ac_barrel_orange", + "aluminum_ac_barrel_pink", + "aluminum_ac_barrel_purple", + "aluminum_ac_barrel_red", + "aluminum_ac_barrel_white", + "aluminum_ac_barrel_yellow", + "aluminum_block", + "aluminum_plating", + "aluminum_plating_black", + "aluminum_plating_blue", + "aluminum_plating_brown", + "aluminum_plating_cyan", + "aluminum_plating_dark_gray", + "aluminum_plating_gray", + "aluminum_plating_green", + "aluminum_plating_light_blue", + "aluminum_plating_light_gray", + "aluminum_plating_lime", + "aluminum_plating_magenta", + "aluminum_plating_orange", + "aluminum_plating_pink", + "aluminum_plating_purple", + "aluminum_plating_red", + "aluminum_plating_slab", + "aluminum_plating_slab_black", + "aluminum_plating_slab_blue", + "aluminum_plating_slab_brown", + "aluminum_plating_slab_cyan", + "aluminum_plating_slab_dark_gray", + "aluminum_plating_slab_gray", + "aluminum_plating_slab_green", + "aluminum_plating_slab_light_blue", + "aluminum_plating_slab_light_gray", + "aluminum_plating_slab_lime", + "aluminum_plating_slab_magenta", + "aluminum_plating_slab_orange", + "aluminum_plating_slab_pink", + "aluminum_plating_slab_purple", + "aluminum_plating_slab_red", + "aluminum_plating_slab_white", + "aluminum_plating_slab_yellow", + "aluminum_plating_stairs", + "aluminum_plating_stairs_black", + "aluminum_plating_stairs_blue", + "aluminum_plating_stairs_brown", + "aluminum_plating_stairs_cyan", + "aluminum_plating_stairs_dark_gray", + "aluminum_plating_stairs_gray", + "aluminum_plating_stairs_green", + "aluminum_plating_stairs_light_blue", + "aluminum_plating_stairs_light_gray", + "aluminum_plating_stairs_lime", + "aluminum_plating_stairs_magenta", + "aluminum_plating_stairs_orange", + "aluminum_plating_stairs_pink", + "aluminum_plating_stairs_purple", + "aluminum_plating_stairs_red", + "aluminum_plating_stairs_white", + "aluminum_plating_stairs_yellow", + "aluminum_plating_trapdoor", + "aluminum_plating_trapdoor_black", + "aluminum_plating_trapdoor_blue", + "aluminum_plating_trapdoor_brown", + "aluminum_plating_trapdoor_cyan", + "aluminum_plating_trapdoor_dark_gray", + "aluminum_plating_trapdoor_gray", + "aluminum_plating_trapdoor_green", + "aluminum_plating_trapdoor_light_blue", + "aluminum_plating_trapdoor_light_gray", + "aluminum_plating_trapdoor_lime", + "aluminum_plating_trapdoor_magenta", + "aluminum_plating_trapdoor_orange", + "aluminum_plating_trapdoor_pink", + "aluminum_plating_trapdoor_purple", + "aluminum_plating_trapdoor_red", + "aluminum_plating_trapdoor_white", + "aluminum_plating_trapdoor_yellow", + "aluminum_plating_white", + "aluminum_plating_yellow", + "aluminum_side_panel", + "aluminum_side_panel_black", + "aluminum_side_panel_blue", + "aluminum_side_panel_brown", + "aluminum_side_panel_cyan", + "aluminum_side_panel_dark_gray", + "aluminum_side_panel_gray", + "aluminum_side_panel_green", + "aluminum_side_panel_light_blue", + "aluminum_side_panel_light_gray", + "aluminum_side_panel_lime", + "aluminum_side_panel_magenta", + "aluminum_side_panel_orange", + "aluminum_side_panel_pink", + "aluminum_side_panel_purple", + "aluminum_side_panel_red", + "aluminum_side_panel_white", + "aluminum_side_panel_yellow", + "ancient_light", + "ancient_well", + "artillery_autoloader", + "artillery_barrel", + "artillery_charge_loader", + "artillerybreech", + "asphalt", + "asphalt_slab", + "assembly_centrifuge_bottom", + "assembly_centrifuge_middle", + "assembly_centrifuge_top", + "assembly_circuit_fabricator", + "assembly_crusher", + "assembly_depot", + "assembly_furnace", + "assembly_machine", + "assembly_mechanical_fabricator", + "autocannon", + "autocannon_barrel", + "autocannon_drum", + "autoloader", + "battle_cannon_barrel", + "battle_cannon_breech", + "battle_cannon_mantlet", + "bauxite", + "bauxite_digester", + "beryllium_block", + "beryllium_ore", + "black_armor", + "black_armor_optic", + "black_armor_slab", + "black_armor_stairs", + "black_armor_trapdoor", + "blast_funnel", + "blast_furnace", + "blast_furnace_bricks", + "blue_armor", + "blue_armor_optic", + "blue_armor_slab", + "blue_armor_stairs", + "blue_armor_trapdoor", + "brass_block", + "breeder_reactor_core", + "breeder_reactor_interface", + "breeder_reactor_port", + "brown_armor", + "brown_armor_optic", + "brown_armor_slab", + "brown_armor_stairs", + "brown_armor_trapdoor", + "burntgrass", + "charred_block", + "chlorine_gas", + "cluster_of_bombs", + "compressed_air", + "concrete_wall", + "control_rod", + "conveyor", + "conveyor_splitter", + "countermeasure_dispenser", + "covered_flame_thrower_barrel", + "covered_machine_gun_barrel", + "cracked_concrete", + "cracked_concrete_wall", + "crude_oil", + "cyan_armor", + "cyan_armor_optic", + "cyan_armor_slab", + "cyan_armor_stairs", + "cyan_armor_trapdoor", + "damaged_concrete", + "damaged_concrete_wall", + "damagedfueltank", + "deepslate_lead_ore", + "defense_core", + "destroyed_concrete", + "destroyed_concrete_wall", + "diesel", + "drive_shaft", + "electric_firebox", + "empty_fuel_rods", + "empty_missile_hardpoint", + "energy_battery", + "energy_distribution_node", + "energy_node", + "engine_cyllinder", + "era_1", + "era_2", + "era_3", + "era_4", + "explosive_barrel", + "extension_shaft", + "fire_spear_missile_hardpoint", + "firebox", + "fission_bomb", + "flame_thrower", + "flame_thrower_barrel", + "foundry", + "fractured_concrete", + "fractured_concrete_wall", + "fuel_rods_1", + "fuel_rods_2", + "fuel_rods_3", + "fuel_rods_4", + "fuel_tank", + "fuel_tank_input", + "fuel_tank_module", + "fusion_bomb", + "gas_bomb", + "gas_dispenser", + "generator", + "giant_coil", + "glass_trapdoor", + "gray_armor", + "gray_armor_optic", + "gray_armor_slab", + "gray_armor_stairs", + "gray_armor_trapdoor", + "green_armor", + "green_armor_optic", + "green_armor_slab", + "green_armor_stairs", + "green_armor_trapdoor", + "harddirt", + "heavy_machine_gun", + "hydrazine", + "item_incinerator", + "jet_compressor", + "jet_exhaust", + "jet_gearbox", + "jet_turbine", + "kerosene", + "land_mine", + "large_electric_motor", + "large_engine_smokestack", + "large_rocket_pod", + "large_rocket_pod_chamber", + "lead_block", + "lead_ore", + "light_autocannon", + "light_blue_armor", + "light_blue_armor_optic", + "light_blue_armor_slab", + "light_blue_armor_stairs", + "light_blue_armor_trapdoor", + "light_gray_armor", + "light_gray_armor_optic", + "light_gray_armor_slab", + "light_gray_armor_stairs", + "light_gray_armor_trapdoor", + "light_machine_gun", + "light_wood_block", + "light_wood_frame", + "light_wood_side_panel", + "light_wood_slab", + "light_wood_stairs", + "light_wood_trapdoor", + "lime_armor", + "lime_armor_optic", + "lime_armor_slab", + "lime_armor_stairs", + "lime_armor_trapdoor", + "liquid_hydrogen", + "liquid_oxygen", + "lithium_block", + "lithium_ore", + "loot_box", + "machine_gun", + "machine_gun_barrel", + "magenta_armor", + "magenta_armor_optic", + "magenta_armor_slab", + "magenta_armor_stairs", + "magenta_armor_trapdoor", + "manual_aimer", + "manual_crank", + "medium_diesel_engine", + "medium_petrol_engine", + "mineral_grinder", + "mini_gun_barrel", + "minigun", + "mortar", + "nickel_block", + "nickel_ore", + "niobium_block", + "nitrate_block", + "node_trigger", + "node_trigger_on", + "offset_era_1", + "offset_era_2", + "offset_era_3", + "offset_era_4", + "oil", + "oil_firebox", + "open_summonation", + "orange_armor", + "orange_armor_optic", + "orange_armor_slab", + "orange_armor_stairs", + "orange_armor_trapdoor", + "ordinance_cluster_warhead", + "ordinance_controller", + "ordinance_core", + "ordinance_fins", + "ordinance_fission_initiator_head", + "ordinance_heavy_warhead", + "ordinance_incendiary_warhead", + "ordinance_inline_fission_warhead", + "ordinance_inline_fusion_warhead_stage_1", + "ordinance_inline_fusion_warhead_stage_2", + "ordinance_inline_warhead", + "ordinance_ir_seeker_head", + "ordinance_kinetic_head", + "ordinance_relocator", + "ordinance_sarh_seeker", + "ordinance_thruster", + "overgrown_reenforced_concrete", + "passenger_seat", + "petrolium", + "phosphate_block", + "pink_armor", + "pink_armor_optic", + "pink_armor_slab", + "pink_armor_stairs", + "pink_armor_trapdoor", + "plutonium_block", + "polished_bauxite", + "polished_trinitite", + "power_reactor_interface", + "power_reactor_port", + "production_input", + "production_output", + "purple_armor", + "purple_armor_optic", + "purple_armor_slab", + "purple_armor_stairs", + "purple_armor_trapdoor", + "pyrochlore_block", + "pyrochlore_ore", + "rac_barrel", + "radar_spear_missile_hardpoint", + "radioactive_ash", + "radioactive_ash_full_block", + "raw_beryllium_block", + "raw_lead_block", + "raw_lithium_block", + "raw_nickel_block", + "raw_uranium_block", + "raw_zinc_block", + "razor_wire", + "reaction_chamber", + "reactor_casing", + "rebar", + "red_armor", + "red_armor_optic", + "red_armor_slab", + "red_armor_stairs", + "red_armor_trapdoor", + "redirector_shaft", + "redstone_tnt", + "reenforced_concrete", + "refinery", + "refinery_tower", + "reinforced_glass", + "reinforced_glass_stairs", + "reinforced_glass_trapdoor", + "robot_chute", + "rocket_pod", + "rocket_pod_chamber", + "rotary_auto_cannon", + "rusty_block", + "rusty_slab", + "rusty_stairs", + "rusty_trapdoor", + "sand_bags", + "scorch_dirt", + "seeker_spear_missile_hardpoint", + "sheet_metal", + "sheet_metal_pane", + "sheet_metal_slab", + "sheet_metal_stairs", + "siren", + "small_bomb", + "small_diesel_engine", + "small_petrol_engine", + "smoke_bomb", + "smoke_launcher", + "solar_generator", + "steel_block", + "steel_door", + "steel_optic", + "steel_plating", + "steel_plating_slab", + "steel_plating_stairs", + "steel_trapdoor", + "steel_truss", + "strike_spear_missile_hardpoint", + "structural_concrete", + "sulfur_block", + "sulfur_ore", + "sulfuric_acid", + "summonation", + "summonator", + "summonator_active", + "summonator_module", + "tar", + "thermal_furnace", + "thick_battle_cannon_barrel", + "tinted_glass_stairs", + "tinted_glass_trapdoor", + "torpedo_thruster", + "trinitite", + "trinitite_glass", + "trinitite_glass_stairs", + "trinitite_glass_trapdoor", + "type_1_bc_muzzle_brake", + "type_2_bc_muzzle_brake", + "uranium_depleted_block", + "uranium_enriched_block", + "uranium_neutral_block", + "uranium_ore", + "white_armor", + "white_armor_optic", + "white_armor_slab", + "white_armor_stairs", + "white_armor_trapdoor", + "wire_fence", + "yellow_armor", + "yellow_armor_optic", + "yellow_armor_slab", + "yellow_armor_stairs", + "yellow_armor_trapdoor", + "zinc_block", + "zinc_ore" + ], + "counts": { + "blockstates": 448, + "classes": 4703, + "entities": 167, + "item_models": 763, + "loot_tables": 435, + "procedures": 3063, + "recipes": 505, + "standalone_items": 326 + }, + "modid": "crusty_chunks", + "source": "Warium 1.2.7", + "source_sha1": "528d81630a23fb4004e3abdd99b16bd225cd1e92", + "standalone_items_from_item_models": [ + "advanced_alloy_component", + "advanced_alloy_ingot", + "advanced_alloy_mixture", + "advanced_automatic_rifle_receiver", + "advanced_component", + "advanced_pistol_receiver", + "aimer", + "aluminate_dust", + "aluminum_dust", + "aluminum_ingot", + "aluminum_plate", + "aluminum_tiny_dust", + "ap_large_bullet", + "ap_shell", + "apfsds_projectile", + "armor_peeler_animated", + "armor_peeler_rocket", + "armor_peeler_unloaded", + "artillery_shell", + "artillery_solid_shell", + "assassin_spawn_egg", + "auto_pistol", + "automatic_rifle", + "automatic_rifle_receiver", + "basic_receiver", + "battle_rifle", + "bauxite_dust", + "bent_component", + "beryllium_dust", + "beryllium_ingot", + "bird_shot", + "blast_armor_boots", + "blast_armor_chestplate", + "blast_armor_helmet", + "blast_armor_leggings", + "blast_clay", + "blast_furnace_brick", + "body_armor_chestplate", + "bolt_action_receiver", + "bolt_action_rifle_animated", + "bored_component", + "brass_dust", + "brass_fitting", + "brass_ingot", + "brass_plate", + "breacher_spawn_egg", + "break_action_shotgun_animated", + "breech_rifle", + "bullet", + "bullet_resistant_helmet_2_helmet", + "bullet_resistant_helmet_3_helmet", + "bullet_resistant_helmet_4_helmet", + "bullet_resistant_helmet_helmet", + "burst_rifle", + "cable", + "cast_component", + "chaff_charge", + "chisel", + "chlorine_dust", + "chlorine_gas_bucket", + "ciws_spawn_egg", + "combustion_cylinder", + "commander_spawn_egg", + "component_foundry_template", + "compressed_advanced_mixture", + "compressed_air_bucket", + "copper_coil", + "copper_dust", + "copper_plate", + "copper_wire", + "crude_oil_bucket", + "cut_component", + "cutters", + "cylinder_foundry_template", + "decimator_spawn_egg", + "diesel_bucket", + "electric_motor", + "ember_particle", + "energy_meter", + "engine_component", + "enriched_lithium_ingot", + "enriched_lithium_nugget", + "era_tile", + "eradication", + "eradicator_spawn_egg", + "extra_large_bullet", + "extra_large_casing", + "extra_large_projectile", + "extra_large_projectile_template", + "filtered_aluminate_dust", + "filtered_pyrochlore_dust", + "fire_agent", + "fire_artillery_shell", + "fire_spear_rocket", + "firing_mechanism", + "firing_pin", + "fission_core", + "flak_projectile", + "flak_shell", + "flame_thrower_animated", + "flame_thrower_tank_chestplate", + "flamer_spawn_egg", + "flare_charge", + "flare_pistol", + "foundry_template", + "fuel_hose", + "fuel_rod", + "fusion_core", + "gas_artillery_shell", + "gas_canister", + "gas_mask_helmet", + "gas_mask_helmet_helmet", + "geiger_counter", + "gold_dust", + "grenade", + "grenade_launcher", + "grenade_shell", + "hammer", + "hand_drill", + "he_projectile", + "heat_projectile", + "heat_shell", + "hollowed_extra_large_projectile", + "hollowed_huge_projectile", + "hollowed_large_projectile", + "huge_barrel_foundry_template", + "huge_bored_barrel", + "huge_bullet", + "huge_cannon_foundry_template", + "huge_casing", + "huge_he_bullet", + "huge_projectile", + "huge_projectile_foundry_template", + "huge_unbored_barrel", + "huge_unbored_cannon_barrel", + "hunter_spawn_egg", + "hydrazine_bucket", + "impact_fuze", + "implosion_lens", + "implosion_module", + "incendiary_bottle", + "incendiary_grenade", + "invisibleitem", + "ir_component", + "iron_dust", + "irongear", + "kerosene_bucket", + "large_barrel_template", + "large_bored_barrel", + "large_bullet", + "large_cannon_foundry_template", + "large_casing", + "large_foundry_template", + "large_magazine", + "large_magazine_0", + "large_projectile", + "large_projectile_foundry_template", + "large_shell", + "large_unbored_barrel", + "large_unbored_cannon_barrel", + "large_volatile_pile", + "lead_dust", + "lead_ingot", + "lead_nugget", + "lever_rifle", + "liquid_hydrogen_bucket", + "liquid_oxygen_bucket", + "lithium_deuteride", + "lithium_dust", + "lithium_ingot", + "lithium_nugget", + "lmg_animated", + "lmg_magazine", + "lmg_magazine_0", + "machine_carbine", + "machine_gun_box", + "machine_gun_box_0", + "mechanical_bore", + "mechanical_extruder", + "mechanical_press", + "mechanical_shear", + "medium_ap_bullet", + "medium_barrel_template", + "medium_bored_barrel", + "medium_cannon_foundry_template", + "medium_casing", + "medium_magazine", + "medium_magazine_0", + "medium_projectile", + "medium_projectile_foundry_template", + "medium_stealth_bullet", + "medium_unbored_barrel", + "medium_unbored_cannon_barrel", + "mg_receiver", + "mortar_shell", + "mortarer_spawn_egg", + "musket_ball", + "neutron_reflector", + "nickel_dust", + "nickel_ingot", + "niobium_dust", + "niobium_ingot", + "niobium_tiny_dust", + "nitrate", + "nvd_helmet_helmet", + "oil_bucket", + "paint_tool", + "particle", + "particle_2", + "petrolium_bucket", + "phosphorus_dust", + "pistol_receiver", + "plutonium_core", + "plutonium_ingot", + "plutonium_nugget", + "powder_charge", + "power_cell", + "precision_component", + "propellent", + "prototype_eradicator_spawn_egg", + "pump_action_shotgun_animated", + "pyrochlore", + "pyrochlore_dust", + "radar_component", + "radar_spear_missile", + "raidscout_spawn_egg", + "raw_beryllium", + "raw_lead", + "raw_lithium", + "raw_nickel", + "raw_uranium", + "raw_zinc", + "reactioncomponent", + "reaper_spawn_egg", + "revolver_animated", + "revolver_receiver", + "rifle_stock", + "rifler_spawn_egg", + "scoped_bolt_action_rifle_animated", + "scoped_breech_rifle", + "scout_spawn_egg", + "seeker_spear_rocket", + "semi_automatic_pistol_animated", + "semi_automatic_rifle_animated", + "shale_oil", + "shaped_charge_fuze", + "shielding_component", + "shotgun_casing", + "shotgun_shell", + "single_shot_rifle", + "slug_shell", + "small_ap_shell", + "small_barrel_template", + "small_bored_barrel", + "small_cannon_foundry_template", + "small_casing", + "small_engine", + "small_flak_projectile", + "small_flak_shell", + "small_he_projectile", + "small_hollow_point_bullet", + "small_projectile", + "small_projectile_foundry_template", + "small_shell", + "small_stealth_bullet", + "small_unbored_barrel", + "small_unbored_cannon_barrel", + "smallbullet", + "smallmagazine", + "smallmagazine_0", + "smg_animated", + "smg_magazine", + "smg_magazine_0", + "smg_receiver", + "smoke_agent", + "smoke_grenade", + "smoke_grenade_shell", + "smoke_mortar_shell", + "smoke_projectile", + "smoke_shell", + "solid_rocket_fuel_pack", + "solid_shell", + "stealth_large_bullet", + "stealth_pistol", + "steel_component", + "steel_crushing_wheel", + "steel_cylinder", + "steel_gear", + "steel_ingot", + "steel_spring", + "steel_tube", + "steel_wire", + "steelplate", + "strike_spear_missile", + "striker_spawn_egg", + "sulfur", + "sulfuric_acid_bucket", + "tech_component", + "thermal_shell", + "thermo_nuclear_fuel", + "thermometer", + "timed_fuze", + "tiny_lithium_deuteride", + "tinyprojectile_item", + "toxic_agent", + "transparent_item", + "trinitite_shard", + "turbine_rotor", + "unfabricated_tech_component", + "uranium_depleted_dust", + "uranium_depleted_ingot", + "uranium_depleted_tiny_dust", + "uranium_enriched_dust", + "uranium_enriched_ingot", + "uranium_enriched_tiny_dust", + "uranium_neural_ingot", + "uranium_neutral_dust", + "uranium_neutraltiny_dust", + "volatile_dust", + "weapon_bolt", + "weapon_supressor", + "welder", + "wood_component", + "worker_spawn_egg", + "zinc_dust", + "zinc_ingot" + ] +} diff --git a/docs/release-checklist.md b/docs/release-checklist.md new file mode 100644 index 0000000..74b276a --- /dev/null +++ b/docs/release-checklist.md @@ -0,0 +1,39 @@ +# Release Checklist + +## Version + +- [ ] Private/internal release scope confirmed. +- [ ] Warium rights clarified before any public release. +- [ ] Version number updated. +- [ ] Changelog updated. +- [ ] README updated. + +## Quality + +- [ ] `python tools/generate_port_sources.py` passes. +- [ ] `python tools/registry_parity.py` passes. +- [ ] `./gradlew --no-daemon build` passes. +- [ ] `./gradlew --no-daemon runData` passes. +- [ ] Dedicated server smoke test passes in Gitea Actions. + +## Security + +- [ ] Security review is current. +- [ ] No secrets are committed. +- [ ] Original Warium jar is not committed. +- [ ] Decompiled source dumps are not committed. +- [ ] Generated extracted assets are not committed. +- [ ] Private dependency jars are not committed. + +## Artifacts + +- [ ] `build/libs/warium-neoforge-1.21.1-1.2.7+neo.21.1.225.jar` exists. +- [ ] Gitea Actions artifact is uploaded. +- [ ] Private generic package is uploaded when `REGISTRY_TOKEN` is configured. +- [ ] Private latest package URL works. + +## Release + +- [ ] Release notes written. +- [ ] Public tag skipped unless explicitly requested after rights clearance. +- [ ] Public release skipped unless explicitly requested after rights clearance. diff --git a/docs/release-notes.md b/docs/release-notes.md new file mode 100644 index 0000000..75c33fe --- /dev/null +++ b/docs/release-notes.md @@ -0,0 +1,33 @@ +# Warium NeoForge 1.21.1 1.2.7+neo.21.1.225 + +## Downloads + +| Variant | Download | +| --- | --- | +| Latest private artifact | `https://git.wilkensxl.de/api/packages/MrSphay/generic/warium-neoforge-1.21.1/latest/warium-neoforge-1.21.1-latest.jar` | + +## Highlights + +- Private NeoForge `21.1.225` scaffold for Minecraft `1.21.1`. +- Reproducible Warium `1.2.7` source artifact verification against SHA1 `528d81630a23fb4004e3abdd99b16bd225cd1e92`. +- Generated registry stubs for original blockstates and standalone item models. +- CI decompile artifact for behavior-port follow-up work. + +## Security + +- Dependency audit: handled by Gitea dependency-check workflow. +- Secret handling: package publishing uses `REGISTRY_TOKEN`; no token is tracked. +- External network calls: Modrinth, NeoForge Maven, Maven Central. + +## Verification + +| Check | Result | +| --- | --- | +| `python tools/generate_port_sources.py` | Passed locally | +| `python tools/registry_parity.py` | Passed locally | +| `./gradlew --no-daemon build` | Pending Gitea runner | +| Artifact download | Pending Gitea runner and `REGISTRY_TOKEN` secret | + +## Notes + +This is not a public release. Keep the repository and packages private until rights are clarified. diff --git a/docs/security-review.md b/docs/security-review.md new file mode 100644 index 0000000..15cd997 --- /dev/null +++ b/docs/security-review.md @@ -0,0 +1,53 @@ +# Security Review + +## Scope + +Project: + +```text +Warium NeoForge 1.21.1 Port +``` + +Reviewed version or commit: + +```text +Unreleased scaffold +``` + +## Code Patterns Checked + +- [x] No secrets committed. +- [x] Generated original assets are ignored. +- [x] Decompiled source output is ignored. +- [x] Original jar artifacts are ignored. +- [x] Private integration jars are ignored. +- [x] External network calls are documented. + +## Dependency Review + +Command: + +```bash +./gradlew --no-daemon build +``` + +Result: + +```text +Pending runner execution. +``` + +## Runtime Review + +- [x] Gitea publishing uses `REGISTRY_TOKEN` secret only. +- [x] Package download is private/internal pending rights clearance. +- [x] Source Warium jar is downloaded from Modrinth and verified by SHA1. +- [x] Required private integrations are shimmed until real NeoForge 1.21.1 jars exist. + +## Release Notes + +Known residual risks: + +```text +The current scaffold preserves registry IDs and resources but does not yet fully port the original MCreator behavior procedures, block entities, GUI logic, entities, AI, weapons, ordnance, nuclear effects, or external integration APIs. +``` diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..5c790ac --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false +org.gradle.parallel=true +org.gradle.caching=true + +neogradle.subsystems.parchment.minecraftVersion=1.21.1 +neogradle.subsystems.parchment.mappingsVersion=2024.11.17 + +minecraft_version=1.21.1 +minecraft_version_range=[1.21.1] +neo_version=21.1.225 +neo_version_range=[21.1.225,) +loader_version_range=[1,) + +mod_id=crusty_chunks +mod_name=Warium NeoForge Port +mod_license=PRIVATE-INTERNAL-PENDING-RIGHTS-CLEARANCE +mod_version=1.2.7+neo.21.1.225 +mod_group_id=net.mcreator.crustychunks +mod_authors=Novum; port scaffold by Codex +mod_description=A private internal NeoForge 1.21.1 port of Warium 1.2.7 pending rights clearance. diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..1b33c55baabb587c669f562ae36f953de2481846 GIT binary patch literal 43764 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..1e3068d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,14 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + url = 'https://maven.neoforged.net/releases' + } + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' +} + +rootProject.name = 'Warium-NeoForge-1.21.1' diff --git a/src/main/java/net/mcreator/crustychunks/CrustyChunksMod.java b/src/main/java/net/mcreator/crustychunks/CrustyChunksMod.java new file mode 100644 index 0000000..918adf6 --- /dev/null +++ b/src/main/java/net/mcreator/crustychunks/CrustyChunksMod.java @@ -0,0 +1,14 @@ +package net.mcreator.crustychunks; + +import net.mcreator.crustychunks.init.GeneratedRegistries; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.common.Mod; + +@Mod(CrustyChunksMod.MODID) +public final class CrustyChunksMod { + public static final String MODID = "crusty_chunks"; + + public CrustyChunksMod(IEventBus modBus) { + GeneratedRegistries.register(modBus); + } +} diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..b3fa5d7 --- /dev/null +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,67 @@ +modLoader="javafml" +loaderVersion="${loader_version_range}" +license="${mod_license}" +issueTrackerURL="https://git.wilkensxl.de/MrSphay/Warium-NeoForge-1.21.1/issues" + +[[mods]] +modId="${mod_id}" +version="${mod_version}" +displayName="${mod_name}" +displayURL="https://git.wilkensxl.de/MrSphay/Warium-NeoForge-1.21.1" +credits="Original Warium by Novum. Private internal NeoForge port pending rights clearance." +authors="${mod_authors}" +description='''${mod_description}''' + +[[mods]] +modId="wariumapi" +version="${mod_version}" +displayName="WariumAPI Compatibility Shim" +description='''Private compatibility shim declared because WariumAPI has no confirmed NeoForge 1.21.1 artifact in the inspected sources. Replace with the real dependency when available.''' + +[[mods]] +modId="wariumvs" +version="${mod_version}" +displayName="WariumVS Compatibility Shim" +description='''Private compatibility shim declared because WariumVS has no confirmed NeoForge 1.21.1 artifact in the inspected sources. Replace with the real dependency when available.''' + +[[dependencies.${mod_id}]] +modId="minecraft" +type="required" +versionRange="${minecraft_version_range}" +ordering="AFTER" +side="BOTH" + +[[dependencies.${mod_id}]] +modId="neoforge" +type="required" +versionRange="${neo_version_range}" +ordering="AFTER" +side="BOTH" + +[[dependencies.${mod_id}]] +modId="geckolib" +type="required" +versionRange="[4.7.5.1,)" +ordering="AFTER" +side="BOTH" + +[[dependencies.${mod_id}]] +modId="ritchiesprojectilelib" +type="required" +versionRange="[2.1.2,)" +ordering="AFTER" +side="BOTH" + +[[dependencies.${mod_id}]] +modId="wariumapi" +type="required" +versionRange="[0,)" +ordering="AFTER" +side="BOTH" + +[[dependencies.${mod_id}]] +modId="wariumvs" +type="required" +versionRange="[0,)" +ordering="AFTER" +side="BOTH" diff --git a/src/main/resources/assets/crusty_chunks/lang/en_us.json b/src/main/resources/assets/crusty_chunks/lang/en_us.json new file mode 100644 index 0000000..3dccf3b --- /dev/null +++ b/src/main/resources/assets/crusty_chunks/lang/en_us.json @@ -0,0 +1,3 @@ +{ + "itemGroup.crusty_chunks.warium": "Warium" +} diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..274d3e3 --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "pack_format": 34, + "description": "Private internal Warium NeoForge 1.21.1 port pending rights clearance" + } +} diff --git a/tools/check_required_integrations.py b/tools/check_required_integrations.py new file mode 100644 index 0000000..404ea9a --- /dev/null +++ b/tools/check_required_integrations.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from pathlib import Path + +from warium_source import ROOT + + +def main() -> None: + required_private = [ + ROOT / "ci" / "required-mods" / "wariumapi-neoforge-1.21.1.jar", + ROOT / "ci" / "required-mods" / "wariumvs-neoforge-1.21.1.jar", + ] + missing = [path for path in required_private if not path.exists()] + if missing: + print("Required private integration jars are not present yet:") + for path in missing: + print(f"- {path}") + print("The built jar includes private compatibility shim mod metadata for wariumapi and wariumvs.") + print("Replace the shims with real jars when NeoForge 1.21.1 artifacts are available.") + else: + print("Required private integration jars are present.") + + +if __name__ == "__main__": + main() diff --git a/tools/decompile_original.py b/tools/decompile_original.py new file mode 100644 index 0000000..577798c --- /dev/null +++ b/tools/decompile_original.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +import shutil +import subprocess +import urllib.request +from pathlib import Path + +from warium_source import BUILD_DIR, ROOT, clean_dir, download_original + +VINEFLOWER_URL = "https://repo1.maven.org/maven2/org/vineflower/vineflower/1.11.1/vineflower-1.11.1.jar" +VINEFLOWER_JAR = BUILD_DIR / "vineflower-1.11.1.jar" +DECOMPILED_DIR = ROOT / "build" / "decompiled" / "warium-1.2.7" + + +def main() -> None: + original = download_original() + BUILD_DIR.mkdir(parents=True, exist_ok=True) + if not VINEFLOWER_JAR.exists(): + request = urllib.request.Request(VINEFLOWER_URL, headers={"User-Agent": "MrSphay/Warium-NeoForge-Port/1.0"}) + with urllib.request.urlopen(request) as response, VINEFLOWER_JAR.open("wb") as out: + shutil.copyfileobj(response, out) + clean_dir(DECOMPILED_DIR) + subprocess.run( + ["java", "-jar", str(VINEFLOWER_JAR), str(original), str(DECOMPILED_DIR)], + cwd=ROOT, + check=True, + ) + report = ROOT / "docs" / "inventory" / "decompile-report.md" + report.parent.mkdir(parents=True, exist_ok=True) + report.write_text( + "# Decompile Report\n\n" + f"- Source: `{original}`\n" + f"- Output: `{DECOMPILED_DIR}`\n" + "- Decompiler: Vineflower 1.11.1\n", + encoding="utf-8", + ) + + +if __name__ == "__main__": + main() diff --git a/tools/generate_port_sources.py b/tools/generate_port_sources.py new file mode 100644 index 0000000..b6f7660 --- /dev/null +++ b/tools/generate_port_sources.py @@ -0,0 +1,141 @@ +from __future__ import annotations + +import json +import shutil +import zipfile +from pathlib import Path + +from warium_source import MODID, ROOT, clean_dir, download_original, safe_java_identifier, write_json + +GENERATED_JAVA = ROOT / "src" / "generated" / "java" +GENERATED_RESOURCES = ROOT / "src" / "generated" / "resources" +INVENTORY_DIR = ROOT / "docs" / "inventory" + + +def main() -> None: + jar = download_original() + clean_dir(GENERATED_JAVA) + clean_dir(GENERATED_RESOURCES) + INVENTORY_DIR.mkdir(parents=True, exist_ok=True) + + with zipfile.ZipFile(jar) as archive: + names = archive.namelist() + block_names = sorted(stem(n) for n in names if n.startswith(f"assets/{MODID}/blockstates/") and n.endswith(".json")) + item_model_names = sorted(stem(n) for n in names if n.startswith(f"assets/{MODID}/models/item/") and n.endswith(".json")) + standalone_items = sorted(name for name in item_model_names if name not in set(block_names)) + + extract_resources(archive) + + inventory = { + "source": "Warium 1.2.7", + "source_sha1": "528d81630a23fb4004e3abdd99b16bd225cd1e92", + "modid": MODID, + "blocks_from_blockstates": block_names, + "standalone_items_from_item_models": standalone_items, + "counts": { + "blockstates": len(block_names), + "item_models": len(item_model_names), + "standalone_items": len(standalone_items), + "classes": len([n for n in names if n.endswith(".class")]), + "procedures": len([n for n in names if n.startswith("net/mcreator/crustychunks/procedures/") and n.endswith(".class")]), + "entities": len([n for n in names if n.startswith("net/mcreator/crustychunks/entity/") and n.endswith(".class")]), + "recipes": len([n for n in names if n.startswith(f"data/{MODID}/recipes/") and n.endswith(".json")]), + "loot_tables": len([n for n in names if n.startswith(f"data/{MODID}/loot_tables/") and n.endswith(".json")]), + }, + } + write_json(INVENTORY_DIR / "original-inventory.json", inventory) + write_generated_registries(block_names, standalone_items) + + +def stem(path: str) -> str: + return Path(path).name.removesuffix(".json") + + +def extract_resources(archive: zipfile.ZipFile) -> None: + for info in archive.infolist(): + if info.is_dir(): + continue + name = info.filename + if name.startswith(f"assets/{MODID}/"): + target = GENERATED_RESOURCES / name + elif name.startswith(f"data/{MODID}/"): + target = GENERATED_RESOURCES / migrate_data_path(name) + elif name == "pack.mcmeta": + continue + else: + continue + target.parent.mkdir(parents=True, exist_ok=True) + with archive.open(info) as src, target.open("wb") as dst: + shutil.copyfileobj(src, dst) + + +def migrate_data_path(path: str) -> str: + path = path.replace(f"data/{MODID}/loot_tables/", f"data/{MODID}/loot_table/") + path = path.replace(f"data/{MODID}/tags/items/", f"data/{MODID}/tags/item/") + path = path.replace(f"data/{MODID}/tags/blocks/", f"data/{MODID}/tags/block/") + path = path.replace(f"data/{MODID}/tags/entity_types/", f"data/{MODID}/tags/entity_type/") + path = path.replace(f"data/{MODID}/tags/fluids/", f"data/{MODID}/tags/fluid/") + return path + + +def write_generated_registries(blocks: list[str], items: list[str]) -> None: + package_dir = GENERATED_JAVA / "net" / "mcreator" / "crustychunks" / "init" + package_dir.mkdir(parents=True, exist_ok=True) + used: set[str] = set() + lines: list[str] = [ + "package net.mcreator.crustychunks.init;", + "", + "import net.mcreator.crustychunks.CrustyChunksMod;", + "import net.minecraft.core.registries.Registries;", + "import net.minecraft.network.chat.Component;", + "import net.minecraft.world.item.BlockItem;", + "import net.minecraft.world.item.CreativeModeTab;", + "import net.minecraft.world.item.Item;", + "import net.minecraft.world.item.ItemStack;", + "import net.minecraft.world.item.Items;", + "import net.minecraft.world.level.block.Block;", + "import net.minecraft.world.level.block.state.BlockBehaviour;", + "import net.neoforged.bus.api.IEventBus;", + "import net.neoforged.neoforge.registries.DeferredHolder;", + "import net.neoforged.neoforge.registries.DeferredRegister;", + "", + "public final class GeneratedRegistries {", + " public static final DeferredRegister BLOCKS = DeferredRegister.create(Registries.BLOCK, CrustyChunksMod.MODID);", + " public static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, CrustyChunksMod.MODID);", + " public static final DeferredRegister CREATIVE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, CrustyChunksMod.MODID);", + "", + ] + + for name in blocks: + ident = safe_java_identifier(name, used) + lines.append(f' public static final DeferredHolder {ident} = BLOCKS.register("{name}", () -> new Block(BlockBehaviour.Properties.of().strength(2.0F, 6.0F)));') + lines.append(f' public static final DeferredHolder {ident}_ITEM = ITEMS.register("{name}", () -> new BlockItem({ident}.get(), new Item.Properties()));') + + for name in items: + ident = safe_java_identifier(name, used) + lines.append(f' public static final DeferredHolder {ident} = ITEMS.register("{name}", () -> new Item(new Item.Properties()));') + + lines.extend([ + "", + ' public static final DeferredHolder WARIUM_TAB = CREATIVE_TABS.register("warium", () -> CreativeModeTab.builder()', + ' .title(Component.translatable("itemGroup.crusty_chunks.warium"))', + " .icon(() -> new ItemStack(Items.IRON_INGOT))", + " .displayItems((parameters, output) -> ITEMS.getEntries().forEach(entry -> output.accept(entry.get())))", + " .build());", + "", + " private GeneratedRegistries() {", + " }", + "", + " public static void register(IEventBus modBus) {", + " BLOCKS.register(modBus);", + " ITEMS.register(modBus);", + " CREATIVE_TABS.register(modBus);", + " }", + "}", + "", + ]) + (package_dir / "GeneratedRegistries.java").write_text("\n".join(lines), encoding="utf-8") + + +if __name__ == "__main__": + main() diff --git a/tools/prepare_runtime_mods.py b/tools/prepare_runtime_mods.py new file mode 100644 index 0000000..89fb6d3 --- /dev/null +++ b/tools/prepare_runtime_mods.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import json +import shutil +import urllib.parse +import urllib.request +from pathlib import Path + +from warium_source import ROOT + +MODS_DIR = ROOT / "run" / "server" / "mods" + +REQUIRED_MODRINTH = [ + ("geckolib", "4.7.5.1"), + ("rpl", "2.1.2"), +] + + +def main() -> None: + MODS_DIR.mkdir(parents=True, exist_ok=True) + for project, version in REQUIRED_MODRINTH: + download_modrinth_version(project, version) + private_dir = ROOT / "ci" / "required-mods" + if private_dir.exists(): + for jar in private_dir.glob("*.jar"): + shutil.copy2(jar, MODS_DIR / jar.name) + print(f"Runtime mods prepared in {MODS_DIR}") + + +def download_modrinth_version(project: str, version_number: str) -> None: + encoded_project = urllib.parse.quote(project) + loaders = urllib.parse.quote(json.dumps(["neoforge"])) + game_versions = urllib.parse.quote(json.dumps(["1.21.1"])) + url = f"https://api.modrinth.com/v2/project/{encoded_project}/version?loaders={loaders}&game_versions={game_versions}" + request = urllib.request.Request(url, headers={"User-Agent": "MrSphay/Warium-NeoForge-Port/1.0"}) + with urllib.request.urlopen(request) as response: + data = json.loads(response.read().decode("utf-8")) + selected = next((entry for entry in data if entry.get("version_number") == version_number), None) + if selected is None: + available = ", ".join(entry.get("version_number", "?") for entry in data[:10]) + raise SystemExit(f"No NeoForge 1.21.1 build found for {project} {version_number}. Available: {available}") + primary = next((file for file in selected["files"] if file.get("primary")), selected["files"][0]) + target = MODS_DIR / primary["filename"].replace(" ", "-") + request = urllib.request.Request(primary["url"], headers={"User-Agent": "MrSphay/Warium-NeoForge-Port/1.0"}) + with urllib.request.urlopen(request) as response, target.open("wb") as out: + shutil.copyfileobj(response, out) + + +if __name__ == "__main__": + main() diff --git a/tools/registry_parity.py b/tools/registry_parity.py new file mode 100644 index 0000000..6d0a835 --- /dev/null +++ b/tools/registry_parity.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from warium_source import ROOT + + +def main() -> None: + inventory_path = ROOT / "docs" / "inventory" / "original-inventory.json" + generated_source = ROOT / "src" / "generated" / "java" / "net" / "mcreator" / "crustychunks" / "init" / "GeneratedRegistries.java" + if not inventory_path.exists() or not generated_source.exists(): + raise SystemExit("Generated inventory is missing; run generatePortSources first.") + inventory = json.loads(inventory_path.read_text(encoding="utf-8")) + source = generated_source.read_text(encoding="utf-8") + missing_blocks = [name for name in inventory["blocks_from_blockstates"] if f'"{name}"' not in source] + missing_items = [name for name in inventory["standalone_items_from_item_models"] if f'"{name}"' not in source] + if missing_blocks or missing_items: + raise SystemExit( + "Registry parity failed: " + f"{len(missing_blocks)} blocks missing, {len(missing_items)} standalone items missing" + ) + print( + "Registry parity OK: " + f"{len(inventory['blocks_from_blockstates'])} blocks and " + f"{len(inventory['standalone_items_from_item_models'])} standalone items generated." + ) + + +if __name__ == "__main__": + main() diff --git a/tools/warium_source.py b/tools/warium_source.py new file mode 100644 index 0000000..87e4a40 --- /dev/null +++ b/tools/warium_source.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +import hashlib +import json +import re +import shutil +import urllib.request +import zipfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +BUILD_DIR = ROOT / "build" / "warium-port" +ORIGINAL_JAR = BUILD_DIR / "Warium-1.2.7.jar" +ORIGINAL_URL = "https://cdn.modrinth.com/data/xgjvEen1/versions/4oIhAhRz/Warium%201.2.7.jar" +ORIGINAL_SHA1 = "528d81630a23fb4004e3abdd99b16bd225cd1e92" +MODID = "crusty_chunks" + + +def download_original() -> Path: + BUILD_DIR.mkdir(parents=True, exist_ok=True) + if not ORIGINAL_JAR.exists() or sha1(ORIGINAL_JAR) != ORIGINAL_SHA1: + request = urllib.request.Request( + ORIGINAL_URL, + headers={"User-Agent": "MrSphay/Warium-NeoForge-Port/1.0"}, + ) + with urllib.request.urlopen(request) as response, ORIGINAL_JAR.open("wb") as out: + shutil.copyfileobj(response, out) + actual = sha1(ORIGINAL_JAR) + if actual != ORIGINAL_SHA1: + raise SystemExit(f"Original jar SHA1 mismatch: expected {ORIGINAL_SHA1}, got {actual}") + return ORIGINAL_JAR + + +def sha1(path: Path) -> str: + digest = hashlib.sha1() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def clean_dir(path: Path) -> None: + if path.exists(): + shutil.rmtree(path) + path.mkdir(parents=True, exist_ok=True) + + +def safe_java_identifier(name: str, used: set[str]) -> str: + ident = re.sub(r"[^a-zA-Z0-9_]", "_", name).upper() + if not ident or ident[0].isdigit(): + ident = "_" + ident + if ident in JAVA_KEYWORDS: + ident = ident + "_ENTRY" + base = ident + suffix = 2 + while ident in used: + ident = f"{base}_{suffix}" + suffix += 1 + used.add(ident) + return ident + + +def write_json(path: Path, data: object) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +JAVA_KEYWORDS = { + "ABSTRACT", "ASSERT", "BOOLEAN", "BREAK", "BYTE", "CASE", "CATCH", "CHAR", + "CLASS", "CONST", "CONTINUE", "DEFAULT", "DO", "DOUBLE", "ELSE", "ENUM", + "EXTENDS", "FINAL", "FINALLY", "FLOAT", "FOR", "GOTO", "IF", "IMPLEMENTS", + "IMPORT", "INSTANCEOF", "INT", "INTERFACE", "LONG", "NATIVE", "NEW", + "PACKAGE", "PRIVATE", "PROTECTED", "PUBLIC", "RETURN", "SHORT", "STATIC", + "STRICTFP", "SUPER", "SWITCH", "SYNCHRONIZED", "THIS", "THROW", "THROWS", + "TRANSIENT", "TRY", "VOID", "VOLATILE", "WHILE", +}