1
0

32 Commits

Author SHA1 Message Date
3ecb20afd6 chore: store db fix patch file in all patches 2025-07-08 05:15:01 +03:00
1e10f24efe fix: typo 2025-07-08 05:06:39 +03:00
006fd7c7f5 fix: another sqlx migrations fix 2025-07-08 04:59:33 +03:00
1e8e001eb8 fix: Impl. fixes for all known migration issues from modrinth authors 2025-07-08 04:25:37 +03:00
585935c799 fix: Impl. fix for migration 20240711194701 2025-07-08 03:42:03 +03:00
a64c3360d2 refactor: remove unnecessary code lines 2025-07-08 03:08:27 +03:00
a2b2711204 refactor: Improve update.js and RunningAppBar.vue.
Bump to v0.10.302
2025-07-08 01:12:16 +03:00
ab57926e44 ref: Remove unused workflow steps 2025-07-07 19:20:40 +03:00
35cd79727a Merge commit 'c47bcf665d0686db29732629b36113d3b25915af' into feature-clean 2025-07-07 19:04:00 +03:00
Josiah Glosson
c47bcf665d Fix MinecraftLaunch failing in the case of a package-private main class on Java 8 (#3932)
I don't know of any mod loaders where this is the case, but better be safe than sorry
2025-07-07 15:42:38 +00:00
fba296215d fix for gitea 2025-07-07 18:37:14 +03:00
d7e03fe2be another fix for github actions 2025-07-07 18:20:37 +03:00
ba88244571 fix? 2025-07-07 18:18:57 +03:00
d6d77256fe fix workflow 2025-07-07 18:01:20 +03:00
7449a209fb update workflow 2025-07-07 17:57:14 +03:00
81852859ca update workflow and tauri config 2025-07-07 17:21:03 +03:00
9bd87cf986 Merge commit 'bc90c27e27df60f95a1fdc3572fb0bd5aa4fd102' into feature-clean 2025-07-07 17:14:06 +03:00
Prospector
bc90c27e27 Add ?new to url to give it a new key 2025-07-07 01:18:40 -07:00
Prospector
c1be57773a Update changelog 2025-07-07 01:10:51 -07:00
IMB11
315c68912c fix: use watch for links not mount event (#3929) 2025-07-07 08:01:21 +00:00
Prospector
559d203996 Add a hack to temporarily patch Java 8 not working (#3927) 2025-07-07 00:52:41 -07:00
Prospector
54522518c3 Update changelog + blog post time 2025-07-06 16:37:47 -07:00
Prospector
bacb1561d5 Allow http from asset.localhost and textures.minecraft.net on mac (#3922) 2025-07-06 22:31:55 +00:00
IMB11
b8521f926f feat: skins blogpost (#3904)
* feat: skins blogpost

* fix: clarify changelog note

* Update packages/blog/articles/skins-now-in-modrinth-app.md

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
Signed-off-by: IMB11 <hendersoncal117@gmail.com>

* fix: review issues

* fix: lint

---------

Signed-off-by: IMB11 <hendersoncal117@gmail.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2025-07-06 21:43:36 +00:00
IMB11
b29672f4b4 fix: model issues & move to @modrinth/assets (#3911)
* fix: model issues & move to `@modrinth/assets`

* revert: vscode settings change

* fix: remove unused props
2025-07-06 21:42:55 +00:00
Alejandro González
a32fe6a41f ci: revamp app build workflow, introduce a new one for release deployment (#3921)
* feat(ci): clean up app release build workflow, set app versions to match tag's

* feat(ci): rename Theseus build workflow, add new release workflow

* chore(ci): minor tweaks to `theseus-build` workflow

* chore: update workflow reference in comments
2025-07-06 21:41:52 +00:00
IMB11
0e35135093 refactor: cleanup & fix caching issues on /app page. (#3919) 2025-07-06 21:41:21 +00:00
Josiah Glosson
31ecace083 Fix launching older Forge versions (#3920) 2025-07-06 19:09:49 +00:00
Alejandro González
e5b134f8f4 feat(app): add free official Java Edition skin packs as default skins (#3913) 2025-07-06 10:16:11 +00:00
f914ea1c7d fix: Ubuntu CI/CD building 2025-07-06 01:38:26 +03:00
f55da799f1 fix: Skins (beta) incorrect loading and parsing 2025-07-06 01:38:12 +03:00
Ben
139a4863d1 Fix typo for skin name tag settings (#3903)
Signed-off-by: Ben <67504107+bjsho@users.noreply.github.com>
2025-07-05 19:42:20 +00:00
46 changed files with 2797 additions and 452 deletions

118
.github/workflows/astralrinth-build.yml vendored Normal file
View File

@@ -0,0 +1,118 @@
name: AstralRinth App build
on:
push:
branches:
- main
- feature*
tags:
- 'v*'
paths:
- .github/workflows/astralrinth-build.yml
- 'apps/app/**'
- 'apps/app-frontend/**'
- 'packages/app-lib/**'
- 'packages/app-macros/**'
- 'packages/assets/**'
- 'packages/ui/**'
- 'packages/utils/**'
jobs:
build:
name: Build
strategy:
fail-fast: false
matrix:
# platform: [macos-latest, windows-latest, ubuntu-latest]
platform: [ubuntu-latest]
include:
# - platform: macos-latest
# artifact-target-name: universal-apple-darwin
# - platform: windows-latest
# artifact-target-name: x86_64-pc-windows-msvc
- platform: ubuntu-latest
artifact-target-name: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.platform }}
steps:
- name: 📥 Check out code
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: 🧰 Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: ${{ matrix.artifact-target-name }}
- name: 🧰 Install pnpm
uses: pnpm/action-setup@v4
- name: 🧰 Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: pnpm
- name: 🧰 Install Linux build dependencies
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -yq \
libgtk-3-dev \
libwebkit2gtk-4.1-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
xdg-utils \
openjdk-11-jdk
- name: 💨 Setup Turbo cache
uses: rharkor/caching-for-turbo@v1.8
- name: 🧰 Install dependencies
run: pnpm install
# - name: ✍️ Set up Windows code signing (jsign)
# if: matrix.platform == 'windows-latest' && env.SIGN_WINDOWS_BINARIES == 'true'
# shell: bash
# run: |
# choco install jsign --ignore-dependencies
- name: 🗑️ Clean up cached bundles
shell: bash
run: |
rm -rf target/release/bundle
rm -rf target/*/release/bundle || true
# - name: 🔨 Build macOS app
# if: matrix.platform == 'macos-latest'
# run: pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-release.conf.json
# env:
# TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
# TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: 🔨 Build Linux app
if: matrix.platform == 'ubuntu-latest'
run: pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
# - name: 🔨 Build Windows app
# if: matrix.platform == 'windows-latest'
# shell: pwsh
# run: |
# $env:JAVA_HOME = "$env:JAVA_HOME_11_X64"
# pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json --verbose --bundles 'nsis'
# env:
# TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
# TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: 📤 Upload app bundles
uses: actions/upload-artifact@v3
with:
name: App bundle (${{ matrix.artifact-target-name }})
path: |
target/release/bundle/**
target/*/release/bundle/**

View File

@@ -1,180 +0,0 @@
name: 'AstralRinth App Build'
on:
push:
branches:
- feature*
tags:
- 'build*'
- 'v*'
paths:
- .github/workflows/theseus-release.yml
- 'apps/app/**'
- 'apps/app-frontend/**'
- 'packages/app-lib/**'
- 'packages/app-macros/**'
- 'packages/assets/**'
- 'packages/ui/**'
- 'packages/utils/**'
workflow_dispatch:
inputs:
sign-windows-binaries:
description: Sign Windows binaries
type: boolean
default: true
required: false
jobs:
build:
strategy:
fail-fast: false
matrix:
platform: [macos-latest, windows-latest, ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- name: Rust setup (mac)
if: startsWith(matrix.platform, 'macos')
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
targets: aarch64-apple-darwin, x86_64-apple-darwin
- name: Rust setup
if: "!startsWith(matrix.platform, 'macos')"
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Setup rust cache
uses: actions/cache@v4
with:
path: |
target/**
!target/*/release/bundle/*/*.dmg
!target/*/release/bundle/*/*.app.tar.gz
!target/*/release/bundle/*/*.app.tar.gz.sig
!target/release/bundle/*/*.dmg
!target/release/bundle/*/*.app.tar.gz
!target/release/bundle/*/*.app.tar.gz.sig
!target/release/bundle/appimage/*.AppImage
!target/release/bundle/appimage/*.AppImage.tar.gz
!target/release/bundle/appimage/*.AppImage.tar.gz.sig
!target/release/bundle/deb/*.deb
!target/release/bundle/rpm/*.rpm
!target/release/bundle/msi/*.msi
!target/release/bundle/msi/*.msi.zip
!target/release/bundle/msi/*.msi.zip.sig
!target/release/bundle/nsis/*.exe
!target/release/bundle/nsis/*.nsis.zip
!target/release/bundle/nsis/*.nsis.zip.sig
key: ${{ runner.os }}-rust-target-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-rust-target-
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Install pnpm via corepack
shell: bash
run: |
corepack enable
corepack prepare --activate
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: install dependencies (ubuntu only)
if: startsWith(matrix.platform, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev pkg-config libayatana-appindicator3-dev librsvg2-dev xdg-utils
- name: Install code signing client (Windows only)
if: startsWith(matrix.platform, 'windows')
run: choco install jsign --ignore-dependencies # GitHub runners come with a global Java installation already
- name: Install frontend dependencies
run: pnpm install
- name: Disable Windows code signing for non-final release builds
if: ${{ startsWith(matrix.platform, 'windows') && !startsWith(github.ref, 'refs/tags/v') && !inputs.sign-windows-binaries }}
run: |
jq 'del(.bundle.windows.signCommand)' apps/app/tauri-release.conf.json > apps/app/tauri-release.conf.json.new
Move-Item -Path apps/app/tauri-release.conf.json.new -Destination apps/app/tauri-release.conf.json -Force
- name: build app (macos)
run: pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-release.conf.json
if: startsWith(matrix.platform, 'macos')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: build app (Linux)
run: pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json
if: startsWith(matrix.platform, 'ubuntu')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: build app (Windows)
run: |
[System.Convert]::FromBase64String("$env:DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_BASE64") | Set-Content -Path signer-client-cert.p12 -AsByteStream
$env:DIGICERT_ONE_SIGNER_CREDENTIALS = "$env:DIGICERT_ONE_SIGNER_API_KEY|$PWD\signer-client-cert.p12|$env:DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_PASSWORD"
$env:JAVA_HOME = "$env:JAVA_HOME_11_X64"
pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json --verbose --bundles 'nsis,updater'
Remove-Item -Path signer-client-cert.p12
if: startsWith(matrix.platform, 'windows')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
DIGICERT_ONE_SIGNER_API_KEY: ${{ secrets.DIGICERT_ONE_SIGNER_API_KEY }}
DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_BASE64: ${{ secrets.DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_BASE64 }}
DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_PASSWORD: ${{ secrets.DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_PASSWORD }}
- name: upload ${{ matrix.platform }}
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.platform }}
path: |
target/*/release/bundle/*/*.dmg
target/*/release/bundle/*/*.app.tar.gz
target/*/release/bundle/*/*.app.tar.gz.sig
target/release/bundle/*/*.dmg
target/release/bundle/*/*.app.tar.gz
target/release/bundle/*/*.app.tar.gz.sig
target/release/bundle/*/*.AppImage
target/release/bundle/*/*.AppImage.tar.gz
target/release/bundle/*/*.AppImage.tar.gz.sig
target/release/bundle/*/*.deb
target/release/bundle/*/*.rpm
target/release/bundle/msi/*.msi
target/release/bundle/msi/*.msi.zip
target/release/bundle/msi/*.msi.zip.sig
target/release/bundle/nsis/*.exe
target/release/bundle/nsis/*.nsis.zip
target/release/bundle/nsis/*.nsis.zip.sig

30
Cargo.lock generated
View File

@@ -542,9 +542,9 @@ dependencies = [
[[package]]
name = "async-channel"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16c74e56284d2188cabb6ad99603d1ace887a5d7e7b695d01b728155ed9ed427"
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
dependencies = [
"concurrent-queue",
"event-listener-strategy",
@@ -632,7 +632,7 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc"
dependencies = [
"async-channel 2.4.0",
"async-channel 2.5.0",
"async-io",
"async-lock",
"async-signal",
@@ -1082,11 +1082,11 @@ dependencies = [
[[package]]
name = "blocking"
version = "1.6.1"
version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
dependencies = [
"async-channel 2.4.0",
"async-channel 2.5.0",
"async-task",
"futures-io",
"futures-lite 2.6.0",
@@ -6022,9 +6022,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "plist"
version = "1.7.2"
version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d77244ce2d584cd84f6a15f86195b8c9b2a0dfbfd817c09e0464244091a58ed"
checksum = "546b279bf0638ee811d9e47de2ca5b66575a543035d79fdf83959dd2f5c3b4c3"
dependencies = [
"base64 0.22.1",
"indexmap 2.10.0",
@@ -6957,9 +6957,9 @@ dependencies = [
[[package]]
name = "rgb"
version = "0.8.50"
version = "0.8.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
checksum = "a457e416a0f90d246a4c3288bd7a25b2304ca727f253f95be383dd17af56be8f"
dependencies = [
"bytemuck",
]
@@ -7355,9 +7355,9 @@ dependencies = [
[[package]]
name = "schemars"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1375ba8ef45a6f15d83fa8748f1079428295d403d6ea991d09ab100155fbc06d"
checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
dependencies = [
"dyn-clone",
"ref-cast",
@@ -7805,7 +7805,7 @@ dependencies = [
"indexmap 1.9.3",
"indexmap 2.10.0",
"schemars 0.9.0",
"schemars 1.0.3",
"schemars 1.0.4",
"serde",
"serde_derive",
"serde_json",
@@ -9036,7 +9036,7 @@ dependencies = [
[[package]]
name = "theseus"
version = "0.10.1"
version = "1.0.0-local"
dependencies = [
"ariadne",
"async-compression",
@@ -9101,7 +9101,7 @@ dependencies = [
[[package]]
name = "theseus_gui"
version = "0.10.1"
version = "1.0.0-local"
dependencies = [
"chrono",
"daedalus",

View File

@@ -1,7 +1,7 @@
{
"name": "@modrinth/app-frontend",
"private": true,
"version": "0.10.1",
"version": "1.0.0-local",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1 +0,0 @@
{"asset":{"version":"2.0","generator":"Blockbench 4.12.4 glTF exporter"},"scenes":[{"nodes":[1],"name":"blockbench_export"}],"scene":0,"nodes":[{"rotation":[0,0,0.19509032201612825,0.9807852804032304],"translation":[0.15625,1,0],"name":"Cape","mesh":0},{"children":[0]}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":288,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":576,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":768,"byteLength":72,"target":34963}],"buffers":[{"byteLength":840,"uri":"data:application/octet-stream;base64,AAAAPQAAAAAAAKA+AAAAPQAAAAAAAKC+AAAAPQAAgL8AAKA+AAAAPQAAgL8AAKC+AAAAvQAAAAAAAKC+AAAAvQAAAAAAAKA+AAAAvQAAgL8AAKC+AAAAvQAAgL8AAKA+AAAAvQAAAAAAAKC+AAAAPQAAAAAAAKC+AAAAvQAAAAAAAKA+AAAAPQAAAAAAAKA+AAAAvQAAgL8AAKA+AAAAPQAAgL8AAKA+AAAAvQAAgL8AAKC+AAAAPQAAgL8AAKC+AAAAvQAAAAAAAKA+AAAAPQAAAAAAAKA+AAAAvQAAgL8AAKA+AAAAPQAAgL8AAKA+AAAAPQAAAAAAAKC+AAAAvQAAAAAAAKC+AAAAPQAAgL8AAKC+AAAAvQAAgL8AAKC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACAPAAAgD0AADA+AACAPQAAgDwAAAg/AAAwPgAACD8AAEA+AACAPQAAsD4AAIA9AABAPgAACD8AALA+AAAIPwAAgDwAAAA9AACAPAAAgD0AADA+AAAAPQAAMD4AAIA9AAAwPgAAAD0AAKg+AAAAPQAAMD4AAAAAAACoPgAAAAAAAEA+AACAPQAAMD4AAIA9AABAPgAACD8AADA+AAAIPwAAAAAAAIA9AACAPAAAgD0AAAAAAAAIPwAAgDwAAAg/AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUA"}],"accessors":[{"bufferView":0,"componentType":5126,"count":24,"max":[0.03125,0,0.3125],"min":[-0.03125,-1,-0.3125],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":24,"max":[0.34375,0.53125],"min":[0,0],"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"}],"materials":[{"pbrMetallicRoughness":{"metallicFactor":0,"roughnessFactor":1,"baseColorTexture":{"index":0}},"alphaMode":"MASK","alphaCutoff":0.05,"doubleSided":true}],"textures":[{"sampler":0,"source":0,"name":"cape.png"}],"samplers":[{"magFilter":9728,"minFilter":9728,"wrapS":33071,"wrapT":33071}],"images":[{"mimeType":"image/png","uri":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAgCAYAAACinX6EAAAAAXNSR0IArs4c6QAABCRJREFUaEPtlktoE1EUhm/sIzFpYsBgHyn2oRZt0C5ESxWFYutKNyKIWBBR6kpBd3Ur2J0FRdCiuKmIIG4qLnzgQqTUXSlVqdoXTU0lljRpYtOHI/8dznBnOm1mphMwJWeTzJ0795zznf/cex2MMXam2S/ht38siJ8V1lgd5mOBARerc3rZnmK37rwvCyk2nE6wezMRh+4EncH9B1ql+fkU64rPsaBz5bqh4T7Daxn1Kc5zVNeEePJIci0Aq71bzenY6JChwAnA0OBHQ/OtJLnWNwqAy61R1tO3k891ueRKoDKwtqbv7MGbgCnfRgHQoqG9hyX4hc/yiho+/HNqVPFtdj2jwTpQgd/RKT7/0ZUku/o4qAJw50KYXbzr4e+3BioYzc3kwGzAUCLWFw2+UBiCb3bNTDHiPQeAP3CGNmg/6VcSBpDu3hhvDQpOCwABwrQKsRIsqYAChy/EQAXwlPiZ3a3igNPkXIyTjr8o5L7PPdzGf59c+sV/faeWeIIIAHPJ8M3kc7l1K09LKghWAIgqoPZ7djPFTlxb4D6yAgBOQfntrUVWVeRk44tplXJorOVGkVIJTBCTpw9ECFYBIEkyMfmsAXh3u1qi5MlxbbGX/x1ZSCjBAAwgfPr6h49R5UVavk0FXC2wju5p07s6iqEF0PtqSlEf+bKzDRwdgaDU7AkoySJ5Oo/D6ZRq/H0yyuJ/lxkSheG/FgCNm7kL0BoEAG32sqtY1YIHd2/mGzTMVgD3y2slqjgW9xY6ma9AThAARIMiBtOpjADwTWc0bEkB+FZsSTxTW0KBsGPXx0yvrUpEeHAAQIM7wBJLcu8DwHaP/H8i6VSND6SiSjBlRW4WWUypVIBbIgzjVgB0tpdKqLReS0KVPTMTfH0ra2cEgAmAAEd+l1z52LybqwBQYAQAyZPh6gtDW4jjuC4fHx8wXSm0JDbeI95SRYHiFZlUaWVtPQiKAugl5E8ASAUYiy8vc0C474uGasPE5PHc4g0wK/f4obom6UNimol7kTZwQLANwOuqBokqDEeQf4lfvvnNxZJcBTBAGZplWQcQ3tcgwY9oWlXinRW4ugoAgNAWWe6ocn1QvgyRTUb4RZFVljlY/3hSBYCqb6cCZo8ekuATVRZPI/gQW8FWAI1VHganVgDQUajdA6y2gAgAcZFBjTBSpG0AcAqc3VVmCMDTbxGWZvIRCaMNkJ7pFMCzVQD4liCQ8kRFUlvaBkCvL+wYw2ZmNUgCgLajFhRPJlv3ADuS1VtjPQCk823S574ffN/x1dQqy8dHR5SN2Spcbaymz2mjwNYDAD4IQn3TDpVLQIAqNjwAANRrAdoI/3sAOF7Xe1khCNQGVH1bL0JGJb1R52VtD8gVYHkAuVKpbMWZV0C2yObKunkF5EqlshVnXgHZIpsr6/4DlbxcPydnT74AAAAASUVORK5CYII="}],"meshes":[{"primitives":[{"mode":4,"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3,"material":0}]}]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -38,7 +38,7 @@
</div>
<div v-if="updateState">
<a>
<Button class="download" :disabled="installState" @click="confirmUpdating(), getRemote(false, false)">
<Button class="download" :disabled="installState" @click="initUpdateModal(), getRemote(false)">
<DownloadIcon />
{{
installState
@@ -48,7 +48,7 @@
</Button>
</a>
</div>
<ModalWrapper ref="confirmUpdate" :has-to-type="false" header="Request to update the AstralRinth launcher">
<ModalWrapper ref="updateModalView" :has-to-type="false" header="Request to update the AstralRinth launcher">
<div class="modal-body">
<div class="markdown-body">
<p>The new version of the AstralRinth launcher is available.</p>
@@ -66,14 +66,30 @@
<p class="cosmic inline-fix">v{{ version }}</p>
</span>
<div class="button-group push-right">
<Button class="download-modal" @click="confirmUpdate.hide()">
Decline</Button>
<Button class="download-modal" @click="approveUpdate()">
Accept
<Button class="updater-modal" @click="updateModalView.hide()">
Cancel</Button>
<Button class="updater-modal" @click="initDownload()">
Download file
</Button>
</div>
</div>
</ModalWrapper>
<ModalWrapper ref="updateRequestFailView" :has-to-type="false" header="Failed to request a file from the server :(">
<div class="modal-body">
<div class="markdown-body">
<p><strong>Error occurred</strong></p>
<p>Unfortunately, the program was unable to download the file from our servers.</p>
<p>Please try downloading it yourself from <a href="https://me.astralium.su/get/ar" target="_blank" rel="noopener noreferrer">Git Astralium</a> if there are any updates available.</p>
</div>
<span>Local AstralRinth
<p class="cosmic inline-fix">v{{ version }}</p>
</span>
</div>
<div class="button-group push-right">
<Button class="updater-modal" @click="updateRequestFailView.hide()">
Close</Button>
</div>
</ModalWrapper>
</div>
<transition name="download">
<Card v-if="showCard === true && currentLoadingBars.length > 0" ref="card" class="info-card">
@@ -129,18 +145,22 @@ const version = await getVersion()
import { installState, getRemote, updateState } from '@/helpers/update.js'
import ModalWrapper from './modal/ModalWrapper.vue'
const confirmUpdate = ref(null)
const updateModalView = ref(null)
const updateRequestFailView = ref(null)
const confirmUpdating = async () => {
confirmUpdate.value.show()
const initUpdateModal = async () => {
updateModalView.value.show()
}
const approveUpdate = async () => {
confirmUpdate.value.hide()
await getRemote(true, true)
const initDownload = async () => {
updateModalView.value.hide()
const result = await getRemote(true);
if (!result) {
updateRequestFailView.value.show()
}
}
await getRemote(true, false)
await getRemote(false)
const router = useRouter()
const card = ref(null)
@@ -375,7 +395,7 @@ onBeforeUnmount(() => {
text-shadow: #26065e;
}
.download-modal {
.updater-modal {
color: #3e8cde;
padding: var(--gap-sm) var(--gap-lg);
text-decoration: none;
@@ -386,9 +406,9 @@ onBeforeUnmount(() => {
transition: color 0.35s ease;
}
.download-modal:hover,
.download-modal:focus,
.download-modal:active {
.updater-modal:hover,
.updater-modal:focus,
.updater-modal:active {
color: #10fae5;
text-shadow: #26065e;
}

View File

@@ -59,7 +59,7 @@ watch(
<div class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Hide nametag</h2>
<p class="m-0 mt-1">Disables the nametag above your player on the skins page. page.</p>
<p class="m-0 mt-1">Disables the nametag above your player on the skins page.</p>
</div>
<Toggle id="hide-nametag-skins-page" v-model="settings.hide_nametag_skins_page" />
</div>

View File

@@ -1,8 +1,3 @@
<script lang="ts">
import capeModelUrl from '@/assets/models/cape.gltf?url'
import wideModelUrl from '@/assets/models/classic_player.gltf?url'
import slimModelUrl from '@/assets/models/slim_player.gltf?url'
</script>
<template>
<UploadSkinModal ref="uploadModal" />
<ModalWrapper ref="modal" @on-hide="resetState">
@@ -16,9 +11,6 @@ import slimModelUrl from '@/assets/models/slim_player.gltf?url'
<div class="max-h-[25rem] w-[16rem] min-w-[16rem] overflow-hidden relative">
<div class="absolute top-[-4rem] left-0 h-[32rem] w-[16rem] flex-shrink-0">
<SkinPreviewRenderer
:slim-model-src="slimModelUrl"
:wide-model-src="wideModelUrl"
:cape-model-src="capeModelUrl"
:variant="variant"
:texture-src="previewSkin || ''"
:cape-src="selectedCapeTexture"

View File

@@ -10,9 +10,6 @@ import {
} from '@modrinth/ui'
import { CheckIcon, XIcon } from '@modrinth/assets'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import capeModelUrl from '@/assets/models/cape.gltf?url'
import wideModelUrl from '@/assets/models/classic_player.gltf?url'
import slimModelUrl from '@/assets/models/slim_player.gltf?url'
const modal = useTemplateRef('modal')
@@ -88,9 +85,6 @@ defineExpose({
<div class="absolute top-[-4rem] left-0 h-[32rem] w-[16rem] flex-shrink-0">
<SkinPreviewRenderer
v-if="currentSkinTexture"
:slim-model-src="slimModelUrl"
:wide-model-src="wideModelUrl"
:cape-model-src="capeModelUrl"
:cape-src="currentCapeTexture"
:texture-src="currentSkinTexture"
:variant="currentSkinVariant"

View File

@@ -4,9 +4,7 @@ import { get_normalized_skin_texture, determineModelType } from '../skins'
import { reactive } from 'vue'
import { setupSkinModel, disposeCaches } from '@modrinth/utils'
import { skinPreviewStorage } from '../storage/skin-preview-storage'
import capeModelUrl from '@/assets/models/cape.gltf?url'
import wideModelUrl from '@/assets/models/classic_player.gltf?url'
import slimModelUrl from '@/assets/models/slim_player.gltf?url'
import { CapeModel, ClassicPlayerModel, SlimPlayerModel } from '@modrinth/assets'
export interface RenderResult {
forwards: string
@@ -127,11 +125,11 @@ class BatchSkinRenderer {
function getModelUrlForVariant(variant: string): string {
switch (variant) {
case 'SLIM':
return slimModelUrl
return SlimPlayerModel
case 'CLASSIC':
case 'UNKNOWN':
default:
return wideModelUrl
return ClassicPlayerModel
}
}
@@ -281,6 +279,7 @@ async function generateHeadRender(skin: Skin): Promise<string> {
headMap.set(headKey, headUrl)
try {
// @ts-expect-error - skinPreviewStorage.store expects a RenderResult, but we are storing a string url.
await skinPreviewStorage.store(headKey, headUrl)
} catch (error) {
console.warn('Failed to store head render in persistent storage:', error)
@@ -335,7 +334,7 @@ export async function generateSkinPreviews(skins: Skin[], capes: Cape[]): Promis
await get_normalized_skin_texture(skin),
modelUrl,
cape?.texture,
capeModelUrl,
CapeModel,
)
map.set(key, renderResult)

View File

@@ -97,7 +97,11 @@ export async function fixUnknownSkins(list: Skin[]) {
export function filterDefaultSkins(list: Skin[]) {
return list
.filter((s) => s.source === 'default' && (!s.name || s.variant === DEFAULT_MODELS[s.name]))
.filter(
(s) =>
s.source === 'default' &&
(!s.name || !(s.name in DEFAULT_MODELS) || s.variant === DEFAULT_MODELS[s.name]),
)
.sort((a, b) => {
const aIndex = a.name ? DEFAULT_MODEL_SORTING.indexOf(a.name) : -1
const bIndex = b.name ? DEFAULT_MODEL_SORTING.indexOf(b.name) : -1

View File

@@ -2,21 +2,19 @@ import { ref } from 'vue'
import { getVersion } from '@tauri-apps/api/app'
import { getArtifact, getOS } from '@/helpers/utils.js'
export const allowState = ref(false)
export const installState = ref(false)
export const updateState = ref(false)
export const latestBetaCommitTruncatedSha = ref('')
export const latestBetaCommitLink = ref('')
export const launcherUrl = 'https://www.astralium.su/get/ar'
const os = ref('')
const currentOS = ref('')
const releaseLink = `https://git.astralium.su/api/v1/repos/didirus/AstralRinth/releases/latest`
const failedFetch = [`Failed to fetch remote releases:`, `Failed to fetch remote commits:`]
const osNames = ['macos', 'windows', 'linux']
const macExtension = `.dmg` // MacOS file type for download
const windowsExtension = `.msi` // Windows file type for download
const blacklistedBuilds = [
const osList = ['macos', 'windows', 'linux']
const macExtensionList = ['.app', '.dmg']
const windowsExtensionList = ['.exe', '.msi']
const blacklistPrefixes = [
`dev`,
`nightly`,
`dirty`,
@@ -26,110 +24,73 @@ const blacklistedBuilds = [
`dirty_nightly`,
] // This is blacklisted builds for download. For example, file.startsWith('dev') is not allowed.
/**
* Asynchronous function to get remote data and handle updates and downloads.
*
* @param {boolean} elementIdBool - Indicates whether to disable an element ID.
* @param {boolean} downloadArtifactBool - Indicates whether to download an artifact.
*/
export async function getRemote(elementIdBool, downloadArtifactBool) {
fetch(releaseLink)
.then((response) => {
if (!response.ok) {
throw new Error(response.status)
}
return response.json()
})
.then(async (data) => {
os.value = await getOS()
const latestRelease = data.name
let remoteVersion = undefined
export async function getRemote(isDownloadState) {
var releaseData = null;
var result = false;
try {
const response = await fetch(releaseLink);
if (!response.ok) {
throw new Error(response.status);
}
const remoteData = await response.json();
currentOS.value = await getOS();
const remoteLatestReleaseTag = remoteData.tag_name;
releaseData = document.getElementById('releaseData');
const remoteVersion = releaseData ? (releaseData.textContent = remoteLatestReleaseTag) : remoteLatestReleaseTag;
if (!elementIdBool) {
const releaseData = document.getElementById('releaseData')
if (releaseData == null) {
console.error('Release data element not found.')
return false
}
releaseData.textContent = latestRelease
remoteVersion = `${releaseData.textContent}`
} else {
remoteVersion = latestRelease
}
if (osNames.includes(os.value.toLowerCase())) {
if (remoteVersion.startsWith('v' + await getVersion())) {
updateState.value = false
allowState.value = false
} else {
updateState.value = true
allowState.value = true
}
} else {
updateState.value = false
allowState.value = false
}
console.log('Update available state is', updateState.value)
console.log('Remote version is', remoteVersion)
console.log('Local version is', await getVersion())
console.log('Operating System is', os.value)
if (osList.includes(currentOS.value.toLowerCase())) {
const localVersion = await getVersion();
const isUpdateAvailable = !remoteVersion.includes(localVersion);
if (downloadArtifactBool) {
installState.value = true
const builds = data.assets
const fileName = getInstaller(getExtension(), builds)
if (fileName != null) {
await getArtifact(fileName[1], fileName[0], os.value, true)
}
installState.value = false
}
})
.catch((error) => {
console.error(failedFetch[0], error)
if (!elementIdBool) {
const errorData = document.getElementById('releaseData')
if (errorData) {
errorData.textContent = `${error.message}`
}
updateState.value = false
allowState.value = false
installState.value = false
}
})
}
updateState.value = isUpdateAvailable;
allowState.value = isUpdateAvailable;
} else {
updateState.value = false;
allowState.value = false;
}
if (isDownloadState) {
installState.value = true;
const builds = remoteData.assets;
const fileName = getInstaller(getExtension(), builds);
result = fileName ? await getArtifact(fileName[1], fileName[0], currentOS.value, true) : false;
installState.value = false;
}
/**
* Retrieves the installer for a specific operating system.
*
* @param {string} osExtension - The file extension of the installer.
* @param {Array} builds - The list of builds.
* @return {Array|null} An array containing the installer name and URL if found, or null if not found.
*/
function getInstaller(osExtension, builds) {
for (let i of builds) {
let blacklistedItem = false
blacklistedBuilds.forEach((item) => {
if (i.name.startsWith(item)) {
return (blacklistedItem = true)
console.log('Update available state is', updateState.value);
console.log('Remote version is', remoteVersion);
console.log('Local version is', await getVersion());
console.log('Operating System is', currentOS.value);
return result;
} catch (error) {
console.error(failedFetch[0], error);
if (!releaseData) {
const errorData = document.getElementById('releaseData');
if (errorData) {
errorData.textContent = `${error.message}`;
}
})
if (i.name.endsWith(osExtension) && !blacklistedItem) {
console.log(i.browser_download_url)
return [i.name, i.browser_download_url]
updateState.value = false;
allowState.value = false;
installState.value = false;
}
}
return null
}
/**
* A function to get the extension based on the operating system.
*
* @return {string} The extension based on the operating system.
*/
function getExtension() {
if (os.value.toLowerCase() == osNames[0]) {
return macExtension
} else if (os.value.toLowerCase() == osNames[1]) {
return windowsExtension
function getInstaller(osExtension, builds) {
console.log(osExtension, builds)
for (const build of builds) {
if (blacklistPrefixes.some(prefix => build.name.startsWith(prefix))) {
continue;
}
if (osExtension.some(ext => build.name.endsWith(ext))) {
console.log(build.name, build.browser_download_url);
return [build.name, build.browser_download_url];
}
}
return null
}
return null;
}
function getExtension() {
return osList.find(osName => osName === currentOS.value.toLowerCase())?.endsWith('macos')
? macExtensionList
: windowsExtensionList;
}

View File

@@ -43,10 +43,6 @@ import { handleSevereError } from '@/store/error'
import { trackEvent } from '@/helpers/analytics'
import type AccountsCard from '@/components/ui/AccountsCard.vue'
import { arrayBufferToBase64 } from '@modrinth/utils'
import capeModelUrl from '@/assets/models/cape.gltf?url'
import wideModelUrl from '@/assets/models/classic_player.gltf?url'
import slimModelUrl from '@/assets/models/slim_player.gltf?url'
const editSkinModal = useTemplateRef('editSkinModal')
const selectCapeModal = useTemplateRef('selectCapeModal')
const uploadSkinModal = useTemplateRef('uploadSkinModal')
@@ -320,9 +316,6 @@ await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
</h1>
<div class="preview-container">
<SkinPreviewRenderer
:wide-model-src="wideModelUrl"
:slim-model-src="slimModelUrl"
:cape-model-src="capeModelUrl"
:cape-src="capeTexture"
:texture-src="skinTexture || ''"
:variant="skinVariant"

View File

@@ -1,6 +1,6 @@
[package]
name = "theseus_gui"
version = "0.10.1"
version = "1.0.0-local" # The actual version is set by the theseus-build workflow on tagging
description = "The Modrinth App is a desktop application for managing your Minecraft mods"
license = "GPL-3.0-only"
repository = "https://github.com/modrinth/code/apps/app/"

View File

@@ -18,5 +18,25 @@
<string>A Minecraft mod wants to access your camera.</string>
<key>NSMicrophoneUsageDescription</key>
<string>A Minecraft mod wants to access your microphone.</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>asset.localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>textures.minecraft.net</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
</dict>
</plist>

View File

@@ -34,9 +34,6 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
// let update_fut = updater.check();
// tracing::info!("Initializing app state...");
State::init().await?;
// let check_bar = theseus::init_loading(
// theseus::LoadingBarType::CheckingForUpdates,
// 1.0,
@@ -87,7 +84,7 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
// #[cfg(not(feature = "updater"))]
// {
// }
tracing::info!("Initializing app state...");
State::init().await?;
tracing::info!("AstralRinth state successfully initialized.");
let state = State::get().await?;
@@ -164,10 +161,10 @@ fn main() {
let mut builder = tauri::Builder::default();
#[cfg(feature = "updater")]
{
builder = builder.plugin(tauri_plugin_updater::Builder::new().build());
}
// #[cfg(feature = "updater")]
// {
// builder = builder.plugin(tauri_plugin_updater::Builder::new().build());
// }
builder = builder
.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {

View File

@@ -41,7 +41,7 @@
]
},
"productName": "AstralRinth App",
"version": "0.10.1",
"version": "0.10.302",
"mainBinaryName": "AstralRinth App",
"identifier": "AstralRinthApp",
"plugins": {
@@ -86,7 +86,7 @@
"capabilities": ["core", "plugins"],
"csp": {
"default-src": "'self' customprotocol: asset:",
"connect-src": "https://git.astralium.su ipc: http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.posthog.com https://*.sentry.io https://api.mclo.gs 'self' data: blob:",
"connect-src": "ipc: https://git.astralium.su http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.posthog.com https://*.sentry.io https://api.mclo.gs 'self' data: blob:",
"font-src": ["https://cdn-raw.modrinth.com/fonts/"],
"img-src": "https: 'unsafe-inline' 'self' asset: http://asset.localhost http://textures.minecraft.net blob: data:",
"style-src": "'unsafe-inline' 'self'",

View File

@@ -1,4 +1,4 @@
<script setup>
<script setup lang="ts">
import {
TrashIcon,
SearchIcon,
@@ -17,76 +17,134 @@ import LatestNewsRow from "~/components/ui/news/LatestNewsRow.vue";
import { homePageProjects } from "~/generated/state.json";
const os = ref(null);
const downloadWindows = ref(null);
const downloadLinux = ref(null);
const downloadSection = ref(null);
const windowsLink = ref(null);
const linuxLinks = {
appImage: null,
deb: null,
rpm: null,
thirdParty: "https://support.modrinth.com/en/articles/9298760",
};
const macLinks = {
universal: null,
};
interface LauncherPlatform {
install_urls: string[];
}
let downloadLauncher;
interface LauncherUpdates {
platforms: {
"darwin-aarch64": LauncherPlatform;
"windows-x86_64": LauncherPlatform;
"linux-x86_64": LauncherPlatform;
};
}
type OSType = "Mac" | "Windows" | "Linux" | null;
const downloadWindows = ref<HTMLAnchorElement | null>(null);
const downloadLinux = ref<HTMLAnchorElement | null>(null);
const downloadSection = ref<HTMLElement | null>(null);
const windowsLink = ref<string | null>(null);
const linuxLinks = reactive({
appImage: null as string | null,
deb: null as string | null,
rpm: null as string | null,
thirdParty: "https://support.modrinth.com/en/articles/9298760",
});
const macLinks = reactive({
universal: null as string | null,
});
const newProjects = homePageProjects.slice(0, 40);
const val = Math.ceil(newProjects.length / 6);
const rows = ref([
const rows = [
newProjects.slice(0, val),
newProjects.slice(val, val * 2),
newProjects.slice(val * 2, val * 3),
newProjects.slice(val * 3, val * 4),
newProjects.slice(val * 4, val * 5),
]);
];
const [{ data: launcherUpdates }] = await Promise.all([
await useAsyncData("launcherUpdates", () =>
$fetch("https://launcher-files.modrinth.com/updates.json"),
),
]);
const { data: launcherUpdates } = await useFetch<LauncherUpdates>(
"https://launcher-files.modrinth.com/updates.json?new",
{
server: false,
getCachedData(key, nuxtApp) {
const cached = (nuxtApp.ssrContext?.cache as any)?.[key] || nuxtApp.payload.data[key];
if (!cached) return;
macLinks.universal = launcherUpdates.value.platforms["darwin-aarch64"].install_urls[0];
windowsLink.value = launcherUpdates.value.platforms["windows-x86_64"].install_urls[0];
linuxLinks.appImage = launcherUpdates.value.platforms["linux-x86_64"].install_urls[1];
linuxLinks.deb = launcherUpdates.value.platforms["linux-x86_64"].install_urls[0];
linuxLinks.rpm = launcherUpdates.value.platforms["linux-x86_64"].install_urls[2];
const now = Date.now();
const cacheTime = cached._cacheTime || 0;
const maxAge = 5 * 60 * 1000;
onMounted(() => {
os.value = navigator?.platform.toString();
os.value = os.value?.includes("Mac")
? "Mac"
: os.value?.includes("Win")
? "Windows"
: os.value?.includes("Linux")
? "Linux"
: null;
if (now - cacheTime > maxAge) {
return null;
}
return cached;
},
transform(data) {
return {
...data,
_cacheTime: Date.now(),
};
},
},
);
const platform = computed<string>(() => {
if (import.meta.server) {
const headers = useRequestHeaders();
return headers["user-agent"] || "";
} else {
return navigator.userAgent || "";
}
});
const os = computed<OSType>(() => {
if (platform.value.includes("Mac")) {
return "Mac";
} else if (platform.value.includes("Win")) {
return "Windows";
} else if (platform.value.includes("Linux")) {
return "Linux";
} else {
return null;
}
});
const downloadLauncher = computed(() => {
if (os.value === "Windows") {
downloadLauncher = () => {
downloadWindows.value.click();
return () => {
downloadWindows.value?.click();
};
} else if (os.value === "Linux") {
downloadLauncher = () => {
downloadLinux.value.click();
return () => {
downloadLinux.value?.click();
};
} else {
downloadLauncher = () => {
return () => {
scrollToSection();
};
}
});
const handleDownload = () => {
downloadLauncher.value();
};
watch(
launcherUpdates,
(newData) => {
if (newData?.platforms) {
macLinks.universal = newData.platforms["darwin-aarch64"]?.install_urls[0] || null;
windowsLink.value = newData.platforms["windows-x86_64"]?.install_urls[0] || null;
linuxLinks.appImage = newData.platforms["linux-x86_64"]?.install_urls[1] || null;
linuxLinks.deb = newData.platforms["linux-x86_64"]?.install_urls[0] || null;
linuxLinks.rpm = newData.platforms["linux-x86_64"]?.install_urls[2] || null;
}
},
{ immediate: true },
);
const scrollToSection = () => {
nextTick(() => {
window.scrollTo({
top: downloadSection.value.offsetTop,
behavior: "smooth",
});
if (downloadSection.value) {
window.scrollTo({
top: downloadSection.value.offsetTop,
behavior: "smooth",
});
}
});
};
@@ -119,7 +177,7 @@ useSeoMeta({
v-if="os"
class="iconified-button brand-button btn btn-large"
rel="noopener nofollow"
@click="downloadLauncher"
@click="handleDownload"
>
<svg
v-if="os === 'Linux'"
@@ -485,7 +543,7 @@ useSeoMeta({
class="project button-animation gradient-border"
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}`"
>
<Avatar :src="project.icon_url" :alt="project.title" size="sm" loading="lazy" />
<Avatar :src="project.icon_url!" :alt="project.title" size="sm" />
<div class="project-info">
<span class="title">
{{ project.title }}
@@ -596,9 +654,7 @@ useSeoMeta({
</div>
<div class="description">
Modrinths launcher is fully open source. You can view the source code on our
<a href="https://github.com/modrinth/theseus" rel="noopener" :target="$external()"
>GitHub</a
>!
<a href="https://github.com/modrinth/theseus" rel="noopener" target="_blank">GitHub</a>!
</div>
</div>
<div class="point">
@@ -788,7 +844,7 @@ useSeoMeta({
Windows
</div>
<div class="description">
<a ref="downloadWindows" :href="windowsLink" download="">
<a ref="downloadWindows" :href="windowsLink || undefined" download="">
<DownloadIcon />
<span> Download the beta </span>
</a>
@@ -812,7 +868,7 @@ useSeoMeta({
Mac
</div>
<div class="description apple">
<a :href="macLinks.universal" download="">
<a :href="macLinks.universal || undefined" download="">
<DownloadIcon />
<span> Download the beta </span>
</a>
@@ -849,19 +905,19 @@ useSeoMeta({
Linux
</div>
<div class="description apple">
<a ref="downloadLinux" :href="linuxLinks.appImage" download="">
<a ref="downloadLinux" :href="linuxLinks.appImage || undefined" download="">
<DownloadIcon />
<span> Download the AppImage </span>
</a>
<a :href="linuxLinks.deb" download="">
<a :href="linuxLinks.deb || undefined" download="">
<DownloadIcon />
<span> Download the DEB </span>
</a>
<a :href="linuxLinks.rpm" download="">
<a :href="linuxLinks.rpm || undefined" download="">
<DownloadIcon />
<span> Download the RPM </span>
</a>
<a :href="linuxLinks.thirdParty" download="">
<a :href="linuxLinks.thirdParty || undefined" download="">
<LinkIcon />
<span> Third-party packages </span>
</a>

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -1,5 +1,12 @@
{
"articles": [
{
"title": "Skins — Now in Modrinth App!",
"summary": "Customize your look, save your favorite skins, and swap them out in a flash, all within Modrinth App.",
"thumbnail": "https://modrinth.com/news/article/skins-now-in-modrinth-app/thumbnail.webp",
"date": "2025-07-06T23:45:00.000Z",
"link": "https://modrinth.com/news/article/skins-now-in-modrinth-app"
},
{
"title": "Creator Updates, July 2025",
"summary": "Addressing recent growth and growing pains that have been affecting creators.",

View File

@@ -4,15 +4,23 @@
<description><![CDATA[Keep up-to-date on the latest news from Modrinth.]]></description>
<link>https://modrinth.com/news/</link>
<generator>@modrinth/blog</generator>
<lastBuildDate>Wed, 02 Jul 2025 02:42:05 GMT</lastBuildDate>
<lastBuildDate>Sat, 05 Jul 2025 21:04:25 GMT</lastBuildDate>
<atom:link href="https://modrinth.com/news/feed/rss.xml" rel="self" type="application/rss+xml"/>
<language><![CDATA[en]]></language>
<item>
<title><![CDATA[Skins — Now in Modrinth App!]]></title>
<description><![CDATA[Customize your look, save your favorite skins, and swap them out in a flash, all within Modrinth App.]]></description>
<link>https://modrinth.com/news/article/skins-now-in-modrinth-app/</link>
<guid isPermaLink="false">https://modrinth.com/news/article/skins-now-in-modrinth-app/</guid>
<pubDate>Sat, 05 Jul 2025 19:19:00 GMT</pubDate>
<content:encoded>&lt;![CDATA[&lt;p&gt;We&apos;re thrilled to roll out Modrinth App &lt;strong&gt;v0.10&lt;/strong&gt; with a beta release of one of our most highly requested features, the &lt;strong&gt;Skins page&lt;/strong&gt;. The Skins page allows you to manage all of your Minecraft skins directly within Modrinth App. You can see all your saved custom skins and the default Minecraft skins in one convenient place.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;./skins-page.webp&quot; alt=&quot;The new skins page, featuring a cute animated player model, your custom skins &amp;amp; default skins.&quot;&gt;&lt;/p&gt;&lt;p&gt;Adding a new skin is simple, even Herobrine could do it! When you add or edit a skin, you can &lt;strong&gt;upload&lt;/strong&gt; your custom texture file directly from your computer, &lt;strong&gt;choose&lt;/strong&gt; between the wide or slim arm style to match your preferred character model, and even &lt;strong&gt;assign&lt;/strong&gt; a specific cape to that look for the perfect finishing touch.&lt;/p&gt;&lt;p&gt;The interface makes it easy to preview your changes in real-time with the animated player model, so you can see exactly how your skin will look in-game before saving it.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;./edit-skin.webp&quot; alt=&quot;The edit skin modal that shows when you go to add or edit a skin.&quot;&gt;&lt;/p&gt;&lt;h2&gt;Fixes and More!&lt;/h2&gt;&lt;p&gt;Alongside this major new feature, &lt;strong&gt;v0.10&lt;/strong&gt; includes a host of improvements and bug fixes to make your experience smoother. We&apos;ve updated the news feed to use our new system, fixed issues with project descriptions, and tidied up how data is handled. For a full breakdown of all the changes, you can &lt;a href=&quot;https://modrinth.com/news/changelog?filter=app&quot;&gt;check out the complete changelog here.&lt;/a&gt;&lt;/p&gt;&lt;p&gt;As the skins feature is in &lt;em&gt;beta&lt;/em&gt;, we&apos;re eager to hear your feedback! &lt;strong&gt;Jump in, give it a try&lt;/strong&gt;, and let us know what you think. You can share your thoughts on our &lt;a href=&quot;https://discord.modrinth.com/&quot; rel=&quot;noopener nofollow ugc&quot;&gt;Discord server&lt;/a&gt; or &lt;a href=&quot;https://support.modrinth.com&quot; rel=&quot;noopener nofollow ugc&quot;&gt;start a support chat&lt;/a&gt; if you&apos;re running into issues.&lt;/p&gt;&lt;p&gt;Thank you! We can&apos;t wait to see your skins in action. Happy customizing!&lt;/p&gt;]]&gt;</content:encoded>
</item>
<item>
<title><![CDATA[Creator Updates, July 2025]]></title>
<description><![CDATA[Addressing recent growth and growing pains that have been affecting creators.]]></description>
<link>https://modrinth.com/news/article/creator-updates-july-2025/</link>
<guid isPermaLink="false">https://modrinth.com/news/article/creator-updates-july-2025/</guid>
<pubDate>Wed, 02 Jul 2025 03:00:00 GMT</pubDate>
<pubDate>Wed, 02 Jul 2025 04:20:00 GMT</pubDate>
<content:encoded>&lt;![CDATA[&lt;p&gt;Hey all,&lt;/p&gt;&lt;p&gt;The last few months have been quite hectic for Modrinth. We&apos;ve experienced all-time highs in both traffic and new creators and have outgrown a lot of our existing systems, which has led to a lot of issues plaguing creators, especially.&lt;/p&gt;&lt;p&gt;The team has been super hard at work at this, and I&apos;m really glad to announce that we&apos;ve fixed most of these issues long term.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Upload issues (inputs not showing up, instability, etc)&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;We&apos;ve tracked these issues down to conflicting code between our ad provider and Modrinth&apos;s. For now, we&apos;ve &lt;strong&gt;disabled ads for all logged in users across the site&lt;/strong&gt; while we work on resolving these long term. Both web users and logged-in web users make a very small percentage of our ad revenue (7% for web and 0.05% for logged-in web users) so creators should see a very minimal revenue drop from this, and have a much better experience navigating and uploading to the site.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Moderation and report response times&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Creators have had to wait, in some cases, weeks to get their projects reviewed. This is unacceptable on our part and we are actively overhauling our moderation tooling to improve the moderation experience (and lowering time spent per project). We&apos;ve also hired 3 additional moderators/support staff (&lt;strong&gt;bringing our total to 7 and the total team to 17 people!&lt;/strong&gt;). We&apos;re hoping to see a significant reduction in queue times over the coming weeks.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ad revenue instability&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;While ad revenue is generally out of our control and tends to fluctuate a lot, on June 4th we noticed a sharp decrease in creator revenue (~35% less than normal levels). While our ad provider initially thought this was a display issue, after further inquiry there were 2 causes: 1) Google AdExchange falsely flagging our traffic as invalid 2) Amazon banning many gaming publishers from their network &lt;a href=&quot;https://www.adweek.com/media/exclusive-ads-from-verizon-shell-and-others-ran-next-to-explicit-videos-on-top-android-app/&quot; rel=&quot;noopener nofollow ugc&quot;&gt;due to panic in the gaming ads space&lt;/a&gt;. While the Amazon ban is now resolved, we no longer are running Google AdExchange in the desktop app due to invalid traffic issues. This will lead to a permanent revenue decrease (AdX contributed to ~20% of our ad revenue). We also updated our prebid version (the underlying tech used to run ad auctions) which has shown a measurable increase, bringing revenue back to &amp;quot;normal&amp;quot; levels. Overall, we are closely monitoring and will keep you all posted. However, despite all the issues, due to some end-of-quarter campaigns, &lt;strong&gt;revenue in June was an all time high, at $227k ($170k paid to creators)&lt;/strong&gt;!&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Payout outages&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Creators should be able to withdraw their revenue at all times, but due to slow PayPal clearing times and poor planning by us, we&apos;ve had multiple week long outages in withdrawals. While we do store funds 1:1, these &amp;quot;outages&amp;quot; happen because we primarily store creator funds in an FDIC-insured bank account, as we wouldn&apos;t want a PayPal/Tremendous account suspension to cause creators to lose funds. We&apos;ve now set up internal reporting which should never cause this to happen again (or, if it does, drastically reduce the time payout outages happen)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Platform Revenue Route&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Due to some unannounced breaking changes in Aditude&apos;s API, the platform revenue API was broken. It is now &lt;a href=&quot;https://api.modrinth.com/v3/payout/platform_revenue&quot; rel=&quot;noopener nofollow ugc&quot;&gt;working&lt;/a&gt;. You can also use &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt; fields to filter any date range!&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;API and Uptime&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;We&apos;ve migrated our infrastructure for the website, app, and servers to OVH over our existing non-redundant AWS system. We&apos;ve hit 99.96% uptime on our API and 99.98% on Modrinth Servers!&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Thank you all for your patience! If you are having any more issues or have any questions about all of this, feel free to DM @geometrically on Discord or &lt;a href=&quot;https://support.modrinth.com&quot; rel=&quot;noopener nofollow ugc&quot;&gt;start a support chat&lt;/a&gt; and we will be happy to help!&lt;/p&gt;]]&gt;</content:encoded>
</item>
<item>

View File

@@ -1,6 +1,6 @@
[package]
name = "theseus"
version = "0.10.1"
version = "1.0.0-local" # The actual version is set by the theseus-build workflow on tagging
authors = ["Jai A <jaiagr+gpg@pm.me>"]
edition.workspace = true

View File

@@ -2,6 +2,7 @@ package com.modrinth.theseus;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
@@ -76,14 +77,14 @@ public final class MinecraftLaunch {
Object thisObject = null;
if (!Modifier.isStatic(mainMethod.getModifiers())) {
thisObject = mainClass.getDeclaredConstructor().newInstance();
thisObject = forceAccessible(mainClass.getDeclaredConstructor()).newInstance();
}
final Object[] parameters = mainMethod.getParameterCount() > 0 ? new Object[] {args} : new Object[] {};
mainMethod.invoke(thisObject, parameters);
} else {
findSimpleMainMethod(mainClass).invoke(null, new Object[] {args});
forceAccessible(findSimpleMainMethod(mainClass)).invoke(null, new Object[] {args});
}
}
@@ -115,4 +116,15 @@ public final class MinecraftLaunch {
private static Method findSimpleMainMethod(Class<?> mainClass) throws NoSuchMethodException {
return mainClass.getMethod("main", String[].class);
}
private static <T extends AccessibleObject> T forceAccessible(T object) throws ReflectiveOperationException {
try {
final Method setAccessible0 = AccessibleObject.class.getDeclaredMethod("setAccessible0", boolean.class);
setAccessible0.setAccessible(true);
setAccessible0.invoke(object, true);
} catch (NoSuchMethodException e) {
object.setAccessible(true);
}
return object;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -622,6 +622,12 @@ pub async fn launch_minecraft(
.into_iter(),
);
// The java launcher requires access to java.lang.reflect in order to force access in to
// whatever module the main class is in
if java_version.parsed_version >= 9 {
command.arg("--add-opens=java.base/java.lang.reflect=ALL-UNNAMED");
}
// The java launcher code requires internal JDK code in Java 25+ in order to support JEP 512
if java_version.parsed_version >= 25 {
command.arg("--add-opens=jdk.internal/jdk.internal.misc=ALL-UNNAMED");

View File

@@ -3,6 +3,7 @@ use sqlx::sqlite::{
SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions,
};
use sqlx::{Pool, Sqlite};
use tokio::time::Instant;
use std::str::FromStr;
use std::time::Duration;
@@ -17,8 +18,10 @@ pub(crate) async fn connect() -> crate::Result<Pool<Sqlite>> {
crate::util::io::create_dir_all(&settings_dir).await?;
}
let uri = format!("sqlite:{}", settings_dir.join("app.db").display());
let db_path = settings_dir.join("app.db");
let db_exists = db_path.exists();
let uri = format!("sqlite:{}", db_path.display());
let conn_options = SqliteConnectOptions::from_str(&uri)?
.busy_timeout(Duration::from_secs(30))
.journal_mode(SqliteJournalMode::Wal)
@@ -30,8 +33,16 @@ pub(crate) async fn connect() -> crate::Result<Pool<Sqlite>> {
.connect_with(conn_options)
.await?;
if db_exists {
fix_modrinth_issued_migrations(&pool).await?;
}
sqlx::migrate!().run(&pool).await?;
if !db_exists {
fix_modrinth_issued_migrations(&pool).await?;
}
if let Err(err) = stale_data_cleanup(&pool).await {
tracing::warn!(
"Failed to clean up stale data from state database: {err}"
@@ -62,3 +73,63 @@ async fn stale_data_cleanup(pool: &Pool<Sqlite>) -> crate::Result<()> {
Ok(())
}
/*
// Patch by AstralRinth - 08.07.2025
Problem files:
/packages/app-lib/migrations/20240711194701_init.sql !eol
/packages/app-lib/migrations/20240813205023_drop-active-unique.sql !eol
/packages/app-lib/migrations/20240930001852_disable-personalized-ads.sql !eol
/packages/app-lib/migrations/20241222013857_feature-flags.sql !eol
*/
async fn fix_modrinth_issued_migrations(
pool: &Pool<Sqlite>,
) -> crate::Result<()> {
let started = Instant::now();
tracing::info!("Fixing modrinth issued migrations");
sqlx::query(
r#"
UPDATE "_sqlx_migrations"
SET checksum = X'e973512979feac07e415405291eefafc1ef0bd89454958ad66f5452c381db8679c20ffadab55194ecf6ba8ec4ca2db21'
WHERE version = '20240711194701';
"#,
)
.execute(pool)
.await?;
tracing::info!("⚙️ Fixed first migration");
sqlx::query(
r#"
UPDATE "_sqlx_migrations"
SET checksum = X'5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206'
WHERE version = '20240813205023';
"#,
)
.execute(pool)
.await?;
tracing::info!("⚙️ Fixed second migration");
sqlx::query(
r#"
UPDATE "_sqlx_migrations"
SET checksum = X'c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57'
WHERE version = '20240930001852';
"#,
)
.execute(pool)
.await?;
tracing::info!("⚙️ Fixed third migration");
sqlx::query(
r#"
UPDATE "_sqlx_migrations"
SET checksum = X'c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704'
WHERE version = '20241222013857';
"#,
)
.execute(pool)
.await?;
tracing::info!("⚙️ Fixed fourth migration");
let elapsed = started.elapsed();
tracing::info!(
"✅ Fixed all known modrinth-issued migrations in {:.2?}",
elapsed
);
Ok(())
}

View File

@@ -1,7 +1,7 @@
module.exports = {
root: true,
extends: ['custom/library'],
ignorePatterns: ['**/*.scss', '**/*.svg', 'node_modules/', 'dist/'],
ignorePatterns: ['**/*.scss', '**/*.svg', 'node_modules/', 'dist/', '**/*.gltf'],
env: {
node: true,
},

View File

@@ -9,3 +9,8 @@ declare module '*.webp' {
const src: string
export default src
}
declare module '*?url' {
const src: string
export default src
}

View File

@@ -97,4 +97,8 @@ export const MicrosoftIcon = _MicrosoftIcon
export const PirateShipIcon = _PirateShipIcon
export const AstralRinthLogo = _AstralRinthLogo
export { default as CapeModel } from './models/cape.gltf?url'
export { default as ClassicPlayerModel } from './models/classic-player.gltf?url'
export { default as SlimPlayerModel } from './models/slim-player.gltf?url'
export * from './generated-icons'

View File

@@ -0,0 +1,92 @@
{
"asset": { "version": "2.0", "generator": "Blockbench 4.12.4 glTF exporter" },
"scenes": [{ "nodes": [1], "name": "blockbench_export" }],
"scene": 0,
"nodes": [
{
"rotation": [0, 0, 0.19509032201612825, 0.9807852804032304],
"translation": [0.15625, 1, 0],
"name": "Cape",
"mesh": 0
},
{ "children": [0] }
],
"bufferViews": [
{ "buffer": 0, "byteOffset": 0, "byteLength": 288, "target": 34962, "byteStride": 12 },
{ "buffer": 0, "byteOffset": 288, "byteLength": 288, "target": 34962, "byteStride": 12 },
{ "buffer": 0, "byteOffset": 576, "byteLength": 192, "target": 34962, "byteStride": 8 },
{ "buffer": 0, "byteOffset": 768, "byteLength": 72, "target": 34963 }
],
"buffers": [
{
"byteLength": 840,
"uri": "data:application/octet-stream;base64,AAAAPQAAAAAAAKA+AAAAPQAAAAAAAKC+AAAAPQAAgL8AAKA+AAAAPQAAgL8AAKC+AAAAvQAAAAAAAKC+AAAAvQAAAAAAAKA+AAAAvQAAgL8AAKC+AAAAvQAAgL8AAKA+AAAAvQAAAAAAAKC+AAAAPQAAAAAAAKC+AAAAvQAAAAAAAKA+AAAAPQAAAAAAAKA+AAAAvQAAgL8AAKA+AAAAPQAAgL8AAKA+AAAAvQAAgL8AAKC+AAAAPQAAgL8AAKC+AAAAvQAAAAAAAKA+AAAAPQAAAAAAAKA+AAAAvQAAgL8AAKA+AAAAPQAAgL8AAKA+AAAAPQAAAAAAAKC+AAAAvQAAAAAAAKC+AAAAPQAAgL8AAKC+AAAAvQAAgL8AAKC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACAPAAAgD0AADA+AACAPQAAgDwAAAg/AAAwPgAACD8AAEA+AACAPQAAsD4AAIA9AABAPgAACD8AALA+AAAIPwAAgDwAAAA9AACAPAAAgD0AADA+AAAAPQAAMD4AAIA9AAAwPgAAAD0AAKg+AAAAPQAAMD4AAAAAAACoPgAAAAAAAEA+AACAPQAAMD4AAIA9AABAPgAACD8AADA+AAAIPwAAAAAAAIA9AACAPAAAgD0AAAAAAAAIPwAAgDwAAAg/AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUA"
}
],
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 24,
"max": [0.03125, 0, 0.3125],
"min": [-0.03125, -1, -0.3125],
"type": "VEC3"
},
{
"bufferView": 1,
"componentType": 5126,
"count": 24,
"max": [1, 1, 1],
"min": [-1, -1, -1],
"type": "VEC3"
},
{
"bufferView": 2,
"componentType": 5126,
"count": 24,
"max": [0.34375, 0.53125],
"min": [0, 0],
"type": "VEC2"
},
{
"bufferView": 3,
"componentType": 5123,
"count": 36,
"max": [23],
"min": [0],
"type": "SCALAR"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"metallicFactor": 0,
"roughnessFactor": 1,
"baseColorTexture": { "index": 0 }
},
"alphaMode": "MASK",
"alphaCutoff": 0.05,
"doubleSided": true
}
],
"textures": [{ "sampler": 0, "source": 0, "name": "cape.png" }],
"samplers": [{ "magFilter": 9728, "minFilter": 9728, "wrapS": 33071, "wrapT": 33071 }],
"images": [
{
"mimeType": "image/png",
"uri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAgCAYAAACinX6EAAAAAXNSR0IArs4c6QAABCRJREFUaEPtlktoE1EUhm/sIzFpYsBgHyn2oRZt0C5ESxWFYutKNyKIWBBR6kpBd3Ur2J0FRdCiuKmIIG4qLnzgQqTUXSlVqdoXTU0lljRpYtOHI/8dznBnOm1mphMwJWeTzJ0795zznf/cex2MMXam2S/ht38siJ8V1lgd5mOBARerc3rZnmK37rwvCyk2nE6wezMRh+4EncH9B1ql+fkU64rPsaBz5bqh4T7Daxn1Kc5zVNeEePJIci0Aq71bzenY6JChwAnA0OBHQ/OtJLnWNwqAy61R1tO3k891ueRKoDKwtqbv7MGbgCnfRgHQoqG9hyX4hc/yiho+/HNqVPFtdj2jwTpQgd/RKT7/0ZUku/o4qAJw50KYXbzr4e+3BioYzc3kwGzAUCLWFw2+UBiCb3bNTDHiPQeAP3CGNmg/6VcSBpDu3hhvDQpOCwABwrQKsRIsqYAChy/EQAXwlPiZ3a3igNPkXIyTjr8o5L7PPdzGf59c+sV/faeWeIIIAHPJ8M3kc7l1K09LKghWAIgqoPZ7djPFTlxb4D6yAgBOQfntrUVWVeRk44tplXJorOVGkVIJTBCTpw9ECFYBIEkyMfmsAXh3u1qi5MlxbbGX/x1ZSCjBAAwgfPr6h49R5UVavk0FXC2wju5p07s6iqEF0PtqSlEf+bKzDRwdgaDU7AkoySJ5Oo/D6ZRq/H0yyuJ/lxkSheG/FgCNm7kL0BoEAG32sqtY1YIHd2/mGzTMVgD3y2slqjgW9xY6ma9AThAARIMiBtOpjADwTWc0bEkB+FZsSTxTW0KBsGPXx0yvrUpEeHAAQIM7wBJLcu8DwHaP/H8i6VSND6SiSjBlRW4WWUypVIBbIgzjVgB0tpdKqLReS0KVPTMTfH0ra2cEgAmAAEd+l1z52LybqwBQYAQAyZPh6gtDW4jjuC4fHx8wXSm0JDbeI95SRYHiFZlUaWVtPQiKAugl5E8ASAUYiy8vc0C474uGasPE5PHc4g0wK/f4obom6UNimol7kTZwQLANwOuqBokqDEeQf4lfvvnNxZJcBTBAGZplWQcQ3tcgwY9oWlXinRW4ugoAgNAWWe6ocn1QvgyRTUb4RZFVljlY/3hSBYCqb6cCZo8ekuATVRZPI/gQW8FWAI1VHganVgDQUajdA6y2gAgAcZFBjTBSpG0AcAqc3VVmCMDTbxGWZvIRCaMNkJ7pFMCzVQD4liCQ8kRFUlvaBkCvL+wYw2ZmNUgCgLajFhRPJlv3ADuS1VtjPQCk823S574ffN/x1dQqy8dHR5SN2Spcbaymz2mjwNYDAD4IQn3TDpVLQIAqNjwAANRrAdoI/3sAOF7Xe1khCNQGVH1bL0JGJb1R52VtD8gVYHkAuVKpbMWZV0C2yObKunkF5EqlshVnXgHZIpsr6/4DlbxcPydnT74AAAAASUVORK5CYII="
}
],
"meshes": [
{
"primitives": [
{
"mode": 4,
"attributes": { "POSITION": 0, "NORMAL": 1, "TEXCOORD_0": 2 },
"indices": 3,
"material": 0
}
]
}
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
---
title: 'Skins — Now in Modrinth App!'
summary: 'Customize your look, save your favorite skins, and swap them out in a flash, all within Modrinth App.'
date: 2025-07-06T16:45:00-07:00
---
We're thrilled to roll out Modrinth App **v0.10** with a beta release of one of our most highly requested features, the **Skins page**. The Skins page allows you to manage all of your Minecraft skins directly within Modrinth App. You can see all your saved custom skins and the default Minecraft skins in one convenient place.
![The new skins page, featuring a cute animated player model, your custom skins & default skins.](./skins-page.webp)
Adding a new skin is simple, even Herobrine could do it! When you add or edit a skin, you can **upload** your custom texture file directly from your computer, **choose** between the wide or slim arm style to match your preferred character model, and even **assign** a specific cape to that look for the perfect finishing touch.
The interface makes it easy to preview your changes in real-time with the animated player model, so you can see exactly how your skin will look in-game before saving it.
![The edit skin modal that shows when you go to add or edit a skin.](./edit-skin.webp)
## Fixes and More!
Alongside this major new feature, **v0.10** includes a host of improvements and bug fixes to make your experience smoother. We've updated the news feed to use our new system, fixed issues with project descriptions, and tidied up how data is handled. For a full breakdown of all the changes, you can [check out the complete changelog here.](https://modrinth.com/news/changelog?filter=app)
As the skins feature is in _beta_, we're eager to hear your feedback! **Jump in, give it a try**, and let us know what you think. You can share your thoughts on our [Discord server](https://discord.modrinth.com/) or [start a support chat](https://support.modrinth.com) if you're running into issues.
Thank you! We can't wait to see your skins in action. Happy customizing!

View File

@@ -20,6 +20,7 @@ import { article as new_site_beta } from './new_site_beta'
import { article as plugins_resource_packs } from './plugins_resource_packs'
import { article as pride_campaign_2025 } from './pride_campaign_2025'
import { article as redesign } from './redesign'
import { article as skins_now_in_modrinth_app } from './skins_now_in_modrinth_app'
import { article as two_years_of_modrinth_history } from './two_years_of_modrinth_history'
import { article as two_years_of_modrinth } from './two_years_of_modrinth'
import { article as whats_modrinth } from './whats_modrinth'
@@ -47,6 +48,7 @@ export const articles = [
plugins_resource_packs,
pride_campaign_2025,
redesign,
skins_now_in_modrinth_app,
two_years_of_modrinth_history,
two_years_of_modrinth,
whats_modrinth,

View File

@@ -0,0 +1,2 @@
// AUTO-GENERATED FILE - DO NOT EDIT
export const html = `<p>We're thrilled to roll out Modrinth App <strong>v0.10</strong> with a beta release of one of our most highly requested features, the <strong>Skins page</strong>. The Skins page allows you to manage all of your Minecraft skins directly within Modrinth App. You can see all your saved custom skins and the default Minecraft skins in one convenient place.</p><p><img src="./skins-page.webp" alt="The new skins page, featuring a cute animated player model, your custom skins &amp; default skins."></p><p>Adding a new skin is simple, even Herobrine could do it! When you add or edit a skin, you can <strong>upload</strong> your custom texture file directly from your computer, <strong>choose</strong> between the wide or slim arm style to match your preferred character model, and even <strong>assign</strong> a specific cape to that look for the perfect finishing touch.</p><p>The interface makes it easy to preview your changes in real-time with the animated player model, so you can see exactly how your skin will look in-game before saving it.</p><p><img src="./edit-skin.webp" alt="The edit skin modal that shows when you go to add or edit a skin."></p><h2>Fixes and More!</h2><p>Alongside this major new feature, <strong>v0.10</strong> includes a host of improvements and bug fixes to make your experience smoother. We've updated the news feed to use our new system, fixed issues with project descriptions, and tidied up how data is handled. For a full breakdown of all the changes, you can <a href="https://modrinth.com/news/changelog?filter=app">check out the complete changelog here.</a></p><p>As the skins feature is in <em>beta</em>, we're eager to hear your feedback! <strong>Jump in, give it a try</strong>, and let us know what you think. You can share your thoughts on our <a href="https://discord.modrinth.com/" rel="noopener nofollow ugc">Discord server</a> or <a href="https://support.modrinth.com" rel="noopener nofollow ugc">start a support chat</a> if you're running into issues.</p><p>Thank you! We can't wait to see your skins in action. Happy customizing!</p>`

View File

@@ -0,0 +1,10 @@
// AUTO-GENERATED FILE - DO NOT EDIT
export const article = {
html: () => import(`./skins_now_in_modrinth_app.content`).then((m) => m.html),
title: 'Skins — Now in Modrinth App!',
summary:
'Customize your look, save your favorite skins, and swap them out in a flash, all within Modrinth App.',
date: '2025-07-06T23:45:00.000Z',
slug: 'skins-now-in-modrinth-app',
thumbnail: true,
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -121,6 +121,7 @@ import {
loadTexture as loadSkinTexture,
} from '@modrinth/utils'
import { useDynamicFontSize } from '../../composables'
import { CapeModel, ClassicPlayerModel, SlimPlayerModel } from '@modrinth/assets'
interface AnimationConfig {
baseAnimation: string
@@ -132,9 +133,6 @@ interface AnimationConfig {
const props = withDefaults(
defineProps<{
textureSrc: string
slimModelSrc: string
wideModelSrc: string
capeModelSrc?: string
capeSrc?: string
variant?: 'SLIM' | 'CLASSIC' | 'UNKNOWN'
nametag?: string
@@ -149,7 +147,6 @@ const props = withDefaults(
antialias: false,
scale: 1,
fov: 40,
capeModelSrc: '',
capeSrc: undefined,
initialRotation: 15.75,
nametag: undefined,
@@ -176,7 +173,7 @@ const { fontSize: nametagFontSize } = useDynamicFontSize({
})
const selectedModelSrc = computed(() =>
props.variant === 'SLIM' ? props.slimModelSrc : props.wideModelSrc,
props.variant === 'SLIM' ? SlimPlayerModel : ClassicPlayerModel,
)
const scene = shallowRef<THREE.Object3D | null>(null)
@@ -421,14 +418,9 @@ async function loadModel(src: string) {
}
}
async function loadCape(src: string) {
if (!src) {
capeScene.value = null
return
}
async function loadCape() {
try {
const { scene: loadedCape } = await useGLTF(src)
const { scene: loadedCape } = await useGLTF(CapeModel)
capeScene.value = markRaw(loadedCape)
applyCapeTexture(capeScene.value, capeTexture.value, transparentTexture)
@@ -581,10 +573,6 @@ watch(capeScene, (newCapeScene) => {
})
watch(selectedModelSrc, (src) => loadModel(src))
watch(
() => props.capeModelSrc,
(src) => src && loadCape(src),
)
watch(
() => props.textureSrc,
async (newSrc) => {
@@ -599,6 +587,7 @@ watch(
watch(
() => props.capeSrc,
async (newCapeSrc) => {
await loadCape()
await loadAndApplyCapeTexture(newCapeSrc)
},
)
@@ -631,9 +620,7 @@ onBeforeMount(async () => {
await loadAndApplyCapeTexture(props.capeSrc)
}
if (props.capeModelSrc) {
await loadCape(props.capeModelSrc)
}
await loadCape()
} catch (error) {
console.error('Failed to initialize skin preview:', error)
}

View File

@@ -10,6 +10,30 @@ export type VersionEntry = {
}
const VERSIONS: VersionEntry[] = [
{
date: `2025-07-07T01:10:00-07:00`,
product: 'app',
version: `0.10.3`,
body: `### Improvements
- Added a workaround for Java 8 instances failing to load.
### Known issues
- Java installations will show as 'Failed' when you test them. This is a visual bug, and does not mean the Java installation is not working.`,
},
{
date: `2025-07-06T16:30:00-07:00`,
product: 'app',
version: `0.10.2`,
body: `### Improvements
- Added additional default skins from free official Minecraft skin packs.
- Fixed some parts of the player model on Skins page rendering incorrectly.
- Fixed a number of issues with skin images not loading on macOS.
- Fixed old Forge versions not loading properly.
- Fixed a typo in Appearance settings for hiding Skins page nametag.
### Known issues
- Java installations will show as 'Failed' when you test them. This is a visual bug, and does not mean the Java installation is not working.`,
},
{
date: `2025-07-05T12:00:00-07:00`,
product: 'app',
@@ -25,7 +49,7 @@ const VERSIONS: VersionEntry[] = [
date: `2025-07-04T12:00:00-07:00`,
product: 'app',
version: `0.10.0`,
body: `**Note: This update was pulled due to issues.**
body: `**Note: This update is no longer available to download due to issues, you should use v0.10.1**
### Added
- Added Skins page as a beta feature. There may be some minor bugs with it, but we'd love to get user feedback on this feature as it's been one of our most highly requested features.

View File

@@ -0,0 +1,103 @@
diff --git a/packages/app-lib/src/state/db.rs b/packages/app-lib/src/state/db.rs
index 14c53d81..607a345f 100644
--- a/packages/app-lib/src/state/db.rs
+++ b/packages/app-lib/src/state/db.rs
@@ -3,6 +3,7 @@ use sqlx::sqlite::{
SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions,
};
use sqlx::{Pool, Sqlite};
+use tokio::time::Instant;
use std::str::FromStr;
use std::time::Duration;
@@ -17,8 +18,10 @@ pub(crate) async fn connect() -> crate::Result<Pool<Sqlite>> {
crate::util::io::create_dir_all(&settings_dir).await?;
}
- let uri = format!("sqlite:{}", settings_dir.join("app.db").display());
+ let db_path = settings_dir.join("app.db");
+ let db_exists = db_path.exists();
+ let uri = format!("sqlite:{}", db_path.display());
let conn_options = SqliteConnectOptions::from_str(&uri)?
.busy_timeout(Duration::from_secs(30))
.journal_mode(SqliteJournalMode::Wal)
@@ -30,10 +33,16 @@ pub(crate) async fn connect() -> crate::Result<Pool<Sqlite>> {
.connect_with(conn_options)
.await?;
- fix_migration_20240711194701(&pool).await?; // Patch by AstralRinth - 08.07.2025
+ if db_exists {
+ fix_modrinth_issued_migrations(&pool).await?;
+ }
sqlx::migrate!().run(&pool).await?;
+ if !db_exists {
+ fix_modrinth_issued_migrations(&pool).await?;
+ }
+
if let Err(err) = stale_data_cleanup(&pool).await {
tracing::warn!(
"Failed to clean up stale data from state database: {err}"
@@ -66,8 +75,17 @@ async fn stale_data_cleanup(pool: &Pool<Sqlite>) -> crate::Result<()> {
}
/*
// Patch by AstralRinth - 08.07.2025
+Problem files:
+/packages/app-lib/migrations/20240711194701_init.sql !eol
+/packages/app-lib/migrations/20240813205023_drop-active-unique.sql !eol
+/packages/app-lib/migrations/20240930001852_disable-personalized-ads.sql !eol
+/packages/app-lib/migrations/20241222013857_feature-flags.sql !eol
*/
-async fn fix_migration_20240711194701(pool: &Pool<Sqlite>) -> crate::Result<()> {
+async fn fix_modrinth_issued_migrations(
+ pool: &Pool<Sqlite>,
+) -> crate::Result<()> {
+ let started = Instant::now();
+ tracing::info!("Fixing modrinth issued migrations");
sqlx::query(
r#"
UPDATE "_sqlx_migrations"
@@ -77,5 +95,41 @@ async fn fix_migration_20240711194701(pool: &Pool<Sqlite>) -> crate::Result<()>
)
.execute(pool)
.await?;
+ tracing::info!("⚙️ Fixed first migration");
+ sqlx::query(
+ r#"
+ UPDATE "_sqlx_migrations"
+ SET checksum = X'5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206'
+ WHERE version = '20240813205023';
+ "#,
+ )
+ .execute(pool)
+ .await?;
+ tracing::info!("⚙️ Fixed second migration");
+ sqlx::query(
+ r#"
+ UPDATE "_sqlx_migrations"
+ SET checksum = X'c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57'
+ WHERE version = '20240930001852';
+ "#,
+ )
+ .execute(pool)
+ .await?;
+ tracing::info!("⚙️ Fixed third migration");
+ sqlx::query(
+ r#"
+ UPDATE "_sqlx_migrations"
+ SET checksum = X'c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704'
+ WHERE version = '20241222013857';
+ "#,
+ )
+ .execute(pool)
+ .await?;
+ tracing::info!("⚙️ Fixed fourth migration");
+ let elapsed = started.elapsed();
+ tracing::info!(
+ "✅ Fixed all known modrinth-issued migrations in {:.2?}",
+ elapsed
+ );
Ok(())
}