mirror of
https://github.com/xfl03/MCCustomSkinLoader.git
synced 2026-11-14 01:24:02 +00:00
Minecraft 26.2 (#380)
This commit is contained in:
1 parent
055cd711b3
commit
fff855f1b0
28 files changed
+382
-278
No files matched your search
@@ -3,14 +3,30 @@ name: Build
|
||||
description: Build project with Gradle and upload artifact
|
||||
|
||||
inputs:
|
||||
is-snapshot:
|
||||
description: Whether this is a snapshot build
|
||||
required: false
|
||||
default: 'true'
|
||||
artifact-name:
|
||||
description: Artifact name for upload
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
mod_name:
|
||||
description: Display name of the mod
|
||||
value: ${{ steps.info.outputs.mod_name }}
|
||||
edition:
|
||||
description: Target mod loader edition
|
||||
value: ${{ steps.info.outputs.edition }}
|
||||
short_version:
|
||||
description: Shortened version identifier
|
||||
value: ${{ steps.info.outputs.short_version }}
|
||||
loaders:
|
||||
description: List of supported mod loaders
|
||||
value: ${{ steps.info.outputs.loaders }}
|
||||
game_versions:
|
||||
description: List of compatible Minecraft game versions
|
||||
value: ${{ steps.info.outputs.game_versions }}
|
||||
java:
|
||||
description: List of compatible Java versions
|
||||
value: ${{ steps.info.outputs.java }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
@@ -22,9 +38,13 @@ runs:
|
||||
|
||||
- name: Build with Gradle
|
||||
shell: pwsh
|
||||
env:
|
||||
IS_SNAPSHOT: ${{ inputs.is-snapshot }}
|
||||
run: ./gradlew clean build --info --stacktrace
|
||||
run: ./gradlew clean build --info --stacktrace -Pbuild_type=${{ github.event_name }}
|
||||
|
||||
- name: Generate metadata JSONs
|
||||
if: github.event_name != 'pull_request'
|
||||
shell: pwsh
|
||||
id: info
|
||||
run: ./.github/scripts/Get-PublishInfo.ps1 -ArtifactDir 'Bootstrap/build/libs' -Type '${{ github.event_name }}'
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Publish to Object Storage
|
||||
description: >-
|
||||
Upload build artifacts to an S3-compatible object store (AWS S3, Cloudflare
|
||||
R2, MinIO, etc.). Requires the AWS CLI to be pre-installed on the runner.
|
||||
|
||||
inputs:
|
||||
artifact-path:
|
||||
description: Path to the directory containing build artifacts.
|
||||
required: true
|
||||
endpoint-url:
|
||||
description: S3-compatible endpoint URL.
|
||||
required: true
|
||||
bucket:
|
||||
description: S3 bucket name.
|
||||
required: true
|
||||
access-key-id:
|
||||
description: S3-compatible access key ID.
|
||||
required: true
|
||||
secret-access-key:
|
||||
description: S3-compatible secret access key.
|
||||
required: true
|
||||
region:
|
||||
description: AWS region (use 'auto' for Cloudflare R2).
|
||||
required: false
|
||||
default: 'auto'
|
||||
target-path:
|
||||
description: Key prefix in the bucket (e.g., 'mods').
|
||||
required: false
|
||||
default: ''
|
||||
files:
|
||||
description: Glob pattern for files to upload.
|
||||
required: false
|
||||
default: '*'
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Validate artifact path
|
||||
shell: pwsh
|
||||
run: |
|
||||
$path = "${{ inputs.artifact-path }}"
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
throw "Artifact path not found: $path"
|
||||
}
|
||||
|
||||
- name: Upload to object storage
|
||||
shell: pwsh
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ inputs.access-key-id }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ inputs.secret-access-key }}
|
||||
AWS_DEFAULT_REGION: ${{ inputs.region }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
Get-ChildItem -Path "${{ inputs.artifact-path }}" -Filter "${{ inputs.files }}" | ForEach-Object {
|
||||
aws s3 cp "$_.FullName" "s3://${{ inputs.bucket }}/${{ inputs.target-path }}$($_.Name)" --endpoint-url "${{ inputs.endpoint-url }}"
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Publish to GitHub Release
|
||||
description: Upload build artifacts to a GitHub Release. Requires contents:write permission at the workflow level.
|
||||
|
||||
inputs:
|
||||
artifact-path:
|
||||
description: Path to the directory containing build artifacts.
|
||||
required: true
|
||||
tag-name:
|
||||
description: Git tag for the release.
|
||||
required: true
|
||||
token:
|
||||
description: GitHub token with contents:write permission.
|
||||
required: true
|
||||
files:
|
||||
description: Glob pattern(s) for files to upload (passed to gh release create).
|
||||
required: false
|
||||
default: '*'
|
||||
notes-file:
|
||||
description: Path to file containing release body text.
|
||||
required: false
|
||||
prerelease:
|
||||
description: Mark as a pre-release (true/false).
|
||||
required: false
|
||||
default: 'false'
|
||||
generate-notes:
|
||||
description: Auto-generate release notes from merged commits (true/false).
|
||||
required: false
|
||||
default: 'false'
|
||||
replace:
|
||||
description: Delete existing release with the same tag before creating (true/false).
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Validate artifact path
|
||||
shell: pwsh
|
||||
run: |
|
||||
$path = "${{ inputs.artifact-path }}"
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
throw "Artifact path not found: $path"
|
||||
}
|
||||
|
||||
- name: Create GitHub Release
|
||||
shell: pwsh
|
||||
env:
|
||||
GH_TOKEN: ${{ inputs.token }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$tag = '${{ inputs.tag-name }}'
|
||||
if('${{ inputs.replace }}' -eq 'true') { gh release delete "$tag" --yes --cleanup-tag }
|
||||
|
||||
gh release create "$tag" '${{ inputs.artifact-path }}/${{ inputs.files }}' --title "CustomSkinLoader $tag" `
|
||||
$(if('${{ inputs.prerelease }}' -eq 'true') { '--prerelease' }) `
|
||||
$(if('${{ inputs.notes-file }}') { '--notes-file'; '${{ inputs.notes-file }}' } elseif ('${{ inputs.generate-notes }}' -eq 'true') { '--generate-notes' })
|
||||
@@ -1,30 +0,0 @@
|
||||
# Composite action: upload jars to R2/COS, generate and upload JSON metadata
|
||||
name: Publish
|
||||
description: Upload built artifacts to object storage and publish JSON metadata
|
||||
|
||||
inputs:
|
||||
artifact-dir:
|
||||
description: Directory containing built artifacts (relative to workspace)
|
||||
required: true
|
||||
type:
|
||||
description: Release type - 'beta' or 'release'
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
# Generate JSON metadata, upload to R2/COS root, and output CDN URLs
|
||||
- name: Generate and upload JSON metadata
|
||||
id: publish-files
|
||||
shell: pwsh
|
||||
run: ./.github/scripts/Publish-Files.ps1 -ArtifactDir '${{ inputs.artifact-dir }}' -Type '${{ inputs.type }}'
|
||||
|
||||
# Refresh Tencent Cloud CDN cache for the JSON files
|
||||
- name: Refresh CDN
|
||||
uses: linrongda/tencent-cdn-action@v2
|
||||
with:
|
||||
secret-id: ${{ env.COS_SECRET_ID }}
|
||||
secret-key: ${{ env.COS_SECRET_KEY }}
|
||||
action: purgeUrls
|
||||
paths: ${{ steps.publish-files.outputs.cdn-urls }}
|
||||
continue-on-error: true
|
||||
@@ -1,29 +1,68 @@
|
||||
param()
|
||||
param(
|
||||
[string]$ArtifactDir = '',
|
||||
[string]$Type = ''
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$props = Get-Content "gradle.properties"
|
||||
# ---- Output build.info.json to GITHUB_OUTPUT ----
|
||||
$info = Get-Content "build.info.json" -Raw | ConvertFrom-Json
|
||||
|
||||
function Get-Prop($key) {
|
||||
($props | Select-String "^$key=") -replace "^.*=", "" | ForEach-Object { $_.Trim() }
|
||||
$info.PSObject.Properties | ForEach-Object {
|
||||
$name = $_.Name
|
||||
$value = $_.Value
|
||||
if ($value -is [array]) {
|
||||
"$name<<EOF" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
$value | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
"EOF" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
} else {
|
||||
"$name=$value" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
}
|
||||
}
|
||||
|
||||
function Set-MultilineOutput($name, $value) {
|
||||
"$name<<EOF" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
$value | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
"EOF" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
# ---- Generate metadata JSONs ----
|
||||
if (-not $ArtifactDir) { return }
|
||||
if (-not (Test-Path -LiteralPath $ArtifactDir)) { throw "ArtifactDir not found: $ArtifactDir" }
|
||||
|
||||
$jar = Get-ChildItem -Path $ArtifactDir -Filter "*.jar" | Where-Object { $_.Name -notlike "*-sources.jar" } | Select-Object -First 1
|
||||
if (-not $jar) { throw "No jar found in $ArtifactDir" }
|
||||
$JarFilename = $jar.Name
|
||||
|
||||
$version = $info.mod_version
|
||||
$isSnapshot = ($Type -ne "release")
|
||||
$shortVersion = if ($isSnapshot) { "$version-s$env:GITHUB_RUN_NUMBER" } else { $version }
|
||||
|
||||
"short_version=$shortVersion" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
|
||||
$mcVersionList = $info.game_versions | ForEach-Object {
|
||||
$parts = $_ -split '\.'
|
||||
"$($parts[0]).$($parts[1])"
|
||||
} | Sort-Object -Unique | Sort-Object {
|
||||
$v = $_ -split '\.'
|
||||
[int]$v[0] * 100000 + [int]$v[1]
|
||||
}
|
||||
|
||||
$loaders = Get-Prop "loaders"
|
||||
$gameVersions = Get-Prop "game_versions"
|
||||
$java = Get-Prop "java"
|
||||
$version = Get-Prop "mod_version"
|
||||
$suffix = if ($isSnapshot) { "-beta" } else { "" }
|
||||
$jarKey = "mods/$JarFilename"
|
||||
$baseUrl = "https://csl.3-3.dev"
|
||||
|
||||
$loadersList = $loaders -split "," | ForEach-Object { $_.Trim() }
|
||||
$gameVersionsList = $gameVersions -split "," | ForEach-Object { $_.Trim() }
|
||||
$javaList = $java -split "," | ForEach-Object { $_.Trim() }
|
||||
@{
|
||||
version = $shortVersion
|
||||
downloads = @{ Universal = "$baseUrl/$jarKey" }
|
||||
launchermeta = @{}
|
||||
} | ConvertTo-Json -Depth 10 | Set-Content -Path "$ArtifactDir/latest$suffix.json" -NoNewline
|
||||
|
||||
Set-MultilineOutput "loaders" $loadersList
|
||||
Set-MultilineOutput "game-versions" $gameVersionsList
|
||||
Set-MultilineOutput "java" $javaList
|
||||
"short-version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
$details = [ordered]@{}
|
||||
foreach ($mcVer in $mcVersionList) {
|
||||
$details[$mcVer] = @{
|
||||
Fabric = "$baseUrl/$jarKey"
|
||||
Forge = "$baseUrl/$jarKey"
|
||||
NeoForge = "$baseUrl/$jarKey"
|
||||
Quilt = "$baseUrl/$jarKey"
|
||||
}
|
||||
}
|
||||
@{
|
||||
version = $shortVersion
|
||||
timestamp = [System.DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
|
||||
details = $details
|
||||
} | ConvertTo-Json -Depth 10 | Set-Content -Path "$ArtifactDir/detail$suffix.json" -NoNewline
|
||||
@@ -1,86 +0,0 @@
|
||||
param(
|
||||
[string]$ArtifactDir,
|
||||
[string]$Type # "beta" or "release"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$suffix = if ($Type -eq "beta") { "-beta" } else { "" }
|
||||
|
||||
# ---- Find the built jar ----
|
||||
$jar = Get-ChildItem -Path $ArtifactDir -Filter "*.jar" | Where-Object { $_.Name -notlike "*-sources.jar" } | Select-Object -First 1
|
||||
if (-not $jar) { throw "No jar found in $ArtifactDir" }
|
||||
|
||||
$jarFilename = $jar.Name
|
||||
$jarKey = "mods/$jarFilename"
|
||||
|
||||
# ---- Derive short version from gradle.properties + build number ----
|
||||
# mod_version=15.0, IS_SNAPSHOT=true, GITHUB_RUN_NUMBER=33 -> 15.0-s33
|
||||
# mod_version=15.0, IS_SNAPSHOT=false -> 15.0
|
||||
$version = ((Get-Content "gradle.properties" | Select-String "^mod_version=") -replace "^mod_version=", "").Trim()
|
||||
$isSnapshot = if ($env:IS_SNAPSHOT) { $env:IS_SNAPSHOT -ne "false" } else { $true }
|
||||
if ($isSnapshot) {
|
||||
$shortVersion = "$version-s$env:GITHUB_RUN_NUMBER"
|
||||
} else {
|
||||
$shortVersion = "$version"
|
||||
}
|
||||
|
||||
# ---- Read MC major versions from gradle.properties ----
|
||||
$mcVersions = ((Get-Content "gradle.properties" | Select-String "^minecraft_major_versions=") -replace "^.*=", "").Trim()
|
||||
$mcVersionList = $mcVersions -split "," | ForEach-Object { $_.Trim() }
|
||||
|
||||
$baseUrl = "_BASE_URL_"
|
||||
|
||||
# ---- Generate latest(-beta).json ----
|
||||
$latestJson = @{
|
||||
version = $shortVersion
|
||||
downloads = @{ Universal = "$baseUrl/$jarKey" }
|
||||
launchermeta = @{}
|
||||
} | ConvertTo-Json -Depth 10
|
||||
|
||||
# ---- Generate detail(-beta).json ----
|
||||
$details = [ordered]@{}
|
||||
foreach ($mcVer in $mcVersionList) {
|
||||
$details[$mcVer] = @{
|
||||
Fabric = "$baseUrl/$jarKey"
|
||||
Forge = "$baseUrl/$jarKey"
|
||||
NeoForge = "$baseUrl/$jarKey"
|
||||
Quilt = "$baseUrl/$jarKey"
|
||||
}
|
||||
}
|
||||
$detailJson = @{
|
||||
version = $shortVersion
|
||||
timestamp = [System.DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
|
||||
details = $details
|
||||
} | ConvertTo-Json -Depth 10
|
||||
|
||||
# ---- Upload files to R2 ----
|
||||
$env:AWS_ACCESS_KEY_ID = $env:R2_SECRET_ID
|
||||
$env:AWS_SECRET_ACCESS_KEY = $env:R2_SECRET_KEY
|
||||
$env:AWS_DEFAULT_REGION = "auto"
|
||||
|
||||
aws s3 cp "$ArtifactDir/$jarFilename" "s3://$env:R2_BUCKET/$jarKey" --endpoint-url $($env:R2_BASE_URL.TrimEnd('/'))
|
||||
$latestJson -replace $baseUrl, "https://csl.3-3.dev" | Set-Content -Path "$ArtifactDir/latest$suffix.json" -NoNewline
|
||||
$detailJson -replace $baseUrl, "https://csl.3-3.dev" | Set-Content -Path "$ArtifactDir/detail$suffix.json" -NoNewline
|
||||
aws s3 cp "$ArtifactDir/latest$suffix.json" "s3://$env:R2_BUCKET/latest$suffix.json" --endpoint-url $($env:R2_BASE_URL.TrimEnd('/'))
|
||||
aws s3 cp "$ArtifactDir/detail$suffix.json" "s3://$env:R2_BUCKET/detail$suffix.json" --endpoint-url $($env:R2_BASE_URL.TrimEnd('/'))
|
||||
|
||||
# ---- Upload files to COS ----
|
||||
$cdnBase = "https://csl.littleservice.cn"
|
||||
try {
|
||||
pip install coscmd -q --disable-pip-version-check
|
||||
coscmd config -a $env:COS_SECRET_ID -s $env:COS_SECRET_KEY -b $env:COS_BUCKET -r ap-shanghai
|
||||
coscmd upload "$ArtifactDir/$jarFilename" "$jarKey"
|
||||
$latestJson -replace $baseUrl, $cdnBase | Set-Content -Path "$ArtifactDir/latest$suffix.json" -NoNewline
|
||||
$detailJson -replace $baseUrl, $cdnBase | Set-Content -Path "$ArtifactDir/detail$suffix.json" -NoNewline
|
||||
coscmd upload "$ArtifactDir/latest$suffix.json" "latest$suffix.json"
|
||||
coscmd upload "$ArtifactDir/detail$suffix.json" "detail$suffix.json"
|
||||
} catch {
|
||||
Write-Warning "COS JSON upload failed: $_"
|
||||
}
|
||||
|
||||
# ---- Output CDN URLs for subsequent refresh step ----
|
||||
"cdn-urls<<EOF" | Out-File $env:GITHUB_OUTPUT -Append
|
||||
"${cdnBase}/latest${suffix}.json" | Out-File $env:GITHUB_OUTPUT -Append
|
||||
"${cdnBase}/detail${suffix}.json" | Out-File $env:GITHUB_OUTPUT -Append
|
||||
"EOF" | Out-File $env:GITHUB_OUTPUT -Append
|
||||
+43
-32
@@ -17,20 +17,10 @@ jobs:
|
||||
with:
|
||||
artifact-name: CustomSkinLoader-Beta-${{ github.run_number }}
|
||||
|
||||
publish:
|
||||
name: Publish
|
||||
publish-release:
|
||||
name: Publish to GitHub Release
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
environment: Build
|
||||
env:
|
||||
IS_SNAPSHOT: 'true'
|
||||
R2_SECRET_ID: ${{ secrets.R2_SECRET_ID }}
|
||||
R2_SECRET_KEY: ${{ secrets.R2_SECRET_KEY }}
|
||||
R2_BUCKET: ${{ secrets.R2_BUCKET }}
|
||||
R2_BASE_URL: ${{ secrets.R2_BASE_URL }}
|
||||
COS_SECRET_ID: ${{ secrets.COS_SECRET_ID }}
|
||||
COS_SECRET_KEY: ${{ secrets.COS_SECRET_KEY }}
|
||||
COS_BUCKET: ${{ secrets.COS_BUCKET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@@ -39,12 +29,7 @@ jobs:
|
||||
name: CustomSkinLoader-Beta-${{ github.run_number }}
|
||||
path: artifact
|
||||
|
||||
- uses: ./.github/actions/publish
|
||||
with:
|
||||
artifact-dir: artifact
|
||||
type: beta
|
||||
|
||||
- name: Generate CI-Build body
|
||||
- name: Generate release body
|
||||
shell: pwsh
|
||||
run: |
|
||||
@"
|
||||
@@ -54,20 +39,46 @@ jobs:
|
||||
[How to use?](https://github.com/xfl03/MCCustomSkinLoader/wiki/How-to-Use)
|
||||
"@ | Set-Content -Path ci-build-body.md -NoNewline
|
||||
|
||||
- name: Move CI-Build tag to current commit
|
||||
uses: richardsimko/update-tag@v1
|
||||
- uses: ./.github/actions/publish-release
|
||||
with:
|
||||
tag_name: CI-Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifact-path: artifact
|
||||
tag-name: CI-Build
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: '*.jar'
|
||||
prerelease: 'true'
|
||||
replace: 'true'
|
||||
notes-file: ci-build-body.md
|
||||
|
||||
- name: GitHub PreRelease
|
||||
uses: softprops/action-gh-release@v3
|
||||
publish-object-storage:
|
||||
name: Publish to Object Storage
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
environment: Build
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
tag_name: CI-Build
|
||||
name: CustomSkinLoader CI-Build
|
||||
prerelease: true
|
||||
body_path: ci-build-body.md
|
||||
files: artifact/*.jar
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: CustomSkinLoader-Beta-${{ github.run_number }}
|
||||
path: artifact
|
||||
|
||||
- name: Upload jar to R2
|
||||
uses: ./.github/actions/publish-object-storage
|
||||
with:
|
||||
artifact-path: artifact
|
||||
endpoint-url: ${{ secrets.R2_BASE_URL }}
|
||||
bucket: ${{ secrets.R2_BUCKET }}
|
||||
access-key-id: ${{ secrets.R2_SECRET_ID }}
|
||||
secret-access-key: ${{ secrets.R2_SECRET_KEY }}
|
||||
target-path: mods/
|
||||
files: '*.jar'
|
||||
|
||||
- name: Upload JSONs to R2
|
||||
uses: ./.github/actions/publish-object-storage
|
||||
with:
|
||||
artifact-path: artifact
|
||||
endpoint-url: ${{ secrets.R2_BASE_URL }}
|
||||
bucket: ${{ secrets.R2_BUCKET }}
|
||||
access-key-id: ${{ secrets.R2_SECRET_ID }}
|
||||
secret-access-key: ${{ secrets.R2_SECRET_KEY }}
|
||||
files: '*.json'
|
||||
@@ -10,28 +10,25 @@ jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: windows-latest
|
||||
outputs:
|
||||
mod_name: ${{ steps.build.outputs.mod_name }}
|
||||
edition: ${{ steps.build.outputs.edition }}
|
||||
short_version: ${{ steps.build.outputs.short_version }}
|
||||
loaders: ${{ steps.build.outputs.loaders }}
|
||||
game_versions: ${{ steps.build.outputs.game_versions }}
|
||||
java: ${{ steps.build.outputs.java }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: ./.github/actions/build
|
||||
id: build
|
||||
with:
|
||||
is-snapshot: 'false'
|
||||
artifact-name: CustomSkinLoader-Release-${{ github.run_number }}
|
||||
|
||||
publish:
|
||||
name: Publish
|
||||
publish-release:
|
||||
name: Publish to GitHub Release
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
environment: Build
|
||||
env:
|
||||
IS_SNAPSHOT: 'false'
|
||||
R2_SECRET_ID: ${{ secrets.R2_SECRET_ID }}
|
||||
R2_SECRET_KEY: ${{ secrets.R2_SECRET_KEY }}
|
||||
R2_BUCKET: ${{ secrets.R2_BUCKET }}
|
||||
R2_BASE_URL: ${{ secrets.R2_BASE_URL }}
|
||||
COS_SECRET_ID: ${{ secrets.COS_SECRET_ID }}
|
||||
COS_SECRET_KEY: ${{ secrets.COS_SECRET_KEY }}
|
||||
COS_BUCKET: ${{ secrets.COS_BUCKET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@@ -40,15 +37,60 @@ jobs:
|
||||
name: CustomSkinLoader-Release-${{ github.run_number }}
|
||||
path: artifact
|
||||
|
||||
- uses: ./.github/actions/publish
|
||||
- uses: ./.github/actions/publish-release
|
||||
with:
|
||||
artifact-dir: artifact
|
||||
type: release
|
||||
artifact-path: artifact
|
||||
tag-name: ${{ github.event.release.tag_name }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: '*.jar'
|
||||
generate-notes: 'true'
|
||||
|
||||
- name: Get publish info from gradle.properties
|
||||
id: info
|
||||
shell: pwsh
|
||||
run: ./.github/scripts/Get-PublishInfo.ps1
|
||||
publish-object-storage:
|
||||
name: Publish to Object Storage
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
environment: Build
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: CustomSkinLoader-Release-${{ github.run_number }}
|
||||
path: artifact
|
||||
|
||||
- name: Upload jar to R2
|
||||
uses: ./.github/actions/publish-object-storage
|
||||
with:
|
||||
artifact-path: artifact
|
||||
endpoint-url: ${{ secrets.R2_BASE_URL }}
|
||||
bucket: ${{ secrets.R2_BUCKET }}
|
||||
access-key-id: ${{ secrets.R2_SECRET_ID }}
|
||||
secret-access-key: ${{ secrets.R2_SECRET_KEY }}
|
||||
target-path: mods/
|
||||
files: '*.jar'
|
||||
|
||||
- name: Upload JSONs to R2
|
||||
uses: ./.github/actions/publish-object-storage
|
||||
with:
|
||||
artifact-path: artifact
|
||||
endpoint-url: ${{ secrets.R2_BASE_URL }}
|
||||
bucket: ${{ secrets.R2_BUCKET }}
|
||||
access-key-id: ${{ secrets.R2_SECRET_ID }}
|
||||
secret-access-key: ${{ secrets.R2_SECRET_KEY }}
|
||||
files: '*.json'
|
||||
|
||||
publish-mod-sites:
|
||||
name: Publish to Mod Sites
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
environment: Build
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: CustomSkinLoader-Release-${{ github.run_number }}
|
||||
path: artifact
|
||||
|
||||
- uses: Kir-Antipov/[email protected]
|
||||
with:
|
||||
@@ -58,25 +100,18 @@ jobs:
|
||||
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
|
||||
curseforge-id: 286924
|
||||
curseforge-token: ${{ secrets.CURSEFORGE_API_KEY }}
|
||||
name: CustomSkinLoader_Universal_${{ steps.info.outputs.short-version }}
|
||||
version: ${{ steps.info.outputs.short-version }}-Universal
|
||||
name: ${{ needs.build.outputs.mod_name }}_${{ needs.build.outputs.edition }}_${{ needs.build.outputs.short_version }}
|
||||
version: ${{ needs.build.outputs.short_version }}-${{ needs.build.outputs.edition }}
|
||||
version-type: release
|
||||
loaders: |
|
||||
${{ steps.info.outputs.loaders }}
|
||||
${{ needs.build.outputs.loaders }}
|
||||
game-versions: |
|
||||
${{ steps.info.outputs.game-versions }}
|
||||
${{ needs.build.outputs.game_versions }}
|
||||
game-version-filter: releases
|
||||
java: |
|
||||
${{ steps.info.outputs.java }}
|
||||
${{ needs.build.outputs.java }}
|
||||
retry-attempts: 2
|
||||
retry-delay: 10000
|
||||
fail-mode: fail
|
||||
files: |
|
||||
artifact/*.jar
|
||||
|
||||
- name: GitHub Release
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
files: artifact/*.jar
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
+1
-1
@@ -38,4 +38,4 @@ bin/
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
+1
-1
@@ -39,7 +39,7 @@ public final class InterfacePatch extends PatchSupport {
|
||||
modified |= this.requireModified("native-image.interface", this.addInterface(context.getCurrentClassNode(), FAKE_NATIVE_IMAGE));
|
||||
}
|
||||
if (context.isTarget(IDENTIFIER)) {
|
||||
modified |= this.requireModified("resource-location.patch", this.makeMethodPublicNonFinal(context.findMethod(IDENTIFIER, "<init>", "(" + objectDesc(STRING) + objectDesc(STRING) + ")V")));
|
||||
modified |= this.requireModified("identifier.patch", this.makeMethodPublicNonFinal(context.findMethod(IDENTIFIER, "<init>", "(" + objectDesc(STRING) + objectDesc(STRING) + ")V")));
|
||||
}
|
||||
|
||||
return modified;
|
||||
|
||||
+2
-2
@@ -88,11 +88,11 @@ public final class SkinTexturePatch extends PatchSupport {
|
||||
private boolean replaceSkinTextureDownloaderProcessLegacySkin(ClassTransformationContext context) {
|
||||
boolean modified = false;
|
||||
// 24w46a ~ 25w37a (1.21.4 ~ 1.21.8)
|
||||
modified |= this.applyIfMatches("[769,772],[0x400000DE,0x4000010C]", "skin-texture-downloader.process-legacy-skin.string", () -> replaceSkinTextureDownloaderProcessLegacySkin(
|
||||
modified |= this.applyIfMatches("[769,772],[0x400000DE,0x4000010C]", "skin-texture-downloader.process-legacy-skin.v1", () -> replaceSkinTextureDownloaderProcessLegacySkin(
|
||||
context, context.findMethod(SKIN_TEXTURE_DOWNLOADER, "lambda$downloadAndRegisterSkin$0", "(" + objectDesc(PATH) + objectDesc(STRING) + "Z)" + objectDesc(NATIVE_IMAGE))
|
||||
));
|
||||
// 1.21.9-pre1+ (1.21.9+)
|
||||
modified |= this.applyIfMatches("[773,800],[804,0x40000000],[0x4000010D,]", "skin-texture-downloader.process-legacy-skin.downloaded-texture", () -> replaceSkinTextureDownloaderProcessLegacySkin(
|
||||
modified |= this.applyIfMatches("[773,800],[804,0x40000000],[0x4000010D,]", "skin-texture-downloader.process-legacy-skin.v2", () -> replaceSkinTextureDownloaderProcessLegacySkin(
|
||||
context, context.findMethod(SKIN_TEXTURE_DOWNLOADER, "lambda$downloadAndRegisterSkin$0", "(" + objectDesc(PATH) + objectDesc(CLIENT_ASSET_DOWNLOADED_TEXTURE) + "Z)" + objectDesc(NATIVE_IMAGE))
|
||||
));
|
||||
return modified;
|
||||
|
||||
+4
-3
@@ -2,6 +2,7 @@ package customskinloader.bootstrap.fabric.v1;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -26,7 +27,7 @@ final class TransformerBootstrap {
|
||||
private static final Logger LOGGER = BootstrapLogger.LOGGER;
|
||||
private static final TransformerBootstrapSupport SUPPORT = new TransformerBootstrapSupport(TransformerBootstrap.class, "intermediary");
|
||||
private static final Object LOCK = new Object();
|
||||
private static final List<PendingMixinTarget> PENDING_MIXIN_TARGETS = new ArrayList<PendingMixinTarget>();
|
||||
private static final List<PendingMixinTarget> PENDING_MIXIN_TARGETS = new ArrayList<>();
|
||||
private static final String DUMMY_MIXIN = "customskinloader.bootstrap.fabric.v1.mixin.DummyMixin";
|
||||
|
||||
private TransformerBootstrap() {
|
||||
@@ -45,7 +46,7 @@ final class TransformerBootstrap {
|
||||
|
||||
private static void releaseRuntimeArtifacts() {
|
||||
try {
|
||||
java.nio.file.Path commonJar = CommonJarInstaller.releaseCommonJar(FabricLoader.getInstance().getGameDir(), "intermediary");
|
||||
Path commonJar = CommonJarInstaller.releaseCommonJar(FabricLoader.getInstance().getGameDir(), "intermediary");
|
||||
FabricLauncherBase.getLauncher().addToClassPath(commonJar, "customskinloader.");
|
||||
LOGGER.info("Added CustomSkinLoader Common jar to Fabric class path: " + BootstrapLogger.formatPath(commonJar));
|
||||
} catch (Exception exception) {
|
||||
@@ -135,7 +136,7 @@ final class TransformerBootstrap {
|
||||
targetClassNames.add(targetInternalName);
|
||||
|
||||
Map<String, List<Object>> mixinMapping = (Map<String, List<Object>>) getField(mixinConfig, "mixinMapping");
|
||||
List<Object> mappedMixins = mixinMapping.computeIfAbsent(targetBinaryName, k -> new ArrayList<Object>());
|
||||
List<Object> mappedMixins = mixinMapping.computeIfAbsent(targetBinaryName, k -> new ArrayList<>());
|
||||
if (!mappedMixins.contains(mixinInfo)) {
|
||||
mappedMixins.add(mixinInfo);
|
||||
}
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import net.minecraftforge.fml.loading.moddiscovery.ModsFolderLocator;
|
||||
|
||||
import customskinloader.bootstrap.util.ModLocatorUtils;
|
||||
|
||||
public final class BridgeModLocator extends ModsFolderLocator {
|
||||
public BridgeModLocator() {
|
||||
public final class CommonModLocator extends ModsFolderLocator {
|
||||
public CommonModLocator() {
|
||||
ModLocatorUtils.initialize(this, ModsFolderLocator.class, FMLPaths.GAMEDIR.get(), "srg");
|
||||
}
|
||||
|
||||
+2
-2
@@ -74,12 +74,12 @@ final class TransformerBootstrap {
|
||||
|
||||
// NeoForge
|
||||
public cpw.mods.modlauncher.api.TargetType getTargetType() {
|
||||
return cpw.mods.modlauncher.api.TargetType.CLASS;
|
||||
return cpw.mods.modlauncher.api.TargetType.CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] labels() {
|
||||
return new String[]{"customskinloader:" + this.targetClassName.replace('/', '.')};
|
||||
return new String[] {"customskinloader:" + this.targetClassName.replace('/', '.')};
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
customskinloader.bootstrap.forge.v2.BridgeModLocator
|
||||
customskinloader.bootstrap.forge.v2.CommonModLocator
|
||||
+1
-1
@@ -1 +1 @@
|
||||
customskinloader.bootstrap.forge.v2.BridgeModLocator
|
||||
customskinloader.bootstrap.forge.v2.CommonModLocator
|
||||
+2
-2
@@ -4,8 +4,8 @@ import customskinloader.bootstrap.util.ModLocatorUtils;
|
||||
import net.neoforged.fml.loading.FMLPaths;
|
||||
import net.neoforged.fml.loading.moddiscovery.ModsFolderLocator;
|
||||
|
||||
public final class BridgeModLocator extends ModsFolderLocator {
|
||||
public BridgeModLocator() {
|
||||
public final class CommonModLocator extends ModsFolderLocator {
|
||||
public CommonModLocator() {
|
||||
ModLocatorUtils.initialize(this, ModsFolderLocator.class, FMLPaths.GAMEDIR.get(), "official");
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
customskinloader.bootstrap.neoforge.v1.BridgeModLocator
|
||||
customskinloader.bootstrap.neoforge.v1.CommonModLocator
|
||||
+2
-2
@@ -4,8 +4,8 @@ import customskinloader.bootstrap.util.ModLocatorUtils;
|
||||
import net.neoforged.fml.loading.FMLPaths;
|
||||
import net.neoforged.fml.loading.moddiscovery.locators.ModsFolderLocator;
|
||||
|
||||
public final class BridgeModLocator extends ModsFolderLocator {
|
||||
public BridgeModLocator() {
|
||||
public final class CommonModLocator extends ModsFolderLocator {
|
||||
public CommonModLocator() {
|
||||
ModLocatorUtils.initialize(this, ModsFolderLocator.class, FMLPaths.GAMEDIR.get(), "official");
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
customskinloader.bootstrap.neoforge.v2.BridgeModLocator
|
||||
customskinloader.bootstrap.neoforge.v2.CommonModLocator
|
||||
@@ -47,7 +47,7 @@ public class CustomSkinAPIPlus extends CustomSkinAPI {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "CustomSKinAPIPlus";
|
||||
return "CustomSkinAPIPlus";
|
||||
}
|
||||
|
||||
public static class CustomSkinAPIPlusPayload{
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package customskinloader.loader.jsonapi;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import customskinloader.loader.JsonAPILoader;
|
||||
import customskinloader.loader.MojangAPILoader;
|
||||
import customskinloader.plugin.ICustomSkinLoaderPlugin;
|
||||
import customskinloader.profile.UserProfile;
|
||||
import customskinloader.utils.TextureUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MinecraftCapesAPI implements JsonAPILoader.IJsonAPI {
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package customskinloader.loader.jsonapi;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gson.Gson;
|
||||
import customskinloader.CustomSkinLoader;
|
||||
@@ -10,8 +12,6 @@ import customskinloader.plugin.ICustomSkinLoaderPlugin;
|
||||
import customskinloader.profile.UserProfile;
|
||||
import customskinloader.utils.TextureUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Wynntils cape API
|
||||
* <a href="https://wynntils.com/">Home</a>
|
||||
|
||||
@@ -73,18 +73,9 @@ public class ProfileCache {
|
||||
return;
|
||||
saveLocalProfile(username,profile);
|
||||
}
|
||||
public void putLoader(String username, Function<UserProfile, ?> loader) {
|
||||
this.profileLoaders.putIfAbsent(username, new ConcurrentLinkedDeque<>());
|
||||
this.profileLoaders.get(username).offerLast(loader);
|
||||
}
|
||||
|
||||
private CachedProfile getCachedProfile(String username){
|
||||
CachedProfile cp=cachedProfiles.get(username.toLowerCase());
|
||||
if(cp!=null)
|
||||
return cp;
|
||||
cp=new CachedProfile();
|
||||
cachedProfiles.put(username.toLowerCase(), cp);
|
||||
return cp;
|
||||
return cachedProfiles.computeIfAbsent(username.toLowerCase(), k -> new CachedProfile());
|
||||
}
|
||||
private UserProfile loadLocalProfile(String username){
|
||||
File localProfile=new File(PROFILE_CACHE_DIR,username.toLowerCase()+".json");
|
||||
|
||||
@@ -142,8 +142,8 @@ public class HttpRequestUtil {
|
||||
CustomSkinLoader.logger.debug("Encoded URL: " + url);
|
||||
}
|
||||
HttpURLConnection c = (HttpURLConnection) (new URL(url)).openConnection();
|
||||
c.setReadTimeout(1000 * 5);
|
||||
c.setConnectTimeout(1000 * 5);
|
||||
c.setReadTimeout(1000 * 10);
|
||||
c.setConnectTimeout(1000 * 10);
|
||||
c.setDoInput(true);
|
||||
c.setUseCaches(false);
|
||||
c.setInstanceFollowRedirects(true);
|
||||
@@ -202,8 +202,11 @@ public class HttpRequestUtil {
|
||||
}
|
||||
|
||||
//Load Content
|
||||
InputStream is = "gzip".equals(c.getContentEncoding()) ? new GZIPInputStream(c.getInputStream()) : c.getInputStream();
|
||||
byte[] bytes = IOUtils.toByteArray(is);
|
||||
byte[] bytes;
|
||||
try (InputStream is = "gzip".equals(c.getContentEncoding()) ? new GZIPInputStream(c.getInputStream()) : c.getInputStream()) {
|
||||
bytes = IOUtils.toByteArray(is);
|
||||
}
|
||||
|
||||
if (request.checkPNG && (bytes.length <= 4 || bytes[1] != (byte) 'P' || bytes[2] != (byte) 'N' || bytes[3] != (byte) 'G')) {
|
||||
CustomSkinLoader.logger.debug("Failed to request (Not Standard PNG)");
|
||||
responce.success = false;
|
||||
|
||||
+8
-20
@@ -1,27 +1,15 @@
|
||||
import groovy.json.JsonSlurper
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
import org.gradle.api.attributes.java.TargetJvmVersion
|
||||
|
||||
def requireBuildProperty = { String propertyName ->
|
||||
def propertyValue = rootProject.findProperty(propertyName)?.toString()?.trim()
|
||||
if (!propertyValue) {
|
||||
throw new GradleException("Missing ${propertyName} in gradle.properties")
|
||||
}
|
||||
propertyValue
|
||||
}
|
||||
def info = new JsonSlurper().parse(file("build.info.json"))
|
||||
|
||||
def isTruthy = { String value ->
|
||||
if (value == null) {
|
||||
return true
|
||||
}
|
||||
|
||||
value.trim().toLowerCase(Locale.ROOT) == 'true'
|
||||
}
|
||||
|
||||
def releaseName = requireBuildProperty('mod_name')
|
||||
def releaseGroup = requireBuildProperty('mod_group')
|
||||
def releaseVersion = requireBuildProperty('mod_version')
|
||||
def releaseGroup = info['mod_group']
|
||||
def releaseName = info['mod_name']
|
||||
def releaseVersion = info['mod_version']
|
||||
def releaseEdition = info['edition']
|
||||
def releaseIsSnapshot = build_type != 'release'
|
||||
def releaseBuildNumber = System.getenv('GITHUB_RUN_NUMBER')?.trim() ?: '0'
|
||||
def releaseIsSnapshot = isTruthy(System.getenv('IS_SNAPSHOT'))
|
||||
def releaseFullVersion = (releaseIsSnapshot ? "${releaseVersion}-SNAPSHOT-${releaseBuildNumber}" : releaseVersion).toString()
|
||||
|
||||
group = releaseGroup
|
||||
@@ -32,7 +20,7 @@ ext {
|
||||
modVersion = releaseVersion
|
||||
modBuildNumber = releaseBuildNumber
|
||||
modFullVersion = releaseFullVersion
|
||||
releaseArchiveBaseName = "${releaseName}_Universal".toString()
|
||||
releaseArchiveBaseName = "${releaseName}_${releaseEdition}".toString()
|
||||
}
|
||||
|
||||
def macroValues = {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"mod_group": "customskinloader",
|
||||
"mod_name": "CustomSkinLoader",
|
||||
"mod_version": "15.0",
|
||||
"edition": "Universal",
|
||||
|
||||
"loaders": [
|
||||
"fabric", "forge", "neoforge", "quilt"
|
||||
],
|
||||
"game_versions": [
|
||||
"1.8", "1.8.8", "1.8.9",
|
||||
"1.9", "1.9.4",
|
||||
"1.10", "1.10.2",
|
||||
"1.11", "1.11.2",
|
||||
"1.12", "1.12.1", "1.12.2",
|
||||
"1.13.2",
|
||||
"1.14", "1.14.1", "1.14.2", "1.14.3", "1.14.4",
|
||||
"1.15", "1.15.1", "1.15.2",
|
||||
"1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5",
|
||||
"1.17", "1.17.1",
|
||||
"1.18", "1.18.1", "1.18.2",
|
||||
"1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4",
|
||||
"1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6",
|
||||
"1.21", "1.21.1", "1.21.2", "1.21.3", "1.21.4", "1.21.5", "1.21.6", "1.21.7", "1.21.8", "1.21.9", "1.21.10", "1.21.11",
|
||||
"26.1", "26.1.1", "26.1.2", "26.2"
|
||||
],
|
||||
"java": [
|
||||
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 25
|
||||
]
|
||||
}
|
||||
+4
-12
@@ -1,16 +1,8 @@
|
||||
mod_name=CustomSkinLoader
|
||||
mod_group=customskinloader
|
||||
mod_version=15.0
|
||||
edition=Universal
|
||||
minecraft_major_versions=1.8,1.9,1.10,1.11,1.12,1.13,1.14,1.15,1.16,1.17,1.18,1.19,1.20,1.21,26.1
|
||||
build_type=beta
|
||||
|
||||
minecraft_version=26.1.2
|
||||
fabric_v1_version=0.19.2
|
||||
minecraft_version=26.2
|
||||
fabric_v1_version=0.19.3
|
||||
forge_v1_version=14.23.5.2864
|
||||
forge_v2_version=36.2.42
|
||||
neoforge_v1_version=20.6.13-beta
|
||||
neoforge_v2_version=26.1.2.59-beta
|
||||
|
||||
loaders=fabric,forge,neoforge,quilt
|
||||
game_versions=1.8,1.8.8,1.8.9,1.9,1.9.4,1.10,1.10.2,1.11,1.11.2,1.12,1.12.1,1.12.2,1.13.2,1.14,1.14.1,1.14.2,1.14.3,1.14.4,1.15,1.15.1,1.15.2,1.16,1.16.1,1.16.2,1.16.3,1.16.4,1.16.5,1.17,1.17.1,1.18,1.18.1,1.18.2,1.19,1.19.1,1.19.2,1.19.3,1.19.4,1.20,1.20.1,1.20.2,1.20.3,1.20.4,1.20.5,1.20.6,1.21,1.21.1,1.21.2,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10,1.21.11,26.1,26.1.1,26.1.2
|
||||
java=8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,25
|
||||
neoforge_v2_version=26.2.0.1-beta
|
||||
Reference in new issue
Block a user