13 Commits

Author SHA1 Message Date
ce03668252 feat: internationalization 2025-04-22 21:34:55 +00:00
58c692a879 chore: bump ver 2025-04-21 22:08:30 +00:00
e569d57788 feat: hex patches 2025-04-21 22:05:37 +00:00
b75cc8f240 docs: update README.md 2025-04-21 04:19:12 -12:00
407b34a884 feat: profile imports/exports 2025-04-21 04:15:52 -12:00
890d26e883 chore: bump ver 2025-04-20 06:38:44 +00:00
2aff5834b9 fix: chunithm crashing with mempatcher 2025-04-20 06:37:46 +00:00
69f2c83109 chore: update CHANGELOG.md 2025-04-19 20:13:11 +00:00
dbbd80c6c3 feat: add 'games' to the manifest 2025-04-19 20:09:32 +00:00
3479804dca feat: 0.12 update 2025-04-19 19:48:08 +00:00
aaeed669df chore: bump ver 2025-04-19 11:46:07 +00:00
7084f40404 fix: improve help pages 2025-04-19 11:44:16 +00:00
f7e9d7d7db docs: rewrite README.md 2025-04-18 19:55:42 +00:00
52 changed files with 1875 additions and 659 deletions

View File

@ -1,3 +1,44 @@
## 0.15.0
- Added internationalization
- Moved some client options to the home page
- Added 'dll-game32' and 'dll-game64' entries for native mods that target both Chunithm and Ongeki
- Ongeki: added mempatcher support (amdaemon)
## 0.14.0
- Added the custom FREE PLAY patch for Verse
## 0.13.0
- Added profile imports/exports
- Fixed error when trying to open an empty Ongeki profile
- Switched the default color scheme from invisible to purple
## 0.12.1
- Chunithm: fixed crash when using mempatcher
## 0.12.0
- Ongeki: cache and mu3.ini config are now split per-profile (requires mu3-mods 3.7+)
- Ongeki: added the few config options of mu3.ini that aren't available in TestMenuConfig, or require a restart
- Chunithm: added Lumi+ patches
- Added support for a non-standard `games` manifest entry intended for local packages
- Expected to be an array containing "ongeki", "chunithm" or both
- Example: { "games": ["ongeki"] }
- Added a button linking to the profile config folder
- Fixed the button linking to the data folder showing up when the folder does not exist
- Uninstalled tool packages are no longer automatically deselected, as that caused issues
## 0.11.1
- Improved help pages
## 0.11.0
- Added help pages
## 0.10.1
- Fixed the order of cells in the CHUNITHM keyboard

View File

@ -1,17 +1,24 @@
Looking for
- maimai DX players willing to help develop/test maimai DX support
- translators (any language other than English)
# STARTLINER
A simple and easy to use launcher, configuration tool and mod manager
for O.N.G.E.K.I. and CHUNITHM, using [Rainycolor Watercolor](https://rainy.patafour.zip).
This is a program that seeks to streamline game data configuration, currently supporting O.N.G.E.K.I. and CHUNITHM.
STARTLINER is four things:
- a mod installer and updater, powered by [Rainycolor Watercolor](https://rainy.patafour.zip),
- a configuration GUI for segatools,
- a glorified `start.bat` clicker, with automatic monitor setup and rollback,
- [an abstraction allowing data configuration without touching the game directory](https://gitea.tendokyu.moe/akanyan/STARTLINER/wiki/Architecture-details).
STARTLINER's core design principle is to modify, configure and launch games without tampering with them.
This makes it possible to keep data cleaner than ever, and to have several configurations pointing at the same data.
Made with Rust (Tauri) and Vue. Technically multiplatform. Contributions welcome.
## Features
- [Clean](https://gitea.tendokyu.moe/akanyan/STARTLINER/wiki/Architecture-details) data modding
- Segatools configuration
- Monitor configuration with automatic rollback
- Support for multiple configurations pointing at the same data
## Usage
Download a prebuilt binary from [Releases](https://gitea.tendokyu.moe/akanyan/STARTLINER/releases) or build it yourself:

View File

@ -1,5 +1,6 @@
### Short-term
- i18n
- https://gitea.tendokyu.moe/TeamTofuShop/segatools/issues/63
### Long-term

231
bun.lock
View File

@ -8,15 +8,15 @@
"@mdi/font": "7.4.47",
"@primevue/forms": "^4.3.3",
"@primevue/themes": "^4.3.3",
"@tailwindcss/vite": "^4.1.3",
"@tauri-apps/api": "^2.4.1",
"@tailwindcss/vite": "^4.1.4",
"@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-cli": "^2.2.0",
"@tauri-apps/plugin-deep-link": "~2.2.1",
"@tauri-apps/plugin-dialog": "~2.2.1",
"@tauri-apps/plugin-fs": "^2.2.1",
"@tauri-apps/plugin-opener": "^2.2.6",
"@tauri-apps/plugin-shell": "~2.2.1",
"@tauri-apps/plugin-updater": "^2.7.0",
"@tauri-apps/plugin-updater": "^2.7.1",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/markdown-it": "^14.1.2",
"markdown-it": "^14.1.0",
@ -24,13 +24,14 @@
"primeicons": "^7.0.0",
"primevue": "^4.3.3",
"roboto-fontface": "^0.10.0",
"tailwindcss": "^4.1.3",
"tailwindcss": "^4.1.4",
"tailwindcss-primeui": "^0.4.0",
"vue": "^3.5.13",
"vuetify": "^3.8.1",
"vue-i18n": "^11.1.3",
"vuetify": "^3.8.2",
},
"devDependencies": {
"@tauri-apps/cli": "^2.4.1",
"@tauri-apps/cli": "^2.5.0",
"@tsconfig/node22": "^22.0.1",
"@types/node": "^22.14.1",
"@vitejs/plugin-vue": "^5.2.3",
@ -38,11 +39,11 @@
"@vue/tsconfig": "^0.5.1",
"npm-run-all2": "^7.0.2",
"sass": "1.77.8",
"sass-embedded": "^1.86.3",
"sass-embedded": "^1.87.0",
"typescript": "^5.8.3",
"unplugin-fonts": "^1.3.1",
"unplugin-vue-components": "^0.27.5",
"vite": "^6.2.6",
"vite": "^6.3.2",
"vite-plugin-vuetify": "^2.1.1",
"vue-tsc": "^2.2.8",
},
@ -145,6 +146,12 @@
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.1", "", {}, "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA=="],
"@intlify/core-base": ["@intlify/core-base@11.1.3", "", { "dependencies": { "@intlify/message-compiler": "11.1.3", "@intlify/shared": "11.1.3" } }, "sha512-cMuHunYO7LE80azTitcvEbs1KJmtd6g7I5pxlApV3Jo547zdO3h31/0uXpqHc+Y3RKt1wo2y68RGSx77Z1klyA=="],
"@intlify/message-compiler": ["@intlify/message-compiler@11.1.3", "", { "dependencies": { "@intlify/shared": "11.1.3", "source-map-js": "^1.0.2" } }, "sha512-7rbqqpo2f5+tIcwZTAG/Ooy9C8NDVwfDkvSeDPWUPQW+Dyzfw2o9H103N5lKBxO7wxX9dgCDjQ8Umz73uYw3hw=="],
"@intlify/shared": ["@intlify/shared@11.1.3", "", {}, "sha512-pTFBgqa/99JRA2H1qfyqv97MKWJrYngXBA/I0elZcYxvJgcCw3mApAoPW3mJ7vx3j+Ti0FyKUFZ4hWxdjKaxvA=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
@ -183,97 +190,101 @@
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.34.6", "", { "os": "android", "cpu": "arm" }, "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.0", "", { "os": "android", "cpu": "arm" }, "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.34.6", "", { "os": "android", "cpu": "arm64" }, "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.0", "", { "os": "android", "cpu": "arm64" }, "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.34.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.34.6", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.34.6", "", { "os": "freebsd", "cpu": "x64" }, "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.34.6", "", { "os": "linux", "cpu": "arm" }, "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.40.0", "", { "os": "linux", "cpu": "arm" }, "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.34.6", "", { "os": "linux", "cpu": "arm" }, "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.40.0", "", { "os": "linux", "cpu": "arm" }, "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.34.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.34.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ=="],
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.34.6", "", { "os": "linux", "cpu": "none" }, "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw=="],
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg=="],
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.34.6", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ=="],
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.34.6", "", { "os": "linux", "cpu": "none" }, "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.34.6", "", { "os": "linux", "cpu": "s390x" }, "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.34.6", "", { "os": "linux", "cpu": "x64" }, "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.40.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.34.6", "", { "os": "linux", "cpu": "x64" }, "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.34.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.34.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.6", "", { "os": "win32", "cpu": "x64" }, "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.40.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.3", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.29.2", "tailwindcss": "4.1.3" } }, "sha512-H/6r6IPFJkCfBJZ2dKZiPJ7Ueb2wbL592+9bQEl2r73qbX6yGnmQVIfiUvDRB2YI0a3PWDrzUwkvQx1XW1bNkA=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.0", "", { "os": "win32", "cpu": "x64" }, "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.3", "@tailwindcss/oxide-darwin-arm64": "4.1.3", "@tailwindcss/oxide-darwin-x64": "4.1.3", "@tailwindcss/oxide-freebsd-x64": "4.1.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.3", "@tailwindcss/oxide-linux-arm64-musl": "4.1.3", "@tailwindcss/oxide-linux-x64-gnu": "4.1.3", "@tailwindcss/oxide-linux-x64-musl": "4.1.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.3", "@tailwindcss/oxide-win32-x64-msvc": "4.1.3" } }, "sha512-t16lpHCU7LBxDe/8dCj9ntyNpXaSTAgxWm1u2XQP5NiIu4KGSyrDJJRlK9hJ4U9yJxx0UKCVI67MJWFNll5mOQ=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.4", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.29.2", "tailwindcss": "4.1.4" } }, "sha512-MT5118zaiO6x6hNA04OWInuAiP1YISXql8Z+/Y8iisV5nuhM8VXlyhRuqc2PEviPszcXI66W44bCIk500Oolhw=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.3", "", { "os": "android", "cpu": "arm64" }, "sha512-cxklKjtNLwFl3mDYw4XpEfBY+G8ssSg9ADL4Wm6//5woi3XGqlxFsnV5Zb6v07dxw1NvEX2uoqsxO/zWQsgR+g=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.4", "@tailwindcss/oxide-darwin-arm64": "4.1.4", "@tailwindcss/oxide-darwin-x64": "4.1.4", "@tailwindcss/oxide-freebsd-x64": "4.1.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.4", "@tailwindcss/oxide-linux-arm64-musl": "4.1.4", "@tailwindcss/oxide-linux-x64-gnu": "4.1.4", "@tailwindcss/oxide-linux-x64-musl": "4.1.4", "@tailwindcss/oxide-wasm32-wasi": "4.1.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.4", "@tailwindcss/oxide-win32-x64-msvc": "4.1.4" } }, "sha512-p5wOpXyOJx7mKh5MXh5oKk+kqcz8T+bA3z/5VWWeQwFrmuBItGwz8Y2CHk/sJ+dNb9B0nYFfn0rj/cKHZyjahQ=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mqkf2tLR5VCrjBvuRDwzKNShRu99gCAVMkVsaEOFvv6cCjlEKXRecPu9DEnxp6STk5z+Vlbh1M5zY3nQCXMXhw=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.4", "", { "os": "android", "cpu": "arm64" }, "sha512-xMMAe/SaCN/vHfQYui3fqaBDEXMu22BVwQ33veLc8ep+DNy7CWN52L+TTG9y1K397w9nkzv+Mw+mZWISiqhmlA=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-7sGraGaWzXvCLyxrc7d+CCpUN3fYnkkcso3rCzwUmo/LteAl2ZGCDlGvDD8Y/1D3ngxT8KgDj1DSwOnNewKhmg=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JGRj0SYFuDuAGilWFBlshcexev2hOKfNkoX+0QTksKYq2zgF9VY/vVMq9m8IObYnLna0Xlg+ytCi2FN2rOL0Sg=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-E2+PbcbzIReaAYZe997wb9rId246yDkCwAakllAWSGqe6VTg9hHle67hfH6ExjpV2LSK/siRzBUs5wVff3RW9w=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sdDeLNvs3cYeWsEJ4H1DvjOzaGios4QbBTNLVLVs0XQ0V95bffT3+scptzYGPMjm7xv4+qMhCDrkHwhnUySEzA=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.3", "", { "os": "linux", "cpu": "arm" }, "sha512-GvfbJ8wjSSjbLFFE3UYz4Eh8i4L6GiEYqCtA8j2Zd2oXriPuom/Ah/64pg/szWycQpzRnbDiJozoxFU2oJZyfg=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-VHxAqxqdghM83HslPhRsNhHo91McsxRJaEnShJOMu8mHmEj9Ig7ToHJtDukkuLWLzLboh2XSjq/0zO6wgvykNA=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-35UkuCWQTeG9BHcBQXndDOrpsnt3Pj9NVIB4CgNiKmpG8GnCNXeMczkUpOoqcOhO6Cc/mM2W7kaQ/MTEENDDXg=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.4", "", { "os": "linux", "cpu": "arm" }, "sha512-OTU/m/eV4gQKxy9r5acuesqaymyeSCnsx1cFto/I1WhPmi5HDxX1nkzb8KYBiwkHIGg7CTfo/AcGzoXAJBxLfg=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-dm18aQiML5QCj9DQo7wMbt1Z2tl3Giht54uVR87a84X8qRtuXxUqnKQkRDK5B4bCOmcZ580lF9YcoMkbDYTXHQ=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-hKlLNvbmUC6z5g/J4H+Zx7f7w15whSVImokLPmP6ff1QqTVE+TxUM9PGuNsjHvkvlHUtGTdDnOvGNSEUiXI1Ww=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-LMdTmGe/NPtGOaOfV2HuO7w07jI3cflPrVq5CXl+2O93DCewADK0uW1ORNAcfu2YxDUS035eY2W38TxrsqngxA=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-aalNWwIi54bbFEizwl1/XpmdDrOaCjRFQRgtbv9slWjmNPuJJTIKPHf5/XXDARc9CneW9FkSTqTbyvNecYAEGw=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-PEj7XR4OGTGoboTIAdXicKuWl4EQIjKHKuR+bFy9oYN7CFZo0eu74+70O4XuERX4yjqVZGAkCdglBODlgqcCXg=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-v+mxVgH2kmur/X5Mdrz9m7TsoVjbdYQT0b4Z+dr+I4RvreCNXyCFELZL/DO0M1RsidZTrm6O1eMnV6zlgEzTMQ=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.3", "", { "os": "win32", "cpu": "x64" }, "sha512-T8gfxECWDBENotpw3HR9SmNiHC9AOJdxs+woasRZ8Q/J4VHN0OMs7F+4yVNZ9EVN26Wv6mZbK0jv7eHYuLJLwA=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.4", "", { "dependencies": { "@emnapi/core": "^1.4.0", "@emnapi/runtime": "^1.4.0", "@emnapi/wasi-threads": "^1.0.1", "@napi-rs/wasm-runtime": "^0.2.8", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-2TLe9ir+9esCf6Wm+lLWTMbgklIjiF0pbmDnwmhR9MksVOq+e8aP3TSsXySnBDDvTTVd/vKu1aNttEGj3P6l8Q=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.3", "", { "dependencies": { "@tailwindcss/node": "4.1.3", "@tailwindcss/oxide": "4.1.3", "tailwindcss": "4.1.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-lUI/QaDxLtlV52Lho6pu07CG9pSnRYLOPmKGIQjyHdTBagemc6HmgZxyjGAQ/5HMPrNeWBfTVIpQl0/jLXvWHQ=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-VlnhfilPlO0ltxW9/BgfLI5547PYzqBMPIzRrk4W7uupgCt8z6Trw/tAj6QUtF2om+1MH281Pg+HHUJoLesmng=="],
"@tauri-apps/api": ["@tauri-apps/api@2.4.1", "", {}, "sha512-5sYwZCSJb6PBGbBL4kt7CnE5HHbBqwH+ovmOW6ZVju3nX4E3JX6tt2kRklFEH7xMOIwR0btRkZktuLhKvyEQYg=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+7S63t5zhYjslUGb8NcgLpFXD+Kq1F/zt5Xv5qTv7HaFTG/DHyHD9GA6ieNAxhgyA4IcKa/zy7Xx4Oad2/wuhw=="],
"@tauri-apps/cli": ["@tauri-apps/cli@2.4.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.4.1", "@tauri-apps/cli-darwin-x64": "2.4.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.4.1", "@tauri-apps/cli-linux-arm64-gnu": "2.4.1", "@tauri-apps/cli-linux-arm64-musl": "2.4.1", "@tauri-apps/cli-linux-riscv64-gnu": "2.4.1", "@tauri-apps/cli-linux-x64-gnu": "2.4.1", "@tauri-apps/cli-linux-x64-musl": "2.4.1", "@tauri-apps/cli-win32-arm64-msvc": "2.4.1", "@tauri-apps/cli-win32-ia32-msvc": "2.4.1", "@tauri-apps/cli-win32-x64-msvc": "2.4.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-9Ta81jx9+57FhtU/mPIckDcOBtPTUdKM75t4+aA0X84b8Sclb0jy1xA8NplmcRzp2fsfIHNngU2NiRxsW5+yOQ=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.4", "", { "dependencies": { "@tailwindcss/node": "4.1.4", "@tailwindcss/oxide": "4.1.4", "tailwindcss": "4.1.4" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-4UQeMrONbvrsXKXXp/uxmdEN5JIJ9RkH7YVzs6AMxC/KC1+Np7WZBaNIco7TEjlkthqxZbt8pU/ipD+hKjm80A=="],
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.4.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QME7s8XQwy3LWClTVlIlwXVSLKkeJ/z88pr917Mtn9spYOjnBfsgHAgGdmpWD3NfJxjg7CtLbhH49DxoFL+hLg=="],
"@tauri-apps/api": ["@tauri-apps/api@2.5.0", "", {}, "sha512-Ldux4ip+HGAcPUmuLT8EIkk6yafl5vK0P0c0byzAKzxJh7vxelVtdPONjfgTm96PbN24yjZNESY8CKo8qniluA=="],
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.4.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/r89IcW6Ya1sEsFUEH7wLNruDTj7WmDWKGpPy7gATFtQr5JEY4heernqE82isjTUimnHZD8SCr0jA3NceI4ybw=="],
"@tauri-apps/cli": ["@tauri-apps/cli@2.5.0", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.5.0", "@tauri-apps/cli-darwin-x64": "2.5.0", "@tauri-apps/cli-linux-arm-gnueabihf": "2.5.0", "@tauri-apps/cli-linux-arm64-gnu": "2.5.0", "@tauri-apps/cli-linux-arm64-musl": "2.5.0", "@tauri-apps/cli-linux-riscv64-gnu": "2.5.0", "@tauri-apps/cli-linux-x64-gnu": "2.5.0", "@tauri-apps/cli-linux-x64-musl": "2.5.0", "@tauri-apps/cli-win32-arm64-msvc": "2.5.0", "@tauri-apps/cli-win32-ia32-msvc": "2.5.0", "@tauri-apps/cli-win32-x64-msvc": "2.5.0" }, "bin": { "tauri": "tauri.js" } }, "sha512-rAtHqG0Gh/IWLjN2zTf3nZqYqbo81oMbqop56rGTjrlWk9pTTAjkqOjSL9XQLIMZ3RbeVjveCqqCA0s8RnLdMg=="],
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.4.1", "", { "os": "linux", "cpu": "arm" }, "sha512-9tDijkRB+CchAGjXxYdY9l/XzFpLp1yihUtGXJz9eh+3qIoRI043n3e+6xmU8ZURr7XPnu+R4sCmXs6HD+NCEQ=="],
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.5.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VuVAeTFq86dfpoBDNYAdtQVLbP0+2EKCHIIhkaxjeoPARR0sLpFHz2zs0PcFU76e+KAaxtEtAJAXGNUc8E1PzQ=="],
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.4.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-pnFGDEXBAzS4iDYAVxTRhAzNu3K2XPGflYyBc0czfHDBXopqRgMyj5Q9Wj7HAwv6cM8BqzXINxnb2ZJFGmbSgA=="],
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.5.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-hUF01sC06cZVa8+I0/VtsHOk9BbO75rd+YdtHJ48xTdcYaQ5QIwL4yZz9OR1AKBTaUYhBam8UX9Pvd5V2/4Dpw=="],
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.4.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Hp0zXgeZNKmT+eoJSCxSBUm2QndNuRxR55tmIeNm3vbyUMJN/49uW7nurZ5fBPsacN4Pzwlx1dIMK+Gnr9A69w=="],
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.5.0", "", { "os": "linux", "cpu": "arm" }, "sha512-LQKqttsK252LlqYyX8R02MinUsfFcy3+NZiJwHFgi5Y3+ZUIAED9cSxJkyNtuY5KMnR4RlpgWyLv4P6akN1xhg=="],
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.4.1", "", { "os": "linux", "cpu": "none" }, "sha512-3T3bo2E4fdYRvzcXheWUeQOVB+LunEEi92iPRgOyuSVexVE4cmHYl+MPJF+EUV28Et0hIVTsHibmDO0/04lAFg=="],
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.5.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-mTQufsPcpdHg5RW0zypazMo4L55EfeE5snTzrPqbLX4yCK2qalN7+rnP8O8GT06xhp6ElSP/Ku1M2MR297SByQ=="],
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.4.1", "", { "os": "linux", "cpu": "x64" }, "sha512-kLN0FdNONO+2i+OpU9+mm6oTGufRC00e197TtwjpC0N6K2K8130w7Q3FeODIM2CMyg0ov3tH+QWqKW7GNhHFzg=="],
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.5.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-rQO1HhRUQqyEaal5dUVOQruTRda/TD36s9kv1hTxZiFuSq3558lsTjAcUEnMAtBcBkps20sbyTJNMT0AwYIk8Q=="],
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.4.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a8exvA5Ub9eg66a6hsMQKJIkf63QAf9OdiuFKOsEnKZkNN2x0NLgfvEcqdw88VY0UMs9dBoZ1AGbWMeYnLrLwQ=="],
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.5.0", "", { "os": "linux", "cpu": "none" }, "sha512-7oS18FN46yDxyw1zX/AxhLAd7T3GrLj3Ai6s8hZKd9qFVzrAn36ESL7d3G05s8wEtsJf26qjXnVF4qleS3dYsA=="],
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.4.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-4JFrslsMCJQG1c573T9uqQSAbF3j/tMKkMWzsIssv8jvPiP++OG61A2/F+y9te9/Q/O95cKhDK63kaiO5xQaeg=="],
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.5.0", "", { "os": "linux", "cpu": "x64" }, "sha512-SG5sFNL7VMmDBdIg3nO3EzNRT306HsiEQ0N90ILe3ZABYAVoPDO/ttpCO37ApLInTzrq/DLN+gOlC/mgZvLw1w=="],
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.4.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-9eXfFORehYSCRwxg2KodfmX/mhr50CI7wyBYGbPLePCjr5z0jK/9IyW6r0tC+ZVjwpX48dkk7hKiUgI25jHjzA=="],
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.5.0", "", { "os": "linux", "cpu": "x64" }, "sha512-QXDM8zp/6v05PNWju5ELsVwF0VH1n6b5pk2E6W/jFbbiwz80Vs1lACl9pv5kEHkrxBj+aWU/03JzGuIj2g3SkQ=="],
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.4.1", "", { "os": "win32", "cpu": "x64" }, "sha512-60a4Ov7Jrwqz2hzDltlS7301dhSAmM9dxo+IRBD3xz7yobKrgaHXYpWvnRomYItHcDd51VaKc9292H8/eE/gsw=="],
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.5.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-pFSHFK6b+o9y4Un8w0gGLwVyFTZaC3P0kQ7umRt/BLDkzD5RnQ4vBM7CF8BCU5nkwmEBUCZd7Wt3TWZxe41o6Q=="],
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.5.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-EArv1IaRlogdLAQyGlKmEqZqm5RfHCUMhJoedWu7GtdbOMUfSAz6FMX2boE1PtEmNO4An+g188flLeVErrxEKg=="],
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.5.0", "", { "os": "win32", "cpu": "x64" }, "sha512-lj43EFYbnAta8pd9JnUq87o+xRUR0odz+4rixBtTUwUgdRdwQ2V9CzFtsMu6FQKpFQ6mujRK6P1IEwhL6ADRsQ=="],
"@tauri-apps/plugin-cli": ["@tauri-apps/plugin-cli@2.2.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-rvNhMog9rHr01Xk+trBFKJ0eZICIvPkm9GX6ogB89/0hROU/lf+a/sb4vC0wtSeR7zrJuCSxwxYuvHCZheaYFA=="],
@ -287,7 +298,7 @@
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.2.1", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-G1GFYyWe/KlCsymuLiNImUgC8zGY0tI0Y3p8JgBCWduR5IEXlIJS+JuG1qtveitwYXlfJrsExt3enhv5l2/yhA=="],
"@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.7.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-oBug5UCH2wOsoYk0LW5LEMAT51mszjg11s8eungRH26x/qOrEjLvnuJJoxVVr9nsWowJ6vnpXKS+lUMfFTlvHQ=="],
"@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.7.1", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-1OPqEY/z7NDVSeTEMIhD2ss/vXWdpfZ5Th2Mk0KtPR/RA6FKuOTDGZQhxoyYBk0pcZJ+nNZUbl/IujDCLBApjA=="],
"@trivago/prettier-plugin-sort-imports": ["@trivago/prettier-plugin-sort-imports@5.2.2", "", { "dependencies": { "@babel/generator": "^7.26.5", "@babel/parser": "^7.26.7", "@babel/traverse": "^7.26.7", "@babel/types": "^7.26.7", "javascript-natural-sort": "^0.7.1", "lodash": "^4.17.21" }, "peerDependencies": { "@vue/compiler-sfc": "3.x", "prettier": "2.x - 3.x", "prettier-plugin-svelte": "3.x", "svelte": "4.x || 5.x" }, "optionalPeers": ["@vue/compiler-sfc", "prettier-plugin-svelte", "svelte"] }, "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA=="],
@ -461,6 +472,8 @@
"fastq": ["fastq@1.19.0", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA=="],
"fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="],
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
@ -653,7 +666,7 @@
"roboto-fontface": ["roboto-fontface@0.10.0", "", {}, "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="],
"rollup": ["rollup@4.34.6", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.6", "@rollup/rollup-android-arm64": "4.34.6", "@rollup/rollup-darwin-arm64": "4.34.6", "@rollup/rollup-darwin-x64": "4.34.6", "@rollup/rollup-freebsd-arm64": "4.34.6", "@rollup/rollup-freebsd-x64": "4.34.6", "@rollup/rollup-linux-arm-gnueabihf": "4.34.6", "@rollup/rollup-linux-arm-musleabihf": "4.34.6", "@rollup/rollup-linux-arm64-gnu": "4.34.6", "@rollup/rollup-linux-arm64-musl": "4.34.6", "@rollup/rollup-linux-loongarch64-gnu": "4.34.6", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6", "@rollup/rollup-linux-riscv64-gnu": "4.34.6", "@rollup/rollup-linux-s390x-gnu": "4.34.6", "@rollup/rollup-linux-x64-gnu": "4.34.6", "@rollup/rollup-linux-x64-musl": "4.34.6", "@rollup/rollup-win32-arm64-msvc": "4.34.6", "@rollup/rollup-win32-ia32-msvc": "4.34.6", "@rollup/rollup-win32-x64-msvc": "4.34.6", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ=="],
"rollup": ["rollup@4.40.0", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.40.0", "@rollup/rollup-android-arm64": "4.40.0", "@rollup/rollup-darwin-arm64": "4.40.0", "@rollup/rollup-darwin-x64": "4.40.0", "@rollup/rollup-freebsd-arm64": "4.40.0", "@rollup/rollup-freebsd-x64": "4.40.0", "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", "@rollup/rollup-linux-arm-musleabihf": "4.40.0", "@rollup/rollup-linux-arm64-gnu": "4.40.0", "@rollup/rollup-linux-arm64-musl": "4.40.0", "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-musl": "4.40.0", "@rollup/rollup-linux-s390x-gnu": "4.40.0", "@rollup/rollup-linux-x64-gnu": "4.40.0", "@rollup/rollup-linux-x64-musl": "4.40.0", "@rollup/rollup-win32-arm64-msvc": "4.40.0", "@rollup/rollup-win32-ia32-msvc": "4.40.0", "@rollup/rollup-win32-x64-msvc": "4.40.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
@ -661,47 +674,47 @@
"sass": ["sass@1.77.8", "", { "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { "sass": "sass.js" } }, "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ=="],
"sass-embedded": ["sass-embedded@1.86.3", "", { "dependencies": { "@bufbuild/protobuf": "^2.0.0", "buffer-builder": "^0.2.0", "colorjs.io": "^0.5.0", "immutable": "^5.0.2", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-android-arm": "1.86.3", "sass-embedded-android-arm64": "1.86.3", "sass-embedded-android-ia32": "1.86.3", "sass-embedded-android-riscv64": "1.86.3", "sass-embedded-android-x64": "1.86.3", "sass-embedded-darwin-arm64": "1.86.3", "sass-embedded-darwin-x64": "1.86.3", "sass-embedded-linux-arm": "1.86.3", "sass-embedded-linux-arm64": "1.86.3", "sass-embedded-linux-ia32": "1.86.3", "sass-embedded-linux-musl-arm": "1.86.3", "sass-embedded-linux-musl-arm64": "1.86.3", "sass-embedded-linux-musl-ia32": "1.86.3", "sass-embedded-linux-musl-riscv64": "1.86.3", "sass-embedded-linux-musl-x64": "1.86.3", "sass-embedded-linux-riscv64": "1.86.3", "sass-embedded-linux-x64": "1.86.3", "sass-embedded-win32-arm64": "1.86.3", "sass-embedded-win32-ia32": "1.86.3", "sass-embedded-win32-x64": "1.86.3" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-3pZSp24ibO1hdopj+W9DuiWsZOb2YY6AFRo/jjutKLBkqJGM1nJjXzhAYfzRV+Xn5BX1eTI4bBTE09P0XNHOZg=="],
"sass-embedded": ["sass-embedded@1.87.0", "", { "dependencies": { "@bufbuild/protobuf": "^2.0.0", "buffer-builder": "^0.2.0", "colorjs.io": "^0.5.0", "immutable": "^5.0.2", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-android-arm": "1.87.0", "sass-embedded-android-arm64": "1.87.0", "sass-embedded-android-ia32": "1.87.0", "sass-embedded-android-riscv64": "1.87.0", "sass-embedded-android-x64": "1.87.0", "sass-embedded-darwin-arm64": "1.87.0", "sass-embedded-darwin-x64": "1.87.0", "sass-embedded-linux-arm": "1.87.0", "sass-embedded-linux-arm64": "1.87.0", "sass-embedded-linux-ia32": "1.87.0", "sass-embedded-linux-musl-arm": "1.87.0", "sass-embedded-linux-musl-arm64": "1.87.0", "sass-embedded-linux-musl-ia32": "1.87.0", "sass-embedded-linux-musl-riscv64": "1.87.0", "sass-embedded-linux-musl-x64": "1.87.0", "sass-embedded-linux-riscv64": "1.87.0", "sass-embedded-linux-x64": "1.87.0", "sass-embedded-win32-arm64": "1.87.0", "sass-embedded-win32-ia32": "1.87.0", "sass-embedded-win32-x64": "1.87.0" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-1IA3iTJNh4BkkA/nidKiVwbmkxr9o6LsPegycHMX/JYs255zpocN5GdLF1+onohQCJxbs5ldr8osKV7qNaNBjg=="],
"sass-embedded-android-arm": ["sass-embedded-android-arm@1.86.3", "", { "os": "android", "cpu": "arm" }, "sha512-UyeXrFzZSvrGbvrWUBcspbsbivGgAgebLGJdSqJulgSyGbA6no3DWQ5Qpdd6+OAUC39BlpPu74Wx9s4RrVuaFw=="],
"sass-embedded-android-arm": ["sass-embedded-android-arm@1.87.0", "", { "os": "android", "cpu": "arm" }, "sha512-Z20u/Y1kFDpMbgiloR5YPLxNuMVeKQRC8e/n68oAAxf3u7rDSmNn2msi7USqgT1f2zdBBNawn/ifbFEla6JiHw=="],
"sass-embedded-android-arm64": ["sass-embedded-android-arm64@1.86.3", "", { "os": "android", "cpu": "arm64" }, "sha512-q+XwFp6WgAv+UgnQhsB8KQ95kppvWAB7DSoJp+8Vino8b9ND+1ai3cUUZPE5u4SnLZrgo5NtrbPvN5KLc4Pfyg=="],
"sass-embedded-android-arm64": ["sass-embedded-android-arm64@1.87.0", "", { "os": "android", "cpu": "arm64" }, "sha512-uqeZoBuXm3W2KhxolScAAfWOLHL21e50g7AxlLmG0he7WZsWw6e9kSnmq301iLIFp4kvmXYXbXbNKAeu9ItRYA=="],
"sass-embedded-android-ia32": ["sass-embedded-android-ia32@1.86.3", "", { "os": "android", "cpu": "ia32" }, "sha512-gTJjVh2cRzvGujXj5ApPk/owUTL5SiO7rDtNLrzYAzi1N5HRuLYXqk3h1IQY3+eCOBjGl7mQ9XyySbJs/3hDvg=="],
"sass-embedded-android-ia32": ["sass-embedded-android-ia32@1.87.0", "", { "os": "android", "cpu": "ia32" }, "sha512-hSWTqo2Igdig528cUb1W1+emw9d1J4+nqOoR4tERS04zcwRRFNDiuBT0o5meV7nkEwE982F+h57YdcRXj8gTtg=="],
"sass-embedded-android-riscv64": ["sass-embedded-android-riscv64@1.86.3", "", { "os": "android", "cpu": "none" }, "sha512-Po3JnyiCS16kd6REo1IMUbFGYtvL9O0rmKaXx5vOuBaJD1LPy2LiSSp7TU7wkJ9IxsTDGzFaSeP1I9qb6D8VVg=="],
"sass-embedded-android-riscv64": ["sass-embedded-android-riscv64@1.87.0", "", { "os": "android", "cpu": "none" }, "sha512-kBAPSjiTBLy5ua/0LRNAJwOAARhzFU7gP35fYORJcdBuz1lkIVPVnid1lh9qQ6Ce9MOJcr7VKFtGnTuqVeig5A=="],
"sass-embedded-android-x64": ["sass-embedded-android-x64@1.86.3", "", { "os": "android", "cpu": "x64" }, "sha512-+7h3jdDv/0kUFx0BvxYlq2fa7CcHiDPlta6k5OxO5K6jyqJwo9hc0Z052BoYEauWTqZ+vK6bB5rv2BIzq4U9nA=="],
"sass-embedded-android-x64": ["sass-embedded-android-x64@1.87.0", "", { "os": "android", "cpu": "x64" }, "sha512-ZHMrNdtdMSpJUYco2MesnlPwDTZftD3pqkkOMI2pbqarPoFUKJtP5k80nwCM0sJGtqfNE+O16w9yPght0CMiJg=="],
"sass-embedded-darwin-arm64": ["sass-embedded-darwin-arm64@1.86.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EgLwV4ORm5Hr0DmIXo0Xw/vlzwLnfAiqD2jDXIglkBsc5czJmo4/IBdGXOP65TRnsgJEqvbU3aQhuawX5++x9A=="],
"sass-embedded-darwin-arm64": ["sass-embedded-darwin-arm64@1.87.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7TK1JWJdCIRSdZv5CJv/HpDz/wIfwUy2FoPz9sVOEj1pDTH0N+VfJd5VutCddIdoQN9jr0ap8vwkc65FbAxV2A=="],
"sass-embedded-darwin-x64": ["sass-embedded-darwin-x64@1.86.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-dfKhfrGPRNLWLC82vy/vQGmNKmAiKWpdFuWiePRtg/E95pqw+sCu6080Y6oQLfFu37Iq3MpnXiSpDuSo7UnPWA=="],
"sass-embedded-darwin-x64": ["sass-embedded-darwin-x64@1.87.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-2JiQzt7FmgUC4MYT2QvbeH/Bi3e76WEhaYoc5P3WyTW8unsHksyTdMuTuYe0Qf9usIyt6bmm5no/4BBw7c8Cig=="],
"sass-embedded-linux-arm": ["sass-embedded-linux-arm@1.86.3", "", { "os": "linux", "cpu": "arm" }, "sha512-+fVCIH+OR0SMHn2NEhb/VfbpHuUxcPtqMS34OCV3Ka99LYZUJZqth4M3lT/ppGl52mwIVLNYzR4iLe6mdZ6mYA=="],
"sass-embedded-linux-arm": ["sass-embedded-linux-arm@1.87.0", "", { "os": "linux", "cpu": "arm" }, "sha512-z5P6INMsGXiUcq1sRRbksyQUhalFFYjTEexuxfSYdK3U2YQMADHubQh8pGzkWvFRPOpnh83RiGuwvpaARYHnsw=="],
"sass-embedded-linux-arm64": ["sass-embedded-linux-arm64@1.86.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-tYq5rywR53Qtc+0KI6pPipOvW7a47ETY69VxfqI9BR2RKw2hBbaz0bIw6OaOgEBv2/XNwcWb7a4sr7TqgkqKAA=="],
"sass-embedded-linux-arm64": ["sass-embedded-linux-arm64@1.87.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-5z+mwJCbGZcg+q+MwdEVSh0ogFK7OSAe175Gsozzr/Izw34Q+RGUw9O82jsV2c4YNuTAQvzEHgIO5cvNvt3Quw=="],
"sass-embedded-linux-ia32": ["sass-embedded-linux-ia32@1.86.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-CmQ5OkqnaeLdaF+bMqlYGooBuenqm3LvEN9H8BLhjkpWiFW8hnYMetiqMcJjhrXLvDw601KGqA5sr/Rsg5s45g=="],
"sass-embedded-linux-ia32": ["sass-embedded-linux-ia32@1.87.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-Xzcp+YPp0iakGL148Jl57CO+MxLuj2jsry3M+rc1cSnDlvkjNVs6TMxaL70GFeV5HdU2V60voYcgE7adDUtJjw=="],
"sass-embedded-linux-musl-arm": ["sass-embedded-linux-musl-arm@1.86.3", "", { "os": "linux", "cpu": "arm" }, "sha512-SEm65SQknI4pl+mH5Xf231hOkHJyrlgh5nj4qDbiBG6gFeutaNkNIeRgKEg3cflXchCr8iV/q/SyPgjhhzQb7w=="],
"sass-embedded-linux-musl-arm": ["sass-embedded-linux-musl-arm@1.87.0", "", { "os": "linux", "cpu": "arm" }, "sha512-4PyqOWhRzyu06RRmpCCBOJdF4BOv7s446wrV6yODtEyyfSIDx3MJabo3KT0oJ1lTWSI/aU3R89bKx0JFXcIHHw=="],
"sass-embedded-linux-musl-arm64": ["sass-embedded-linux-musl-arm64@1.86.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-4zOr2C/eW89rxb4ozTfn7lBzyyM5ZigA1ZSRTcAR26Qbg/t2UksLdGnVX9/yxga0d6aOi0IvO/7iM2DPPRRotg=="],
"sass-embedded-linux-musl-arm64": ["sass-embedded-linux-musl-arm64@1.87.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-HWE5eTRCoKzFZWsxOjDMTF5m4DDTQ0n7NJxSYiUXPBDydr9viPXbGOMYG7WVJLjiF7upr7DYo/mfp/SNTMlZyg=="],
"sass-embedded-linux-musl-ia32": ["sass-embedded-linux-musl-ia32@1.86.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-84Tcld32LB1loiqUvczWyVBQRCChm0wNLlkT59qF29nxh8njFIVf9yaPgXcSyyjpPoD9Tu0wnq3dvVzoMCh9AQ=="],
"sass-embedded-linux-musl-ia32": ["sass-embedded-linux-musl-ia32@1.87.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-aQaPvlRn3kh93PLQvl6BcFKu8Ji92+42blFEkg6nMVvmugD5ZwH2TGFrX25ibx4CYxRpMS4ssF7a0i7vy5HB1Q=="],
"sass-embedded-linux-musl-riscv64": ["sass-embedded-linux-musl-riscv64@1.86.3", "", { "os": "linux", "cpu": "none" }, "sha512-IxEqoiD7vdNpiOwccybbV93NljBy64wSTkUOknGy21SyV43C8uqESOwTwW9ywa3KufImKm8L3uQAW/B0KhJMWg=="],
"sass-embedded-linux-musl-riscv64": ["sass-embedded-linux-musl-riscv64@1.87.0", "", { "os": "linux", "cpu": "none" }, "sha512-o5DxcqiFzET3KRWo+futHr/lhAMBP3tJGGx8YIgpHQYfvDMbsvE0hiFC+nZ/GF9dbcGd+ceIQwfvE5mcc7Gsjw=="],
"sass-embedded-linux-musl-x64": ["sass-embedded-linux-musl-x64@1.86.3", "", { "os": "linux", "cpu": "x64" }, "sha512-ePeTPXUxPK6JgHcUfnrkIyDtyt+zlAvF22mVZv6y1g/PZFm1lSfX+Za7TYHg9KaYqaaXDiw6zICX4i44HhR8rA=="],
"sass-embedded-linux-musl-x64": ["sass-embedded-linux-musl-x64@1.87.0", "", { "os": "linux", "cpu": "x64" }, "sha512-dKxWsu9Wu/CyfzQmHdeiGqrRSzJ85VUjbSx+aP1/7ttmps3SSg+YW95PuqnCOa7GSuSreC3dKKpXHTywUxMLQA=="],
"sass-embedded-linux-riscv64": ["sass-embedded-linux-riscv64@1.86.3", "", { "os": "linux", "cpu": "none" }, "sha512-NuXQ72dwfNLe35E+RaXJ4Noq4EkFwM65eWwCwxEWyJO9qxOx1EXiCAJii6x8kkOh5daWuMU0VAI1B9RsJaqqQQ=="],
"sass-embedded-linux-riscv64": ["sass-embedded-linux-riscv64@1.87.0", "", { "os": "linux", "cpu": "none" }, "sha512-Sy3ESZ4FwBiijvmTA9n+0p0w3MNCue1AgINVPzpAY27EFi0h49eqQm9SWfOkFqmkFS2zFRYowdQOr5Bbr2gOXA=="],
"sass-embedded-linux-x64": ["sass-embedded-linux-x64@1.86.3", "", { "os": "linux", "cpu": "x64" }, "sha512-t8be9zJ5B82+og9bQmIQ83yMGYZMTMrlGA+uGWtYacmwg6w3093dk91Fx0YzNSZBp3Tk60qVYjCZnEIwy60x0g=="],
"sass-embedded-linux-x64": ["sass-embedded-linux-x64@1.87.0", "", { "os": "linux", "cpu": "x64" }, "sha512-+UfjakOcHHKTnEqB3EZ+KqzezQOe1emvy4Rs+eQhLyfekpYuNze/qlRvYxfKTmrtvDiUrIto8MXsyZfMLzkuMA=="],
"sass-embedded-win32-arm64": ["sass-embedded-win32-arm64@1.86.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-4ghuAzjX4q8Nksm0aifRz8hgXMMxS0SuymrFfkfJlrSx68pIgvAge6AOw0edoZoe0Tf5ZbsWUWamhkNyNxkTvw=="],
"sass-embedded-win32-arm64": ["sass-embedded-win32-arm64@1.87.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-m1DS6FYUE0/fv+vt38uQB/kxR4UjnyD+2zcSc298pFmA0aYh/XZIPWw7RxG1HL3KLE1ZrGyu3254MPoxRhs3ig=="],
"sass-embedded-win32-ia32": ["sass-embedded-win32-ia32@1.86.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-tCaK4zIRq9mLRPxLzBAdYlfCuS/xLNpmjunYxeWkIwlJo+k53h1udyXH/FInnQ2GgEz0xMXyvH3buuPgzwWYsw=="],
"sass-embedded-win32-ia32": ["sass-embedded-win32-ia32@1.87.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-JztXLo59GMe2E6g+kCsyiERYhtZgkcyDYx6CrXoSTE5WaE+RbxRiCCCv8/1+hf406f08pUxJ8G0Ody7M5urtBA=="],
"sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.86.3", "", { "os": "win32", "cpu": "x64" }, "sha512-zS+YNKfTF4SnOfpC77VTb0qNZyTXrxnAezSoRV0xnw6HlY+1WawMSSB6PbWtmbvyfXNgpmJUttoTtsvJjRCucg=="],
"sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.87.0", "", { "os": "win32", "cpu": "x64" }, "sha512-4nQErpauvhgSo+7ClumGdjdf9sGx+U9yBgvhI0+zUw+D5YvraVgvA0Lk8Wuwntx2PqnvKUk8YDr/vxHJostv4Q=="],
"semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
@ -725,12 +738,14 @@
"sync-message-port": ["sync-message-port@1.1.3", "", {}, "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg=="],
"tailwindcss": ["tailwindcss@4.1.3", "", {}, "sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g=="],
"tailwindcss": ["tailwindcss@4.1.4", "", {}, "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A=="],
"tailwindcss-primeui": ["tailwindcss-primeui@0.4.0", "", { "peerDependencies": { "tailwindcss": ">=3.1.0" } }, "sha512-YYC7B7Yyzm1/4pEGgpf1ABAhbrKY++LuPoUamnKE7fTPO5Ct/Qr/dT+Uq2yiVhQnaW1zHQpYnThxfksaxhlDfQ=="],
"tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="],
"tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"ts-api-utils": ["ts-api-utils@2.0.1", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w=="],
@ -765,7 +780,7 @@
"varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="],
"vite": ["vite@6.2.6", "", { "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw=="],
"vite": ["vite@6.3.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.3", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.12" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg=="],
"vite-plugin-vuetify": ["vite-plugin-vuetify@2.1.1", "", { "dependencies": { "@vuetify/loader-shared": "^2.1.0", "debug": "^4.3.3", "upath": "^2.0.1" }, "peerDependencies": { "vite": ">=5", "vue": "^3.0.0", "vuetify": "^3.0.0" } }, "sha512-Pb7bKhQH8qPMzURmEGq2aIqCJkruFNsyf1NcrrtnjsOIkqJPMcBbiP0oJoO8/uAmyB5W/1JTbbUEsyXdMM0QHQ=="],
@ -775,9 +790,11 @@
"vue-eslint-parser": ["vue-eslint-parser@10.1.2", "", { "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.6.0", "lodash": "^4.17.21", "semver": "^7.6.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-1guOfYgNlD7JH2popr/bt5vc7Mzt6quRCnEbqLgpMHvoHEGV1oImzdqrLd+oMD76cHt8ilBP4cda9WA72TLFDQ=="],
"vue-i18n": ["vue-i18n@11.1.3", "", { "dependencies": { "@intlify/core-base": "11.1.3", "@intlify/shared": "11.1.3", "@vue/devtools-api": "^6.5.0" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-Pcylh9z9S5+CJAqgbRZ3EKxFIBIrtY5YUppU722GIT65+Nukm0TCqiQegZnNLCZkXGthxe0cpqj0AoM51H+6Gw=="],
"vue-tsc": ["vue-tsc@2.2.8", "", { "dependencies": { "@volar/typescript": "~2.4.11", "@vue/language-core": "2.2.8" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-jBYKBNFADTN+L+MdesNX/TB3XuDSyaWynKMDgR+yCSln0GQ9Tfb7JS2lr46s2LiFUT1WsmfWsSvIElyxzOPqcQ=="],
"vuetify": ["vuetify@3.8.1", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=2.1.0", "vue": "^3.5.0", "webpack-plugin-vuetify": ">=3.1.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-3qReKBBWIIdJJmwnFU1blVIKHDtnLfIP7kk0MwUrrfjYkWmsDpsymtDnsukkTCnlJ1WvhLr64eQFosr0RVbj9w=="],
"vuetify": ["vuetify@3.8.2", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=2.1.0", "vue": "^3.5.0", "webpack-plugin-vuetify": ">=3.1.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-UJNFP4egmKJTQ3V3MKOq+7vIUKO7/Fko5G6yUsOW2Rm0VNBvAjgO6VY6EnK3DTqEKN6ugVXDEPw37NQSTGLZvw=="],
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
@ -805,8 +822,30 @@
"@primeuix/forms/@primeuix/utils": ["@primeuix/utils@0.4.1", "", {}, "sha512-5+1NLfyna+gLRPeFTo+xlR0tfPVLuVdidbeahAMLkQga5Rw0LxyUBCyD2/Zv2JkV69o2T+hpEDyddl3VdnYoBw=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.9", "", { "dependencies": { "@emnapi/core": "^1.4.0", "@emnapi/runtime": "^1.4.0", "@tybys/wasm-util": "^0.9.0" }, "bundled": true }, "sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg=="],
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tauri-apps/plugin-cli/@tauri-apps/api": ["@tauri-apps/api@2.4.1", "", {}, "sha512-5sYwZCSJb6PBGbBL4kt7CnE5HHbBqwH+ovmOW6ZVju3nX4E3JX6tt2kRklFEH7xMOIwR0btRkZktuLhKvyEQYg=="],
"@tauri-apps/plugin-deep-link/@tauri-apps/api": ["@tauri-apps/api@2.4.1", "", {}, "sha512-5sYwZCSJb6PBGbBL4kt7CnE5HHbBqwH+ovmOW6ZVju3nX4E3JX6tt2kRklFEH7xMOIwR0btRkZktuLhKvyEQYg=="],
"@tauri-apps/plugin-dialog/@tauri-apps/api": ["@tauri-apps/api@2.4.1", "", {}, "sha512-5sYwZCSJb6PBGbBL4kt7CnE5HHbBqwH+ovmOW6ZVju3nX4E3JX6tt2kRklFEH7xMOIwR0btRkZktuLhKvyEQYg=="],
"@tauri-apps/plugin-fs/@tauri-apps/api": ["@tauri-apps/api@2.4.1", "", {}, "sha512-5sYwZCSJb6PBGbBL4kt7CnE5HHbBqwH+ovmOW6ZVju3nX4E3JX6tt2kRklFEH7xMOIwR0btRkZktuLhKvyEQYg=="],
"@tauri-apps/plugin-opener/@tauri-apps/api": ["@tauri-apps/api@2.3.0", "", {}, "sha512-33Z+0lX2wgZbx1SPFfqvzI6su63hCBkbzv+5NexeYjIx7WA9htdOKoRR7Dh3dJyltqS5/J8vQFyybiRoaL0hlA=="],
"@tauri-apps/plugin-shell/@tauri-apps/api": ["@tauri-apps/api@2.4.1", "", {}, "sha512-5sYwZCSJb6PBGbBL4kt7CnE5HHbBqwH+ovmOW6ZVju3nX4E3JX6tt2kRklFEH7xMOIwR0btRkZktuLhKvyEQYg=="],
"@vue/compiler-sfc/postcss": ["postcss@8.5.1", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ=="],
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
@ -829,14 +868,34 @@
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"rollup/@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
"sass-embedded/immutable": ["immutable@5.0.3", "", {}, "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw=="],
"unplugin-vue-components/unplugin": ["unplugin@1.16.1", "", { "dependencies": { "acorn": "^8.14.0", "webpack-virtual-modules": "^0.6.2" } }, "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w=="],
"vue-i18n/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
"@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"eslint-plugin-vue/vue-eslint-parser/eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
@ -846,5 +905,17 @@
"eslint-plugin-vue/vue-eslint-parser/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
"eslint/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
}
}

View File

@ -14,15 +14,15 @@
"@mdi/font": "7.4.47",
"@primevue/forms": "^4.3.3",
"@primevue/themes": "^4.3.3",
"@tailwindcss/vite": "^4.1.3",
"@tauri-apps/api": "^2.4.1",
"@tailwindcss/vite": "^4.1.4",
"@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-cli": "^2.2.0",
"@tauri-apps/plugin-deep-link": "~2.2.1",
"@tauri-apps/plugin-dialog": "~2.2.1",
"@tauri-apps/plugin-fs": "^2.2.1",
"@tauri-apps/plugin-opener": "^2.2.6",
"@tauri-apps/plugin-shell": "~2.2.1",
"@tauri-apps/plugin-updater": "^2.7.0",
"@tauri-apps/plugin-updater": "^2.7.1",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/markdown-it": "^14.1.2",
"markdown-it": "^14.1.0",
@ -30,13 +30,14 @@
"primeicons": "^7.0.0",
"primevue": "^4.3.3",
"roboto-fontface": "^0.10.0",
"tailwindcss": "^4.1.3",
"tailwindcss": "^4.1.4",
"tailwindcss-primeui": "^0.4.0",
"vue": "^3.5.13",
"vuetify": "^3.8.1"
"vue-i18n": "^11.1.3",
"vuetify": "^3.8.2"
},
"devDependencies": {
"@tauri-apps/cli": "^2.4.1",
"@tauri-apps/cli": "^2.5.0",
"@tsconfig/node22": "^22.0.1",
"@types/node": "^22.14.1",
"@vitejs/plugin-vue": "^5.2.3",
@ -44,11 +45,11 @@
"@vue/tsconfig": "^0.5.1",
"npm-run-all2": "^7.0.2",
"sass": "1.77.8",
"sass-embedded": "^1.86.3",
"sass-embedded": "^1.87.0",
"typescript": "^5.8.3",
"unplugin-fonts": "^1.3.1",
"unplugin-vue-components": "^0.27.5",
"vite": "^6.2.6",
"vite": "^6.3.2",
"vite-plugin-vuetify": "^2.1.1",
"vue-tsc": "^2.2.8"
}

370
rust/Cargo.lock generated
View File

@ -134,9 +134,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.97"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "arbitrary"
@ -162,7 +162,7 @@ dependencies = [
"enumflags2",
"futures-channel",
"futures-util",
"rand 0.9.0",
"rand 0.9.1",
"raw-window-handle",
"serde",
"serde_repr",
@ -208,9 +208,9 @@ dependencies = [
[[package]]
name = "async-compression"
version = "0.4.22"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59a194f9d963d8099596278594b3107448656ba73831c9d8c783e613ce86da64"
checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07"
dependencies = [
"flate2",
"futures-core",
@ -488,11 +488,11 @@ dependencies = [
[[package]]
name = "block2"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d59b4c170e16f0405a2e95aff44432a0d41aa97675f3d52623effe95792a037"
checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2"
dependencies = [
"objc2 0.6.0",
"objc2 0.6.1",
]
[[package]]
@ -521,9 +521,9 @@ dependencies = [
[[package]]
name = "brotli-decompressor"
version = "4.0.2"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37"
checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@ -644,9 +644,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.18"
version = "1.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
dependencies = [
"jobserver",
"libc",
@ -732,18 +732,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.35"
version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.35"
version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
dependencies = [
"anstream",
"anstyle",
@ -1199,9 +1199,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a0d569e003ff27784e0e14e4a594048698e0c0f0b66cabcb51511be55a7caa0"
dependencies = [
"bitflags 2.9.0",
"block2 0.6.0",
"block2 0.6.1",
"libc",
"objc2 0.6.0",
"objc2 0.6.1",
]
[[package]]
name = "dispatch2"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
dependencies = [
"bitflags 2.9.0",
"objc2 0.6.1",
]
[[package]]
@ -1995,9 +2005,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.4.8"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2"
checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633"
dependencies = [
"atomic-waker",
"bytes",
@ -2536,9 +2546,9 @@ dependencies = [
[[package]]
name = "jiff"
version = "0.2.6"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f33145a5cbea837164362c7bd596106eb7c5198f97d1ba6f6ebb3223952e488"
checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6"
dependencies = [
"jiff-static",
"log",
@ -2549,9 +2559,9 @@ dependencies = [
[[package]]
name = "jiff-static"
version = "0.2.6"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43ce13c40ec6956157a3635d97a1ee2df323b263f09ea14165131289cb0f5c19"
checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254"
dependencies = [
"proc-macro2",
"quote",
@ -2697,9 +2707,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.171"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libloading"
@ -2770,12 +2780,6 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "lockfree-object-pool"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
[[package]]
name = "log"
version = "0.4.27"
@ -2908,10 +2912,10 @@ dependencies = [
"dpi",
"gtk",
"keyboard-types",
"objc2 0.6.0",
"objc2 0.6.1",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation 0.3.0",
"objc2-foundation 0.3.1",
"once_cell",
"png",
"serde",
@ -3056,9 +3060,9 @@ dependencies = [
[[package]]
name = "objc2"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59"
checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551"
dependencies = [
"objc2-encode",
"objc2-exception-helper",
@ -3066,75 +3070,77 @@ dependencies = [
[[package]]
name = "objc2-app-kit"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb"
checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
dependencies = [
"bitflags 2.9.0",
"block2 0.6.0",
"block2 0.6.1",
"libc",
"objc2 0.6.0",
"objc2 0.6.1",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-core-image",
"objc2-foundation 0.3.0",
"objc2-quartz-core 0.3.0",
"objc2-foundation 0.3.1",
"objc2-quartz-core 0.3.1",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c1948a9be5f469deadbd6bcb86ad7ff9e47b4f632380139722f7d9840c0d42c"
checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d"
dependencies = [
"bitflags 2.9.0",
"objc2 0.6.0",
"objc2-foundation 0.3.0",
"objc2 0.6.1",
"objc2-foundation 0.3.1",
]
[[package]]
name = "objc2-core-data"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f860f8e841f6d32f754836f51e6bc7777cd7e7053cf18528233f6811d3eceb4"
checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d"
dependencies = [
"bitflags 2.9.0",
"objc2 0.6.0",
"objc2-foundation 0.3.0",
"objc2 0.6.1",
"objc2-foundation 0.3.1",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925"
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
dependencies = [
"bitflags 2.9.0",
"objc2 0.6.0",
"dispatch2 0.3.0",
"objc2 0.6.1",
]
[[package]]
name = "objc2-core-graphics"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02"
checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4"
dependencies = [
"bitflags 2.9.0",
"objc2 0.6.0",
"dispatch2 0.3.0",
"objc2 0.6.1",
"objc2-core-foundation",
"objc2-io-surface",
]
[[package]]
name = "objc2-core-image"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ffa6bea72bf42c78b0b34e89c0bafac877d5f80bf91e159a5d96ea7f693ca56"
checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e"
dependencies = [
"objc2 0.6.0",
"objc2-foundation 0.3.0",
"objc2 0.6.1",
"objc2-foundation 0.3.1",
]
[[package]]
@ -3166,25 +3172,25 @@ dependencies = [
[[package]]
name = "objc2-foundation"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998"
checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
dependencies = [
"bitflags 2.9.0",
"block2 0.6.0",
"block2 0.6.1",
"libc",
"objc2 0.6.0",
"objc2 0.6.1",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19"
checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c"
dependencies = [
"bitflags 2.9.0",
"objc2 0.6.0",
"objc2 0.6.1",
"objc2-core-foundation",
]
@ -3202,14 +3208,14 @@ dependencies = [
[[package]]
name = "objc2-osa-kit"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1ac59da3ceebc4a82179b35dc550431ad9458f9cc326e053f49ba371ce76c5a"
checksum = "26bb88504b5a050dbba515d2414607bf5e57dd56b107bc5f0351197a3e7bdc5d"
dependencies = [
"bitflags 2.9.0",
"objc2 0.6.0",
"objc2 0.6.1",
"objc2-app-kit",
"objc2-foundation 0.3.0",
"objc2-foundation 0.3.1",
]
[[package]]
@ -3227,39 +3233,39 @@ dependencies = [
[[package]]
name = "objc2-quartz-core"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb3794501bb1bee12f08dcad8c61f2a5875791ad1c6f47faa71a0f033f20071"
checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5"
dependencies = [
"bitflags 2.9.0",
"objc2 0.6.0",
"objc2-foundation 0.3.0",
"objc2 0.6.1",
"objc2-foundation 0.3.1",
]
[[package]]
name = "objc2-ui-kit"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "777a571be14a42a3990d4ebedaeb8b54cd17377ec21b92e8200ac03797b3bee1"
checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed"
dependencies = [
"bitflags 2.9.0",
"objc2 0.6.0",
"objc2 0.6.1",
"objc2-core-foundation",
"objc2-foundation 0.3.0",
"objc2-foundation 0.3.1",
]
[[package]]
name = "objc2-web-kit"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b717127e4014b0f9f3e8bba3d3f2acec81f1bde01f656823036e823ed2c94dce"
checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad"
dependencies = [
"bitflags 2.9.0",
"block2 0.6.0",
"objc2 0.6.0",
"block2 0.6.1",
"objc2 0.6.1",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation 0.3.0",
"objc2-foundation 0.3.1",
]
[[package]]
@ -3375,8 +3381,8 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
dependencies = [
"objc2 0.6.0",
"objc2-foundation 0.3.0",
"objc2 0.6.1",
"objc2-foundation 0.3.1",
"objc2-osa-kit",
"serde",
"serde_json",
@ -3810,9 +3816,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.94"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
@ -3854,7 +3860,7 @@ checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc"
dependencies = [
"bytes",
"getrandom 0.3.2",
"rand 0.9.0",
"rand 0.9.1",
"ring",
"rustc-hash",
"rustls",
@ -3922,13 +3928,12 @@ dependencies = [
[[package]]
name = "rand"
version = "0.9.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
"zerocopy",
]
[[package]]
@ -4119,17 +4124,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80c844748fdc82aae252ee4594a89b6e7ebef1063de7951545564cbc4e57075d"
dependencies = [
"ashpd",
"block2 0.6.0",
"dispatch2",
"block2 0.6.1",
"dispatch2 0.2.0",
"glib-sys",
"gobject-sys",
"gtk-sys",
"js-sys",
"log",
"objc2 0.6.0",
"objc2 0.6.1",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation 0.3.0",
"objc2-foundation 0.3.1",
"raw-window-handle",
"wasm-bindgen",
"wasm-bindgen-futures",
@ -4607,9 +4612,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
dependencies = [
"libc",
]
@ -4918,9 +4923,9 @@ dependencies = [
[[package]]
name = "tao"
version = "0.32.8"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63c8b1020610b9138dd7b1e06cf259ae91aa05c30f3bd0d6b42a03997b92dec1"
checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82"
dependencies = [
"bitflags 2.9.0",
"core-foundation 0.10.0",
@ -4939,9 +4944,9 @@ dependencies = [
"ndk",
"ndk-context",
"ndk-sys",
"objc2 0.6.0",
"objc2 0.6.1",
"objc2-app-kit",
"objc2-foundation 0.3.0",
"objc2-foundation 0.3.1",
"once_cell",
"parking_lot",
"raw-window-handle",
@ -4949,8 +4954,8 @@ dependencies = [
"tao-macros",
"unicode-segmentation",
"url",
"windows",
"windows-core 0.60.1",
"windows 0.61.1",
"windows-core 0.61.0",
"windows-version",
"x11-dl",
]
@ -4985,9 +4990,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.4.1"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d08db1ff9e011e04014e737ec022610d756c0eae0b3b3a9037bccaf3003173a"
checksum = "e7b0bc1aec81bda6bc455ea98fcaed26b3c98c1648c627ad6ff1c704e8bf8cbc"
dependencies = [
"anyhow",
"bytes",
@ -5006,9 +5011,10 @@ dependencies = [
"log",
"mime",
"muda",
"objc2 0.6.0",
"objc2 0.6.1",
"objc2-app-kit",
"objc2-foundation 0.3.0",
"objc2-foundation 0.3.1",
"objc2-ui-kit",
"percent-encoding",
"plist",
"raw-window-handle",
@ -5031,14 +5037,14 @@ dependencies = [
"webkit2gtk",
"webview2-com",
"window-vibrancy",
"windows",
"windows 0.61.1",
]
[[package]]
name = "tauri-build"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fd20e4661c2cce65343319e6e8da256958f5af958cafc47c0d0af66a55dcd17"
checksum = "d7a0350f0df1db385ca5c02888a83e0e66655c245b7443db8b78a70da7d7f8fc"
dependencies = [
"anyhow",
"cargo_toml",
@ -5058,9 +5064,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458258b19032450ccf975840116ecf013e539eadbb74420bd890e8c56ab2b1a4"
checksum = "f93f035551bf7b11b3f51ad9bc231ebbe5e085565527991c16cf326aa38cdf47"
dependencies = [
"base64 0.22.1",
"brotli",
@ -5085,9 +5091,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d402813d3b9c773a0fa58697c457c771f10e735498fdcb7b343264d18e5a601f"
checksum = "8db4df25e2d9d45de0c4c910da61cd5500190da14ae4830749fee3466dddd112"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@ -5099,9 +5105,9 @@ dependencies = [
[[package]]
name = "tauri-plugin"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4190775d6ff73fe66d9af44c012739a2659720efd9c0e1e56a918678038699d"
checksum = "37a5ebe6a610d1b78a94650896e6f7c9796323f408800cef436e0fa0539de601"
dependencies = [
"anyhow",
"glob",
@ -5120,7 +5126,7 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5458ae16eac81bdbe8d9da2a9f3e01e8cdedbc381cc1727c01127542c8a61c5"
dependencies = [
"clap 4.5.35",
"clap 4.5.37",
"log",
"serde",
"serde_json",
@ -5199,7 +5205,7 @@ dependencies = [
"dunce",
"glob",
"objc2-app-kit",
"objc2-foundation 0.3.0",
"objc2-foundation 0.3.1",
"open",
"schemars",
"serde",
@ -5208,7 +5214,7 @@ dependencies = [
"tauri-plugin",
"thiserror 2.0.12",
"url",
"windows",
"windows 0.60.0",
"zbus",
]
@ -5251,9 +5257,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-updater"
version = "2.7.0"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82da763248e635d60ee4aed56c862290e523acc838d83097171f9a544708387"
checksum = "73f05c38afd77a4b8fd98e8fb6f1cdbb5fbb8a46ba181eb2758b05321e3c6209"
dependencies = [
"base64 0.22.1",
"dirs",
@ -5283,37 +5289,39 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.5.1"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00ada7ac2f9276f09b8c3afffd3215fd5d9bff23c22df8a7c70e7ef67cacd532"
checksum = "00f004905d549854069e6774533d742b03cacfd6f03deb08940a8677586cbe39"
dependencies = [
"cookie",
"dpi",
"gtk",
"http",
"jni",
"objc2 0.6.1",
"objc2-ui-kit",
"raw-window-handle",
"serde",
"serde_json",
"tauri-utils",
"thiserror 2.0.12",
"url",
"windows",
"windows 0.61.1",
]
[[package]]
name = "tauri-runtime-wry"
version = "2.5.1"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf2e5842c57e154af43a20a49c7efee0ce2578c20b4c2bdf266852b422d2e421"
checksum = "f85d056f4d4b014fe874814034f3416d57114b617a493a4fe552580851a3f3a2"
dependencies = [
"gtk",
"http",
"jni",
"log",
"objc2 0.6.0",
"objc2 0.6.1",
"objc2-app-kit",
"objc2-foundation 0.3.0",
"objc2-foundation 0.3.1",
"once_cell",
"percent-encoding",
"raw-window-handle",
@ -5324,15 +5332,15 @@ dependencies = [
"url",
"webkit2gtk",
"webview2-com",
"windows",
"windows 0.61.1",
"wry",
]
[[package]]
name = "tauri-utils"
version = "2.3.1"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f037e66c7638cc0a2213f61566932b9a06882b8346486579c90e4b019bac447"
checksum = "b2900399c239a471bcff7f15c4399eb1a8c4fe511ba2853e07c996d771a5e0a4"
dependencies = [
"anyhow",
"brotli",
@ -5731,19 +5739,19 @@ dependencies = [
[[package]]
name = "tray-icon"
version = "0.20.0"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d433764348e7084bad2c5ea22c96c71b61b17afe3a11645710f533bd72b6a2b5"
checksum = "9f7eee98ec5c90daf179d55c20a49d8c0d043054ce7c26336c09a24d31f14fa0"
dependencies = [
"crossbeam-channel",
"dirs",
"libappindicator",
"muda",
"objc2 0.6.0",
"objc2 0.6.1",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-foundation 0.3.0",
"objc2-foundation 0.3.1",
"once_cell",
"png",
"serde",
@ -6185,15 +6193,15 @@ dependencies = [
[[package]]
name = "webview2-com"
version = "0.36.0"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0d606f600e5272b514dbb66539dd068211cc20155be8d3958201b4b5bd79ed3"
checksum = "b542b5cfbd9618c46c2784e4d41ba218c336ac70d44c55e47b251033e7d85601"
dependencies = [
"webview2-com-macros",
"webview2-com-sys",
"windows",
"windows-core 0.60.1",
"windows-implement 0.59.0",
"windows 0.61.1",
"windows-core 0.61.0",
"windows-implement 0.60.0",
"windows-interface",
]
@ -6210,13 +6218,13 @@ dependencies = [
[[package]]
name = "webview2-com-sys"
version = "0.36.0"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb27fccd3c27f68e9a6af1bcf48c2d82534b8675b83608a4d81446d095a17ac"
checksum = "8ae2d11c4a686e4409659d7891791254cf9286d3cfe0eef54df1523533d22295"
dependencies = [
"thiserror 2.0.12",
"windows",
"windows-core 0.60.1",
"windows 0.61.1",
"windows-core 0.61.0",
]
[[package]]
@ -6256,10 +6264,10 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c"
dependencies = [
"objc2 0.6.0",
"objc2 0.6.1",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation 0.3.0",
"objc2-foundation 0.3.1",
"raw-window-handle",
"windows-sys 0.59.0",
"windows-version",
@ -6271,11 +6279,24 @@ version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddf874e74c7a99773e62b1c671427abf01a425e77c3d3fb9fb1e4883ea934529"
dependencies = [
"windows-collections",
"windows-collections 0.1.1",
"windows-core 0.60.1",
"windows-future",
"windows-future 0.1.1",
"windows-link",
"windows-numerics",
"windows-numerics 0.1.1",
]
[[package]]
name = "windows"
version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
dependencies = [
"windows-collections 0.2.0",
"windows-core 0.61.0",
"windows-future 0.2.0",
"windows-link",
"windows-numerics 0.2.0",
]
[[package]]
@ -6287,6 +6308,15 @@ dependencies = [
"windows-core 0.60.1",
]
[[package]]
name = "windows-collections"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
"windows-core 0.61.0",
]
[[package]]
name = "windows-core"
version = "0.60.1"
@ -6323,6 +6353,16 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-future"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32"
dependencies = [
"windows-core 0.61.0",
"windows-link",
]
[[package]]
name = "windows-implement"
version = "0.59.0"
@ -6372,6 +6412,16 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-numerics"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core 0.61.0",
"windows-link",
]
[[package]]
name = "windows-registry"
version = "0.4.0"
@ -6771,12 +6821,12 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "wry"
version = "0.50.5"
version = "0.51.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b19b78efae8b853c6c817e8752fc1dbf9cab8a8ffe9c30f399bd750ccf0f0730"
checksum = "c886a0a9d2a94fd90cfa1d929629b79cfefb1546e2c7430c63a47f0664c0e4e2"
dependencies = [
"base64 0.22.1",
"block2 0.6.0",
"block2 0.6.1",
"cookie",
"crossbeam-channel",
"dpi",
@ -6790,10 +6840,10 @@ dependencies = [
"kuchikiki",
"libc",
"ndk",
"objc2 0.6.0",
"objc2 0.6.1",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation 0.3.0",
"objc2-foundation 0.3.1",
"objc2-ui-kit",
"objc2-web-kit",
"once_cell",
@ -6807,8 +6857,8 @@ dependencies = [
"webkit2gtk",
"webkit2gtk-sys",
"webview2-com",
"windows",
"windows-core 0.60.1",
"windows 0.61.1",
"windows-core 0.61.0",
"windows-version",
"x11-dl",
]
@ -7075,15 +7125,13 @@ dependencies = [
[[package]]
name = "zopfli"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7"
dependencies = [
"bumpalo",
"crc32fast",
"lockfree-object-pool",
"log",
"once_cell",
"simd-adler32",
]

View File

@ -1,7 +1,8 @@
use std::hash::{DefaultHasher, Hash, Hasher};
use std::path::Path;
use std::time::SystemTime;
use crate::model::config::GlobalConfig;
use crate::model::patch::PatchFileVec;
use crate::model::patch::{PatchFileVec, PatchList};
use crate::pkg::{Feature, Status};
use crate::profiles::types::Profile;
use crate::{model::misc::Game, pkg::PkgKey};
@ -165,4 +166,22 @@ impl AppData {
panic!("unable to initialize the logger? {:?}", e);
}
}
pub fn patches_enabled(&self, game_target: impl AsRef<Path>, amd_target: impl AsRef<Path>) -> Result<Vec<&PatchList>> {
let ch1 = sha256::try_digest(game_target.as_ref())?;
let ch2 = sha256::try_digest(amd_target.as_ref())?;
let mut res = Vec::new();
for pfile in &self.patch_vec.0 {
for plist in &pfile.0 {
let this_hash = plist.sha256.to_ascii_lowercase();
log::debug!("checking {}", this_hash);
if this_hash == ch1 || this_hash == ch2 {
log::debug!("enabling {this_hash}");
res.push(plist);
}
}
}
Ok(res)
}
}

View File

@ -65,13 +65,19 @@ pub async fn startline(app: AppHandle, refresh: bool) -> Result<(), String> {
let mut amd_dlls = Vec::new();
if let Some(p) = &appd.profile {
hash = appd.sum_packages(p);
(game_dlls, amd_dlls) = prepare_dlls(p.mod_pkgs(), &appd.pkgs).map_err(|e| e.to_string())?
(game_dlls, amd_dlls) = prepare_dlls(p.meta.game, p.mod_pkgs(), &appd.pkgs).map_err(|e| e.to_string())?
}
if let Some(p) = &appd.profile {
log::debug!("{}", hash);
let patches_enabled = appd.patches_enabled(
&p.data.sgt.target,
&p.data.sgt.target.parent().unwrap().join("amdaemon.exe")
).map_err(|e| e.to_string())?;
let info = p.prepare_display()
.map_err(|e| e.to_string())?;
let lineup_res = p.line_up(hash, refresh, &appd.patch_vec).await
let lineup_res = p.line_up(hash, refresh, patches_enabled).await
.map_err(|e| e.to_string());
#[cfg(target_os = "windows")]
@ -404,6 +410,33 @@ pub async fn create_shortcut(app: AppHandle, profile_meta: ProfileMeta) -> Resul
util::create_shortcut(app, &profile_meta).map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn export_profile(state: State<'_, Mutex<AppData>>, export_keychip: bool, files: Vec<String>) -> Result<(), String> {
log::debug!("invoke: export_profile({:?}, {:?} files)", export_keychip, files.len());
let appd = state.lock().await;
match &appd.profile {
Some(p) => {
p.export(export_keychip, files)
.map_err(|e| e.to_string())?;
}
None => {
let err = "export_profile: no profile".to_owned();
log::error!("{}", err);
return Err(err);
}
}
Ok(())
}
#[tauri::command]
pub async fn import_profile(path: PathBuf) -> Result<(), String> {
log::debug!("invoke: import_profile({:?})", path);
Profile::import(path).map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn list_platform_capabilities() -> Result<Vec<String>, ()> {
log::debug!("invoke: list_platform_capabilities");

View File

@ -205,6 +205,8 @@ pub async fn run(_args: Vec<String>) {
cmd::save_current_profile,
cmd::load_segatools_ini,
cmd::create_shortcut,
cmd::export_profile,
cmd::import_profile,
cmd::get_global_config,
cmd::set_global_config,

View File

@ -1,6 +1,6 @@
use std::collections::{BTreeMap, BTreeSet};
use serde::{Deserialize, Serialize};
use crate::pkg::{Status, PkgKey, PkgKeyVersion};
use crate::pkg::{PkgKey, PkgKeyVersion};
use super::misc::Game;
@ -14,7 +14,10 @@ pub struct PackageManifest {
pub dependencies: BTreeSet<PkgKeyVersion>,
#[serde(default)]
pub installers: Vec<BTreeMap<String, serde_json::Value>>
pub installers: Vec<BTreeMap<String, serde_json::Value>>,
#[serde(default)]
pub games: Option<Vec<Game>>,
}
pub type PackageList = BTreeMap<PkgKey, PackageListEntry>;
@ -22,6 +25,5 @@ pub type PackageList = BTreeMap<PkgKey, PackageListEntry>;
#[derive(Serialize, Deserialize, Clone)]
pub struct PackageListEntry {
pub version: String,
pub status: Status,
pub games: Vec<Game>,
}

View File

@ -71,10 +71,17 @@ impl Game {
pub fn has_module(&self, module: ProfileModule) -> bool {
match self {
Game::Ongeki => make_bitflags!(ProfileModule::{Segatools | Display | Network | BepInEx | Mu3Ini | Keyboard}),
Game::Ongeki => make_bitflags!(ProfileModule::{Segatools | Display | Network | BepInEx | Mu3Ini | Keyboard | Mempatcher}),
Game::Chunithm => make_bitflags!(ProfileModule::{Segatools | Display | Network | Keyboard | Mempatcher}),
}.contains(module)
}
pub fn bitness(&self) -> i32 {
match self {
Game::Ongeki => 64,
Game::Chunithm => 32,
}
}
}
impl std::fmt::Display for Game {

View File

@ -42,6 +42,7 @@ pub struct Patch {
pub enum PatchData {
Normal(NormalPatch),
Number(NumberPatch),
Hex(HexPatch),
}
#[derive(Deserialize, Serialize, Clone, Debug)]
@ -65,6 +66,12 @@ pub struct NumberPatch {
pub max: i32
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct HexPatch {
pub offset: u64,
pub off: Vec<u8>,
}
impl Serialize for Patch {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer {
@ -83,6 +90,11 @@ impl Serialize for Patch {
state.serialize_field("size", &patch.size)?;
state.serialize_field("min", &patch.min)?;
state.serialize_field("max", &patch.max)?;
},
PatchData::Hex(patch) => {
state.serialize_field("type", "hex")?;
state.serialize_field("offset", &patch.offset)?;
state.serialize_field("off", &patch.off)?;
}
}
state.end()
@ -114,6 +126,23 @@ impl<'de> serde::Deserialize<'de> for Patch {
.ok_or_else(|| de::Error::missing_field("max"))?
).map_err(|_| de::Error::missing_field("max"))?
}),
Some("hex") => {
let mut off_list = Vec::new();
for off in value.get("off").and_then(Value::as_array).unwrap() {
off_list.push(u8::try_from(
off.as_u64().ok_or_else(|| de::Error::missing_field("off"))?
).map_err(|_| de::Error::missing_field("off"))?);
}
// for off in value.get("off").and_then(Value::as_str).unwrap().bytes() {
// off_list.push(off);
// }
PatchData::Hex(HexPatch {
offset: value.get("offset")
.and_then(Value::as_u64)
.ok_or_else(|| de::Error::missing_field("offset"))?,
off: off_list
})
},
None => {
let mut patches = vec![];
for patch in value.get("patches").and_then(Value::as_array).unwrap() {

View File

@ -166,11 +166,26 @@ pub enum Mu3Audio {
Excl2Ch,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(default)]
pub struct Mu3Ini {
pub audio: Option<Mu3Audio>,
pub sample_rate: i32,
pub blacklist: Option<(i32, i32)>,
pub gp: i32,
pub enable_bonus_tracks: bool,
}
impl Default for Mu3Ini {
fn default() -> Self {
Self {
audio: Some(Mu3Audio::Shared),
sample_rate: 48_000,
blacklist: Some((10000, 19999)),
gp: 999,
enable_bonus_tracks: true
}
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]

View File

@ -1,25 +1,23 @@
use std::path::Path;
use anyhow::Result;
use crate::model::patch::{Patch, PatchData, PatchFileVec, PatchSelection, PatchSelectionData};
use crate::model::patch::{Patch, PatchData, PatchList, PatchSelection, PatchSelectionData};
impl PatchSelection {
pub async fn render_to_file(
&self,
filename: &str,
patches: &PatchFileVec,
patch_lists: &Vec<&PatchList>,
path: impl AsRef<Path>
) -> Result<()> {
let mut res = "".to_owned();
for file in &patches.0 {
for list in &file.0 {
if list.filename != filename {
continue;
}
for patch in &list.patches {
if let Some(selection) = self.0.get(&patch.id) {
res += &Self::render(filename, patch, selection);
}
for list in patch_lists {
if list.filename != filename {
continue;
}
for patch in &list.patches {
if let Some(selection) = self.0.get(&patch.id) {
res += &Self::render(filename, patch, selection);
}
}
}
@ -52,6 +50,21 @@ impl PatchSelection {
} else {
log::error!("invalid number patch {:?}", patch);
}
},
PatchData::Hex(data) => {
if let PatchSelectionData::Hex(val) = sel {
res += &format!("{} F+{:X} ", filename, data.offset);
for byte in val {
res += &format!("{:02X}", byte);
}
res += " ";
for byte in &data.off {
res += &format!("{:02X}", byte);
}
} else {
log::error!("invalid number patch {:?}", patch);
}
}
}
format!("{}\n", res)

View File

@ -1,11 +1,11 @@
use std::path::Path;
use anyhow::Result;
use anyhow::{anyhow, Result};
use ini::Ini;
use crate::model::profile::{Mu3Audio, Mu3Ini};
impl Mu3Ini {
pub fn line_up(&self, game_path: impl AsRef<Path>) -> Result<()> {
let file = game_path.as_ref().join("mu3.ini");
pub fn line_up(&self, data_dir: impl AsRef<Path>, cfg_dir: impl AsRef<Path>) -> Result<()> {
let file = cfg_dir.as_ref().join("mu3.ini");
if !file.exists() {
std::fs::write(&file, "")?;
@ -20,9 +20,26 @@ impl Mu3Ini {
Mu3Audio::Excl2Ch => "2",
};
ini.with_section(Some("Sound")).set("WasapiExclusive", value);
ini.with_section(Some("Sound"))
.set("WasapiExclusive", value)
.set("SampleRate", self.sample_rate.to_string());
}
if let Some(blacklist) = self.blacklist {
ini.with_section(Some("Extra"))
.set("BlacklistMin", blacklist.0.to_string())
.set("BlacklistMax", blacklist.1.to_string());
}
let cache_path = data_dir.as_ref().join("mu3-mods-cache");
let cache_path = cache_path.to_str()
.ok_or_else(|| anyhow!("Invalid cache path"))?;
ini.with_section(Some("Extra"))
.set("GP", self.gp.to_string())
.set("CacheDir", cache_path)
.set("UnlockBonusTracks", crate::util::bool_to_01(self.enable_bonus_tracks));
ini.write_to_file(file)?;
Ok(())

View File

@ -1,6 +1,7 @@
use anyhow::Result;
use std::collections::BTreeSet;
use std::path::PathBuf;
use crate::model::misc::Game;
use crate::pkg::{PkgKey, Status};
use crate::pkg_store::PackageStore;
use crate::util;
@ -60,6 +61,7 @@ pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgK
}
pub fn prepare_dlls(
game: Game,
enabled_pkgs: &BTreeSet<PkgKey>,
store: &PackageStore,
) -> Result<(Vec<PathBuf>, Vec<PathBuf>)> {
@ -72,6 +74,16 @@ pub fn prepare_dlls(
if let Some(game_dll) = &dlls.game {
res_game.push(pkg.path().join(game_dll.clone()));
}
if let Some(game32_dll) = &dlls.game32 {
if game.bitness() == 32 {
res_game.push(pkg.path().join(game32_dll.clone()));
}
}
if let Some(game64_dll) = &dlls.game64 {
if game.bitness() == 64 {
res_game.push(pkg.path().join(game64_dll.clone()));
}
}
if let Some(amd_dll) = &dlls.amd {
res_amd.push(pkg.path().join(amd_dll.clone()));
}

View File

@ -5,30 +5,30 @@ use crate::{model::{misc::{ConfigHook, ConfigHookAime, ConfigHookAimeUnit, Confi
use crate::pkg_store::PackageStore;
impl Segatools {
pub fn fix(&mut self, store: &PackageStore) {
macro_rules! remove_if_nonpresent {
($item:expr,$key:expr,$emptyval:expr,$store:expr) => {
if let Ok(pkg) = $store.get($key) {
if pkg.loc.is_none() {
$item = $emptyval;
}
} else {
$item = $emptyval;
}
}
}
pub fn fix(&mut self, _store: &PackageStore) {
// macro_rules! remove_if_nonpresent {
// ($item:expr,$key:expr,$emptyval:expr,$store:expr) => {
// if let Ok(pkg) = $store.get($key) {
// if pkg.loc.is_none() {
// $item = $emptyval;
// }
// } else {
// $item = $emptyval;
// }
// }
// }
if let Some(key) = &self.hook {
remove_if_nonpresent!(self.hook, key, None, store);
}
if let IOSelection::Custom(key) = &self.io2 {
remove_if_nonpresent!(self.io2, key, IOSelection::default(), store);
}
match &self.aime {
Aime::AMNet(key) => remove_if_nonpresent!(self.aime, key, Aime::BuiltIn, store),
Aime::Other(key) => remove_if_nonpresent!(self.aime, key, Aime::BuiltIn, store),
_ => {},
}
// if let Some(key) = &self.hook {
// remove_if_nonpresent!(self.hook, key, None, store);
// }
// if let IOSelection::Custom(key) = &self.io2 {
// remove_if_nonpresent!(self.io2, key, IOSelection::default(), store);
// }
// match &self.aime {
// Aime::AMNet(key) => remove_if_nonpresent!(self.aime, key, Aime::BuiltIn, store),
// Aime::Other(key) => remove_if_nonpresent!(self.aime, key, Aime::BuiltIn, store),
// _ => {},
// }
}
pub fn load_from_ini(&mut self, ini: &Ini, config_dir: impl AsRef<Path>) -> Result<()> {
log::debug!("loading sgt");

View File

@ -12,13 +12,18 @@ impl PatchFileVec {
std::fs::create_dir(&path)?;
}
std::fs::write(path.join("builtin-chunithm.json5"), include_bytes!("../static/standard-chunithm.json5"))?;
std::fs::write(path.join("builtin-amdaemon.json5"), include_bytes!("../static/standard-amdaemon.json5"))?;
let mut res = Vec::new();
for f in std::fs::read_dir(path)? {
let f = f?;
let f = f.path();
res.push(
serde_json5::from_str::<PatchFile>(&std::fs::read_to_string(f)?)?
);
let f = &f.path();
match serde_json5::from_str::<PatchFile>(&std::fs::read_to_string(f)?) {
Ok(parsed) => res.push(parsed),
Err(e) => {
log::error!("Error parsing {f:?}: {e}");
anyhow::bail!("Error parsing {f:?}: {e}");
}
}
}
Ok(PatchFileVec(res))
}
@ -26,20 +31,21 @@ impl PatchFileVec {
pub fn find_patches(&self, target: impl AsRef<Path>) -> Result<Vec<Patch>> {
let checksum = try_digest(target.as_ref())?;
let mut res = Vec::new();
let mut res_patches = Vec::new();
for pfile in &self.0 {
for plist in &pfile.0 {
log::debug!("checking {}", plist.sha256);
if plist.sha256 == checksum {
let this_hash = plist.sha256.to_ascii_lowercase();
log::debug!("checking {}", this_hash);
if this_hash == checksum {
let mut cloned = plist.clone().patches;
res.append(&mut cloned);
res_patches.append(&mut cloned);
}
}
}
if res.len() == 0 {
if res_patches.len() == 0 {
log::warn!("no matching patchset for {:?} ({})", target.as_ref(), checksum);
}
Ok(res)
Ok(res_patches)
}
}

View File

@ -41,6 +41,8 @@ pub enum Status {
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
pub struct DLLs {
pub game: Option<String>,
pub game32: Option<String>,
pub game64: Option<String>,
pub amd: Option<String>
}
@ -120,7 +122,7 @@ impl Package {
})
}
pub async fn from_dir(dir: PathBuf, source: PackageSource) -> Result<Package> {
pub async fn from_dir(dir: PathBuf, source: PackageSource) -> Result<(Package, Option<Vec<Game>>)> {
let str = fs::read_to_string(dir.join("manifest.json")).await?;
let mft: local::PackageManifest = serde_json::from_str(&str)?;
@ -133,7 +135,7 @@ impl Package {
let status = Self::parse_status(&mft, &dir);
let dependencies = Self::sanitize_deps(mft.dependencies);
Ok(Package {
Ok((Package {
namespace: Self::dir_to_namespace(&dir)?,
name: mft.name.clone(),
description: mft.description.clone(),
@ -146,7 +148,7 @@ impl Package {
}),
rmt: None,
source
})
}, mft.games))
}
pub fn key(&self) -> PkgKey {
@ -231,10 +233,12 @@ impl Package {
if dir.as_ref().join("app").join("data").exists() {
return Status::Unsupported;
}
return Status::OK(make_bitflags!(Feature::Mod), DLLs { game: None, amd: None });
return Status::OK(make_bitflags!(Feature::Mod), DLLs { game: None, game32: None, game64: None, amd: None });
} else {
let mut flags = BitFlags::default();
let mut game_dll = None;
let mut game32_dll = None;
let mut game64_dll = None;
let mut amd_dll = None;
for installer in &mft.installers {
if let Some(serde_json::Value::String(id)) = installer.get("identifier") {
@ -262,6 +266,16 @@ impl Package {
flags |= Feature::Mod;
game_dll = Some(path.to_owned());
}
if let Some(serde_json::Value::String(path)) = installer.get("dll-game32") {
flags |= Feature::GameDLL;
flags |= Feature::Mod;
game32_dll = Some(path.to_owned());
}
if let Some(serde_json::Value::String(path)) = installer.get("dll-game64") {
flags |= Feature::GameDLL;
flags |= Feature::Mod;
game64_dll = Some(path.to_owned());
}
if let Some(serde_json::Value::String(path)) = installer.get("dll-amdaemon") {
flags |= Feature::AmdDLL;
flags |= Feature::Mod;
@ -272,8 +286,8 @@ impl Package {
}
}
}
log::debug!("{} parse result: {:?} {:?} {:?}", mft.name, flags, game_dll, amd_dll);
Status::OK(flags, DLLs { game: game_dll, amd: amd_dll })
log::debug!("{} parse result: {:?} {:?} {:?} {:?} {:?}", mft.name, flags, game_dll, game32_dll, game64_dll, amd_dll);
Status::OK(flags, DLLs { game: game_dll, game32: game32_dll, game64: game64_dll, amd: amd_dll })
}
}

View File

@ -83,7 +83,7 @@ impl PackageStore {
pub async fn reload_package(&mut self, key: PkgKey) {
let dir = util::pkg_dir().join(&key.0);
if let Ok(pkg) = Package::from_dir(dir, PackageSource::Rainy).await {
if let Ok((pkg, _)) = Package::from_dir(dir, PackageSource::Rainy).await {
self.update_nonremote(key, pkg);
} else {
log::error!("couldn't reload {}", key);
@ -102,7 +102,13 @@ impl PackageStore {
}
while let Some(res) = futures.join_next().await {
if let Ok(Ok(pkg)) = res {
if let Ok(Ok((pkg, locally_declared_games))) = res {
if let Some(games) = locally_declared_games {
self.meta_list.insert(pkg.key(), PackageListEntry {
version: pkg.loc.as_ref().unwrap().version.clone(),
games
});
}
self.update_nonremote(pkg.key(), pkg);
}
}
@ -152,7 +158,6 @@ impl PackageStore {
PackageListEntry {
// from_rainy() is guaranteed to include rmt
version: r.rmt.as_ref().unwrap().version.clone(),
status: Status::Unchecked,
games: vec![ game ],
}
});

View File

@ -1,6 +1,6 @@
pub use types::{Profile, ProfileData, ProfileMeta, ProfilePaths, StartPayload};
use std::{collections::{BTreeMap, BTreeSet}, path::{Path, PathBuf}};
use crate::{model::{misc::Game, patch::{PatchFileVec, PatchSelection}, profile::{Aime, ChunithmKeyboard, IOSelection, Keyboard, Mu3Ini, OngekiKeyboard, ProfileModule}}, modules::{display_windows::DisplayInfo, package::prepare_packages}, pkg::PkgKey, pkg_store::PackageStore, util};
use crate::{model::{misc::Game, patch::{PatchList, PatchSelection}, profile::{Aime, ChunithmKeyboard, IOSelection, Keyboard, Mu3Ini, OngekiKeyboard, ProfileModule}}, modules::{display_windows::DisplayInfo, package::prepare_packages}, pkg::PkgKey, pkg_store::PackageStore, util};
use tauri::Emitter;
use std::process::Stdio;
use crate::model::profile::BepInEx;
@ -10,6 +10,7 @@ use std::fs::File;
use tokio::process::Command;
use tokio::task::JoinSet;
pub mod template;
pub mod types;
impl Profile {
@ -28,14 +29,14 @@ impl Profile {
bepinex: if meta.game == Game::Ongeki { Some(BepInEx::default()) } else { None },
#[cfg(not(target_os = "windows"))]
wine: crate::model::profile::Wine::default(),
mu3_ini: if meta.game == Game::Ongeki { Some(Mu3Ini { audio: None, blacklist: None }) } else { None },
mu3_ini: if meta.game == Game::Ongeki { Some(Mu3Ini::default()) } else { None },
keyboard:
if meta.game == Game::Ongeki {
Some(Keyboard::Ongeki(OngekiKeyboard::default()))
} else {
Some(Keyboard::Chunithm(ChunithmKeyboard::default()))
},
patches: if meta.game == Game::Chunithm { Some(PatchSelection(BTreeMap::new())) } else { None }
patches: Some(PatchSelection(BTreeMap::new()))
},
meta: meta.clone()
};
@ -67,6 +68,21 @@ impl Profile {
data.sgt.io2 = IOSelection::Custom(io);
data.sgt.io = None;
}
if let Some(ini) = &mut data.mu3_ini {
if ini.audio.is_none() {
ini.audio = Some(crate::model::profile::Mu3Audio::Shared);
}
if ini.blacklist.is_none() {
ini.blacklist = Some((10000, 19999));
}
} else {
data.mu3_ini = Some(Mu3Ini::default());
}
if data.patches.is_none() {
data.patches = Some(PatchSelection(BTreeMap::new()));
}
Self::load_existing_mu3_ini(&data, &ProfileMeta { game, name: name.clone() })?;
}
if game == Game::Chunithm {
if data.keyboard.is_none() {
@ -168,7 +184,7 @@ impl Profile {
Ok(info)
}
pub async fn line_up(&self, pkg_hash: String, refresh: bool, patch_files: &PatchFileVec) -> Result<()> {
pub async fn line_up(&self, pkg_hash: String, refresh: bool, patchlists_enabled: Vec<&PatchList>) -> Result<()> {
if !self.data_dir().exists() {
tokio::fs::create_dir(self.data_dir()).await?;
}
@ -203,13 +219,13 @@ impl Profile {
}
if let Some(mu3ini) = &self.data.mu3_ini {
mu3ini.line_up(&self.data.sgt.target.parent().unwrap())?;
mu3ini.line_up(&self.data_dir(), &self.config_dir())?;
}
if let Some(patches) = &self.data.patches {
futures::try_join!(
patches.render_to_file("amdaemon.exe", patch_files, self.data_dir().join("patch-amd.mph")),
patches.render_to_file("chusanApp.exe", patch_files, self.data_dir().join("patch-game.mph"))
patches.render_to_file("amdaemon.exe", &patchlists_enabled, self.data_dir().join("patch-amd.mph")),
patches.render_to_file("chusanApp.exe", &patchlists_enabled, self.data_dir().join("patch-game.mph"))
)?;
}
@ -283,6 +299,14 @@ impl Profile {
"ONGEKI_LANG_PATH",
self.data_dir().join("lang"),
)
.env(
"MU3_MODS_CONFIG_PATH",
self.config_dir().join("mu3.ini"),
)
.env(
"STARTLINER",
"1"
)
.current_dir(&exe_dir)
.raw_arg("-d")
.raw_arg("-k")
@ -403,6 +427,19 @@ impl Profile {
Ok(false)
}
}
fn load_existing_mu3_ini(data: &ProfileData, meta: &ProfileMeta) -> Result<()> {
if let Some(parent) = data.sgt.target.parent() {
let mu3_ini_target_path = parent.join("mu3.ini");
let mu3_ini_profile_path = util::profile_config_dir(meta.game, &meta.name).join("mu3.ini");
log::debug!("mu3.ini paths: {:?} {:?}", mu3_ini_target_path, mu3_ini_profile_path);
if mu3_ini_target_path.exists() && !mu3_ini_profile_path.exists() {
std::fs::copy(&mu3_ini_target_path, &mu3_ini_profile_path)?;
log::info!("copied mu3.ini from {:?}", &mu3_ini_target_path);
}
}
Ok(())
}
}
impl ProfilePaths for Profile {

View File

@ -0,0 +1,90 @@
use std::{fs::File, io::{Read, Write}, path::PathBuf};
use zip::{write::FileOptions, ZipArchive, ZipWriter};
use crate::util;
use super::{Profile, ProfilePaths};
impl Profile {
fn find_template_json(archive: &mut ZipArchive<File>) -> anyhow::Result<String> {
if let Ok(mut file) = archive.by_name("template.json") {
let mut contents = Vec::new();
file.read_to_end(&mut contents)?;
Ok(String::from_utf8(contents)?)
} else {
anyhow::bail!("invalid template: no template.json found")
}
}
pub fn import(path: PathBuf) -> anyhow::Result<()> {
let file = File::open(path)?;
let mut archive = ZipArchive::new(file)?;
match Self::find_template_json(&mut archive) {
Ok(raw_p) => {
let p = serde_json::from_str::<Profile>(&raw_p)?;
let dir = util::config_dir().join(format!("profile-{}-{}", &p.meta.game, &p.meta.name));
if dir.exists() {
anyhow::bail!("profile {} already exists", &p.meta.name);
}
std::fs::create_dir(&dir)?;
archive.extract(&dir)?;
std::fs::remove_file(dir.join("template.json"))?;
std::fs::write(dir.join("profile.json"), serde_json::to_string_pretty(&p.data)?)?;
}
Err(e) => {
return Err(e);
}
}
Ok(())
}
pub fn export(&self, export_keychip: bool, extra_files: Vec<String>) -> anyhow::Result<()> {
let mut prf = self.clone();
let dir = util::config_dir().join("exports");
if !dir.exists() {
std::fs::create_dir(&dir)?;
}
let path = dir.join(format!("{}-{}-template.zip", &self.meta.game, &self.meta.name));
{
let sgt = &mut prf.data.sgt;
sgt.target = PathBuf::new();
if sgt.amfs.is_absolute() {
sgt.amfs = PathBuf::new();
}
if sgt.option.is_absolute() {
sgt.option = PathBuf::new();
}
if sgt.appdata.is_absolute() {
sgt.appdata = PathBuf::new();
}
}
{
let network = &mut prf.data.network;
if network.local_path.is_absolute() {
network.local_path = PathBuf::new();
}
if !export_keychip {
network.keychip = String::new();
}
}
let file = File::create(&path)?;
let mut zip = ZipWriter::new(file);
let options: FileOptions<'_, ()> = FileOptions::default();
zip.start_file("template.json", options)?;
zip.write_all(&serde_json::to_string_pretty(&prf)?.as_bytes())?;
for file in extra_files {
log::debug!("extra file: {file}");
zip.start_file(&file, options)?;
zip.write_all(&std::fs::read(self.config_dir().join(file))?)?;
}
zip.finish()?;
Ok(())
}
}

View File

@ -199,7 +199,7 @@ pub fn create_shortcut(
obj.SetDescription(&format!("{} {} (STARTLINER)", &meta.game.print(), &meta.name))?;
obj.SetArguments(&format!("--start --game {} --profile {}", &meta.game, &meta.name))?;
obj.SetIconLocation(
target_dir.join(format!("icon-{}.ico", &meta.game)).to_str().ok_or_else(|| anyhow!("Illegal icon path"))?,
target_dir.join(format!("icon-{}.ico", &meta.game)).to_str().ok_or_else(|| anyhow!("Illegal icon path"))?,
0
)?;

View File

@ -0,0 +1,105 @@
[
{
// CHUNITHM Luminous+
filename: 'amdaemon.exe',
version: '2.25.00',
sha256: '00FB867D1EE821033101B8773FAC116A45DF1939D23C38E9DAFC9B86CD5A3777',
patches: [
{
id: 'standard-localhost',
name: "Allow 127.0.0.1/localhost as the network server",
patches: [
{ offset: 0x6E28A4, off: [0x31, 0x32, 0x37, 0x2F], on: [0x30, 0x2F, 0x38, 0x00] },
{ offset: 0x3C94C4, off: [0xFF, 0x15, 0xC6, 0x2F, 0x1B, 0x00, 0x8B], on: [0x33, 0xC0, 0x48, 0x83, 0xC4, 0x28, 0xC3] }
]
},
{
id: 'standard-credit-freeze',
name: "Infinite credits",
patches: [
{ offset: 0x2BBBC8, off: [0x28], on: [0x08] }
]
}
]
},
{
// CHUNITHM Verse
filename: 'amdaemon.exe',
version: '2.30.00',
sha256: 'd4809220578374865370e31c541ed6e406b854d8c26cfe7464c2c15145113bfd',
patches: [
{
id: 'standard-localhost',
name: 'Allow 127.0.0.1/localhost as the network server',
patches: [
{
offset: 0x6e1ca4,
off: [0x31, 0x32, 0x37, 0x2f],
on: [0x30, 0x2f, 0x38, 0x00],
},
{
offset: 0x3c88c4,
off: [0xff, 0x15, 0xc6, 0x2f, 0x1b, 0x00, 0x8b],
on: [0x33, 0xc0, 0x48, 0x83, 0xc4, 0x28, 0xc3],
},
],
},
{
id: 'standard-credit-freeze',
name: 'Credit freeze',
tooltip: 'Prevents credits from being used. At least one credit must be available to start the game or purchase premium tickets.',
patches: [{ offset: 0x2bafc8, off: [0x28], on: [0x08] }],
},
{
id: 'standard-openssl-fix',
name: 'OpenSSL SHA crash bug fix',
tooltip: 'Fix crashes on 10th generation and newer Intel CPUs',
patches: [
{ offset: 0x4d4a43, off: [0x48], on: [0x4c] },
{ offset: 0x4d4a4b, off: [0x48], on: [0x49] },
],
},
],
},
{
// Ongeki
filename: "amdaemon.exe",
version: "46d47eab",
sha256: '962C76331306D0839AFD40808EA99D83E651D39C4708C448ADE0C77E8BC0A1B0',
patches: [
{
id: 'standard-localhost',
name: "Allow 127.0.0.1/localhost as the network server",
patches: [
{
offset: 0x69042C,
off: [0x31, 0x32, 0x37, 0x2F],
on: [0x30, 0x2F, 0x38, 0x00]
},
{
offset: 0x3A0E34,
off: [0xFF, 0x15, 0x86, 0xD5, 0x19, 0x00, 0x8B],
on: [0x33, 0xC0, 0x48, 0x83, 0xC4, 0x28, 0xC3]
}
]
},
{
id: 'standard-credit-freeze',
name: "Credit freeze",
tooltip: "Prevents credits from being used. At least one credit must be available to start the game or purchase premium tickets.",
patches: [
{ offset: 0x2AB4E8, off: [0x28], on: [0x08] }
]
},
{
id: 'standard-openssl-fix',
name: "OpenSSL SHA crash bug fix",
tooltip: "Fixes crashing on 10th generation and newer Intel CPUs",
patches: [
{ offset: 0x4A4C43, off: [0x48], on: [0x4C] },
{ offset: 0x4A4C4B, off: [0x48], on: [0x49] }
]
},
]
},
]

View File

@ -1,4 +1,132 @@
[
{
filename: 'chusanApp.exe',
version: '2.26.00',
sha256: 'AD2DCC02CE52B3FFF24A2919F8617854581DD2E2C0378EA13D84438FCCA2D522',
patches: [
{
id: 'standard-shared-audio',
name: "Force shared audio mode, system audio sample rate must be 48000Hz",
tooltip: "Improves compatibility, but may increase latency",
patches: [
{offset: 0xF233DA, off: [0x01], on: [0x00]}
]
},
{
id: 'standard-2ch',
name: "Force 2 channel audio output",
tooltip: "May cause bass overload",
patches: [
{offset: 0xF234B1, off: [0x75, 0x3f], on: [0x90, 0x90]}
]
},
{
id: 'standard-song-timer',
name: "Disable song select timer",
patches: [
{offset: 0xA03916, off: [0x74], on: [0xeb]}
]
},
{
id: 'standard-map-timer',
name: "Map selection timer",
tooltip: "If set to negative, the timer becomes 968 + value (e.g. 968 + -1 = 967)",
type: "number",
offset: 0x965B37,
default: 30,
size: 1,
min: -128,
max: 127,
},
{
id: 'standard-ticket-timer',
name: "Ticket selection timer",
tooltip: "If set to negative, the timer becomes 968 + value (e.g. 968 + -1 = 967)",
type: "number",
offset: 0x9592C2,
default: 60,
size: 1,
min: -128,
max: 127,
},
{
id: 'standard-course-timer',
name: "Course selection timer",
tooltip: "If set to negative, the timer becomes 968 + value (e.g. 968 + -1 = 967)",
type: "number",
offset: 0xA0EADB,
default: 30,
size: 1,
min: -128,
max: 127,
},
{
id: 'standard-unlimited-tracks',
name: "Unlimited maximum tracks",
tooltip: "Must check to play more than 7 tracks per credit",
patches: [
{offset: 0x71E2E0, off: [0xf0], on: [0xc0]}
]
},
{
id: 'standard-maximum-tracks',
type: "number",
name: "Maximum tracks",
offset: 0x3980C1,
default: 3,
size: 1,
min: 3,
max: 12
},
{
id: 'standard-no-encryption',
name: "No encryption",
tooltip: "Will also disable TLS",
patches: [
{offset: 0x1DE29E8, off: [0xE1], on: [0x00]},
{offset: 0x1DE29EC, off: [0xE1], on: [0x00]}
]
},
{
id: 'standard-no-tls',
name: "No TLS",
tooltip: "Title server workaround",
patches: [
{offset: 0xF06447, off: [0x80], on: [0x00]}
]
},
{
id: 'standard-head-to-head',
name: "Patch for head-to-head play",
tooltip: "Fix infinite sync while trying to connect to head to head play",
patches: [
{offset: 0x6533A3, off: [0x01], on: [0x00]}
]
},
{
id: 'standard-bypass-1080p',
name: "Bypass 1080p monitor check",
patches: [
{offset: 0x1CCBF, off: [0x81, 0xbc, 0x24, 0xb8, 0x02, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x75, 0x1f, 0x81, 0xbc, 0x24, 0xbc, 0x02, 0x00, 0x00, 0x38, 0x04, 0x00, 0x00, 0x75, 0x12], on: [0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90]}
]
},
{
id: 'standard-bypass-120hz',
name: "Bypass 120Hz monitor check",
patches: [
{offset: 0x1CCB1, off: [0x85, 0xc0], on: [0xeb, 0x30]}
]
},
{
id: 'standard-force-free-play-text',
name: "Force FREE PLAY credit text",
tooltip: "Replaces the credit count with FREE PLAY",
patches: [
{offset: 0x3875A4, off: [0x3c, 0x01], on: [0x38, 0xc0]}
]
},
],
},
{
filename: 'chusanApp.exe',
version: '2.30.00',
@ -176,43 +304,25 @@
},
],
},
],
},
{
filename: 'amdaemon.exe',
version: '2.30.00',
sha256: 'd4809220578374865370e31c541ed6e406b854d8c26cfe7464c2c15145113bfd',
patches: [
{
id: 'standard-localhost',
name: 'Allow 127.0.0.1/localhost as the network server',
patches: [
{
offset: 0x6e1ca4,
off: [0x31, 0x32, 0x37, 0x2f],
on: [0x30, 0x2f, 0x38, 0x00],
},
{
offset: 0x3c88c4,
off: [0xff, 0x15, 0xc6, 0x2f, 0x1b, 0x00, 0x8b],
on: [0x33, 0xc0, 0x48, 0x83, 0xc4, 0x28, 0xc3],
},
],
id: 'standard-custom-free-play-length',
type: 'number',
name: 'Custom FREE PLAY text length',
tooltip: 'Changes the length of the text displayed when Force FREE PLAY credit text is enabled',
danger: 'If this is longer than 11 characters, \"Force FREE PLAY credit text\" MUST be enabled.',
offset: 0x3875A9,
size: 1,
default: 9,
min: 0,
max: 27,
},
{
id: 'standard-credit-freeze',
name: 'Credit freeze',
tooltip: 'Prevents credits from being used. At least one credit must be available to start the game or purchase premium tickets.',
patches: [{ offset: 0x2bafc8, off: [0x28], on: [0x08] }],
},
{
id: 'standard-openssl-fix',
name: 'OpenSSL SHA crash bug fix',
tooltip: 'Fix crashes on 10th generation and newer Intel CPUs',
patches: [
{ offset: 0x4d4a43, off: [0x48], on: [0x4c] },
{ offset: 0x4d4a4b, off: [0x48], on: [0x49] },
],
id: 'standard-custom-free-play-text',
type: 'hex',
name: 'Custom FREE PLAY text',
tooltip: 'Replace the FREE PLAY text when using Infinite credits',
offset: 0x1A5DFB4,
off: [0x46, 0x52, 0x45, 0x45, 0x20, 0x50, 0x4c, 0x41, 0x59],
},
],
},

View File

@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "STARTLINER",
"version": "0.11.0",
"version": "0.15.0",
"identifier": "zip.patafour.startliner",
"build": {
"beforeDevCommand": "bun run dev",

View File

@ -221,15 +221,12 @@ listen<DownloadingStatus>('download-progress', (event) => {
>
<div class="fixed w-full flex z-100">
<TabList class="grow" :show-navigators="false">
<Tab value="users"><div class="pi pi-users"></div></Tab>
<Tab value="users"><div class="pi pi-home"></div></Tab>
<Tab :disabled="isProfileDisabled" value="loc"
><div class="pi pi-box"></div
></Tab>
<Tab
v-if="
prf.current?.meta.game === 'chunithm' &&
prf.current.data.sgt.target.length > 0
"
v-if="(prf.current?.data.sgt.target.length ?? 0) > 0"
value="patches"
><div class="pi pi-ticket"></div
></Tab>

View File

@ -12,7 +12,7 @@ invoke('get_changelog').then((s) => (changelog.value = s as string));
<template>
<h1>About</h1>
STARTLINER is a simple launcher, configuration tool and mod manager for
STARTLINER is a launcher, configuration tool and mod manager for
O.N.G.E.K.I. and CHUNITHM.
<h1>Changelog</h1>
<ScrollPanel style="height: 200px">

View File

@ -8,6 +8,9 @@ import { invoke } from '../invoke';
import { usePkgStore, usePrfStore } from '../stores';
import { Package } from '../types';
import { pkgKey } from '../util';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
search: String,
@ -55,7 +58,7 @@ const missing = computed(() => {
</script>
<template>
<Fieldset legend="Missing" v-if="(missing?.length ?? 0) > 0">
<Fieldset :legend="t('store.missing')" v-if="(missing?.length ?? 0) > 0">
<div class="flex items-center" v-for="p in missing">
<ModTitlecard
show-namespace

View File

@ -10,6 +10,9 @@ import { invoke } from '../invoke';
import { usePkgStore, usePrfStore } from '../stores';
import { Feature, Package } from '../types';
import { hasFeature } from '../util';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
const pkgs = usePkgStore();
@ -38,12 +41,7 @@ if (unsupported.value === true && model.value === true) {
<div class="flex items-center">
<ModTitlecard show-version show-icon show-description :pkg="pkg" />
<UpdateButton :pkg="pkg" />
<span
v-tooltip="
unsupported &&
'This package is currently incompatible with STARTLINER.'
"
>
<span v-tooltip="unsupported && t('store.incompatible')">
<ToggleSwitch
v-if="hasFeature(pkg, Feature.Mod) || unsupported === true"
class="scale-[1.33] shrink-0"

View File

@ -8,6 +8,9 @@ import ModStoreEntry from './ModStoreEntry.vue';
import { invoke } from '../invoke';
import { usePkgStore, usePrfStore } from '../stores';
import { pkgKey } from '../util';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const pkgs = usePkgStore();
const prf = usePrfStore();
@ -60,7 +63,7 @@ const getRecommendedTooltip = () => {
return 'segatools-mu3hook';
}
if (prf.current!.meta.game === 'chunithm') {
return 'segatools-chusanhook and mempatcher';
return 'segatools-chusanhook + mempatcher';
}
return '';
};
@ -80,15 +83,17 @@ const installRecommended = () => {
<div class="flex gap-4 items-center">
<div class="flex flex-col gap-2">
<div class="flex gap-2">
<div class="grow">Show installed</div>
<div class="grow">{{ t('store.installed') }}</div>
<ToggleSwitch v-model="pkgs.showInstalled" />
</div>
<div class="flex gap-2">
<div class="text-amber-400 grow">Show deprecated</div>
<div class="text-amber-400 grow">
{{ t('store.deprecated') }}
</div>
<ToggleSwitch v-model="pkgs.showDeprecated" />
</div>
<!-- <div class="flex gap-2">
<div class="text-red-400 grow">Show NSFW</div>
<div class="text-red-400 grow">{{ t('store.nsfw') }}</div>
<ToggleSwitch v-model="pkgs.showNSFW" />
</div> -->
</div>
@ -114,7 +119,7 @@ const installRecommended = () => {
<Divider />
<Button
v-if="shouldShowRecommended"
label="Install recommended packages"
:label="t('store.installRecommended')"
v-tooltip="getRecommendedTooltip"
icon="pi pi-plus"
class="mb-3"

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ComputedRef, computed, onMounted } from 'vue';
import { ComputedRef, computed, onMounted, ref } from 'vue';
import Button from 'primevue/button';
import Carousel from 'primevue/carousel';
import Dialog from 'primevue/dialog';
@ -11,7 +11,7 @@ import { VueMarkdownIt } from '@f3ve/vue-markdown-it';
const prf = usePrfStore();
const client = useClientStore();
defineProps({
const props = defineProps({
visible: Boolean,
firstTime: Boolean,
onFinish: Function,
@ -101,6 +101,14 @@ onMounted(async () => {
image: '/help-finale-chunithm.png',
};
});
const counter = ref(0);
const exitLabel = computed(() => {
return props.firstTime === true && counter.value < data.value.length - 1
? 'Skip'
: 'Close';
});
</script>
<template>
@ -115,7 +123,13 @@ onMounted(async () => {
"
:style="{ width: '760px', scale: client.scaleValue }"
>
<Carousel :value="data" :num-visible="1" :num-scroll="1">
<Carousel
:value="data"
:num-visible="1"
:num-scroll="1"
:page="counter"
v-on:update:page="(p) => (counter = p)"
>
<template #item="slotProps">
<div class="md-container markdown">
<vue-markdown-it
@ -135,9 +149,15 @@ onMounted(async () => {
</template>
</Carousel>
<div style="width: 100%; text-align: center">
<Button
v-if="counter < data.length - 1"
class="m-auto mr-4"
label="Next"
@click="() => (counter += 1)"
/>
<Button
class="m-auto"
label="OK"
:label="exitLabel"
@click="() => onFinish && onFinish()"
/>
</div>

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed } from 'vue';
import InputNumber from 'primevue/inputnumber';
import SelectButton from 'primevue/selectbutton';
import ToggleSwitch from 'primevue/toggleswitch';
import FileEditor from './FileEditor.vue';
@ -13,56 +14,41 @@ import NetworkOptions from './options/Network.vue';
import SegatoolsOptions from './options/Segatools.vue';
import StartlinerOptions from './options/Startliner.vue';
import { usePrfStore } from '../stores';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
const audioModel = computed({
const blacklistMinModel = computed({
get() {
return prf.current?.data.mu3_ini?.audio ?? null;
},
set(value: 'Shared' | 'Excl6Ch' | 'Excl2Ch') {
if (prf.current!.data.mu3_ini === undefined) {
prf.current!.data.mu3_ini = {};
if (prf.current?.data.mu3_ini?.blacklist === undefined) {
return null;
}
prf.current!.data.mu3_ini!.audio = value;
return prf.current?.data.mu3_ini?.blacklist[0];
},
set(value: number) {
prf.current!.data.mu3_ini!.blacklist = [
value,
prf.current!.data.mu3_ini!.blacklist?.[1] ?? 19999,
];
},
});
// const blacklistMinModel = computed({
// get() {
// if (prf.current?.data.mu3_ini?.blacklist === undefined) {
// return null;
// }
// return prf.current?.data.mu3_ini?.blacklist[0];
// },
// set(value: number) {
// if (prf.current!.data.mu3_ini === undefined) {
// prf.current!.data.mu3_ini = {};
// }
// prf.current!.data.mu3_ini!.blacklist = [
// value,
// prf.current!.data.mu3_ini!.blacklist?.[1] ?? 19999,
// ];
// },
// });
// const blacklistMaxModel = computed({
// get() {
// if (prf.current?.data.mu3_ini?.blacklist === undefined) {
// return null;
// }
// return prf.current?.data.mu3_ini?.blacklist[1];
// },
// set(value: number) {
// if (prf.current!.data.mu3_ini === undefined) {
// prf.current!.data.mu3_ini = {};
// }
// prf.current!.data.mu3_ini!.blacklist = [
// prf.current!.data.mu3_ini!.blacklist?.[0] ?? 10000,
// value,
// ];
// },
// });
const blacklistMaxModel = computed({
get() {
if (prf.current?.data.mu3_ini?.blacklist === undefined) {
return null;
}
return prf.current?.data.mu3_ini.blacklist[1];
},
set(value: number) {
prf.current!.data.mu3_ini!.blacklist = [
prf.current!.data.mu3_ini!.blacklist?.[0] ?? 10000,
value,
];
},
});
prf.reload();
</script>
@ -77,7 +63,7 @@ prf.reload();
title="Extensions"
v-if="prf.current!.meta.game === 'chunithm'"
>
<OptionRow title="Saekawa config">
<OptionRow :title="t('cfg.extensions.saekawa')">
<FileEditor
filename="saekawa.toml"
promptname="saekawa config file"
@ -85,51 +71,76 @@ prf.reload();
/> </OptionRow
></OptionCategory>
<OptionCategory
title="Extensions"
:title="t('cfg.extensions.title')"
v-if="prf.current!.meta.game === 'ongeki'"
>
<OptionRow title="Inohara config">
<OptionRow :title="t('cfg.extensions.inohara')">
<FileEditor
filename="inohara.cfg"
promptname="inohara config file"
extension="cfg"
/>
</OptionRow>
<OptionRow title="BepInEx console">
<OptionRow :title="t('cfg.extensions.bepInExConsole')">
<!-- @vue-expect-error -->
<ToggleSwitch v-model="prf.current!.data.bepinex.console" />
</OptionRow>
<OptionRow
title="Audio mode"
tooltip="Exclusive 2-channel mode requires a patch"
:title="t('cfg.extensions.audioMode')"
:tooltip="t('cfg.extensions.audioTooltip')"
>
<SelectButton
v-model="audioModel"
v-model="prf.current!.data.mu3_ini!.audio"
:options="[
{ title: 'Shared', value: 'Shared' },
{ title: 'Exclusive 6-channel', value: 'Excl6Ch' },
{ title: 'Exclusive 2-channel', value: 'Excl2Ch' },
{ title: t('cfg.extensions.audioShared'), value: 'Shared' },
{ title: t('cfg.extensions.audio6Ch'), value: 'Excl6Ch' },
{ title: t('cfg.extensions.audio2Ch'), value: 'Excl2Ch' },
]"
:allow-empty="true"
:allow-empty="false"
option-label="title"
option-value="value"
/></OptionRow>
<!-- <OptionRow
<OptionRow
:title="t('cfg.extensions.sampleRate')"
v-if="
prf.current?.data.mods.includes(
'7EVENDAYSHOLIDAYS-ExclusiveAudio'
)
"
>
<SelectButton
v-model="prf.current!.data.mu3_ini!.sample_rate"
:disabled="prf.current!.data.mu3_ini!.audio === 'Shared'"
:options="[
{ title: '44.1KHz', value: 44100 },
{ title: '48KHz', value: 48000 },
{ title: '96KHz', value: 96000 },
{ title: '192KHz', value: 192000 },
]"
:allow-empty="false"
option-label="title"
option-value="value"
/></OptionRow>
<OptionRow
v-if="
prf.current?.data.mods.includes('7EVENDAYSHOLIDAYS-Blacklist')
"
class="number-input"
title="Song ID Blacklist"
tooltip="Requires a patch"
:title="t('cfg.extensions.blacklist')"
:tooltip="t('cfg.extensions.blacklistTooltip')"
><InputNumber
class="shrink"
size="small"
:min="10000"
:min="9000"
:max="99999"
placeholder="10000"
:use-grouping="false"
:allow-empty="false"
v-model="blacklistMinModel" />
x
~
<InputNumber
class="shrink"
size="small"
@ -139,7 +150,36 @@ prf.reload();
:use-grouping="false"
:allow-empty="false"
v-model="blacklistMaxModel"
/></OptionRow> -->
/></OptionRow>
<OptionRow
class="number-input"
title="GP"
v-if="
prf.current?.data.mods.includes('7EVENDAYSHOLIDAYS-DisableGP')
"
><InputNumber
class="shrink"
size="small"
:min="0"
:max="9999"
:use-grouping="false"
:allow-empty="false"
v-model="prf.current!.data.mu3_ini!.gp"
/>
</OptionRow>
<OptionRow
:title="t('cfg.extensions.bonusTracks')"
:tooltip="t('cfg.extensions.bonusTracksTooltip')"
v-if="
prf.current?.data.mods.includes(
'7EVENDAYSHOLIDAYS-UnlockAllMusic'
)
"
>
<ToggleSwitch
v-model="prf.current!.data.mu3_ini!.enable_bonus_tracks"
/>
</OptionRow>
</OptionCategory>
<KeyboardOptions />
<StartlinerOptions />

View File

@ -1,9 +1,14 @@
<script setup lang="ts">
import { computed } from 'vue';
import InputNumber from 'primevue/inputnumber';
import InputText from 'primevue/inputtext';
import ToggleSwitch from 'primevue/toggleswitch';
import OptionRow from './OptionRow.vue';
import { usePrfStore } from '../stores';
import { Patch } from '../types';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
@ -23,20 +28,40 @@ const setNumber = (key: string, val: number) => {
}
};
defineProps({
const props = defineProps({
patch: Object as () => Patch,
});
// One day, I will repent
const hexModel = computed({
get() {
const hex = (prf.current!.data.patches[props.patch!.id!] as any)?.hex;
if (hex !== undefined) {
return new TextDecoder().decode(new Int8Array(hex).buffer);
} else {
return 'FREE PLAY';
}
},
set(value: string) {
(prf.current!.data.patches[props.patch!.id!] as any) = {
hex: new TextEncoder().encode(value),
};
},
});
// Doesn't need to be reactive
const nameKey = `patch.${props.patch?.id}`;
let name = t(nameKey);
if (name === nameKey) {
name = props.patch?.name ?? 'No name';
}
</script>
<template>
<OptionRow
:title="patch?.name"
:tooltip="patch?.tooltip"
:greytext="patch?.id"
>
<OptionRow :title="name" :tooltip="patch?.tooltip" :greytext="patch?.id">
<ToggleSwitch
v-if="patch?.type === undefined"
:model-value="prf.current!.data.patches[patch!.id!] !== undefined"
:model-value="prf.current!.data.patches?.[patch!.id!] !== undefined"
@update:model-value="(v: boolean) => toggleUnary(patch!.id!, v)"
/>
<InputNumber
@ -50,5 +75,6 @@ defineProps({
:max="patch?.max"
:placeholder="(patch?.default ?? 0).toString()"
/>
<InputText v-else-if="patch?.type === 'hex'" v-model="hexModel" />
</OptionRow>
</template>

View File

@ -6,6 +6,9 @@ import PatchEntry from './PatchEntry.vue';
import { invoke } from '../invoke';
import { usePrfStore } from '../stores';
import { Patch } from '../types';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
@ -30,18 +33,21 @@ invoke('list_patches', { target: prf.current!.data.sgt.target }).then(
})) as Patch[];
})();
const errorMessage =
"No compatible patches found. Make sure you're using unpacked and unpatched files.";
const errorMessage = t('patch.noneFound');
</script>
<template>
<OptionCategory title="chusanApp.exe" always-found>
<OptionCategory
v-if="prf.current?.meta.game === 'chunithm'"
title="chusanApp.exe"
always-found
>
<PatchEntry
v-if="gamePatches !== null"
v-for="p in gamePatches"
:patch="p"
/>
<div v-if="gamePatches === null">Loading...</div>
<div v-if="gamePatches === null">{{ t('patch.loading') }}</div>
<div v-if="gamePatches !== null && gamePatches.length === 0">
{{ errorMessage }}
</div>

View File

@ -1,33 +1,215 @@
<script setup lang="ts">
import { Ref, ref } from 'vue';
import Button from 'primevue/button';
import Dialog from 'primevue/dialog';
import Select from 'primevue/select';
import SelectButton from 'primevue/selectbutton';
import ToggleSwitch from 'primevue/toggleswitch';
import * as path from '@tauri-apps/api/path';
import { open } from '@tauri-apps/plugin-dialog';
import ProfileListEntry from './ProfileListEntry.vue';
import { usePrfStore } from '../stores';
import { invoke } from '../invoke';
import { useClientStore, useGeneralStore, usePrfStore } from '../stores';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
const client = useClientStore();
const general = useGeneralStore();
const exportVisible = ref(false);
const exportKeychip = ref(false);
const files = new Set<string>();
const exportTemplate = async () => {
const fl = [...files.values()];
exportVisible.value = false;
await invoke('export_profile', {
exportKeychip: exportKeychip.value,
files: fl,
});
await invoke('open_file', {
path: await path.join(general.configDir, 'exports'),
});
};
const fileList = {
ongeki: ['aime.txt', 'inohara.cfg', 'mu3.ini', 'segatools-base.ini'],
chunithm: ['aime.txt', 'saekawa.toml', 'segatools-base.ini'],
};
const fileListCurrent: Ref<string[]> = ref([]);
const recalcFileList = async () => {
const res: string[] = [];
files.clear();
for (const idx in fileList[prf.current!.meta.game]) {
const f = fileList[prf.current!.meta.game][idx];
const p = await path.join(await prf.configDir, f);
if (await invoke('file_exists', { path: p })) {
res.push(f);
files.add(f);
}
}
fileListCurrent.value = res;
};
const openExportDialog = async () => {
await recalcFileList();
exportVisible.value = true;
};
const importPick = async () => {
const res = await open({
multiple: false,
directory: false,
filters: [
{
name: t('profile.template'),
extensions: ['zip'],
},
],
});
if (res != null) {
await invoke('import_profile', { path: res });
await prf.reloadList();
}
};
</script>
<template>
<div v-if="prf.list.length === 0">
Welcome to STARTLINER! Start by creating a profile.
</div>
<div class="mt-4 flex flex-row flex-wrap align-middle gap-4">
<Button
label="O.N.G.E.K.I. profile"
icon="pi pi-plus"
class="ongeki-button profile-button"
@click="() => prf.create('ongeki')"
/>
<Button
label="CHUNITHM profile"
icon="pi pi-plus"
class="chunithm-button profile-button"
@click="() => prf.create('chunithm')"
/>
</div>
<div class="mt-12 flex flex-col flex-wrap align-middle gap-4">
<div v-for="p in prf.list">
<ProfileListEntry :p="p" />
<Dialog
modal
:visible="exportVisible"
:closable="false /*this shit doesn't work */"
:header="`Export ${prf.current?.meta.name}`"
:style="{ width: '300px', scale: client.scaleValue }"
>
<div class="flex flex-col gap-4">
<div class="flex flex-row">
<div class="grow">Export keychip</div>
<ToggleSwitch v-model="exportKeychip" />
</div>
<div class="flex flex-row" v-for="f in fileListCurrent">
<div class="grow">Export {{ f }}</div>
<ToggleSwitch
:model-value="true"
@update:model-value="
(v) => {
if (v === true) {
files.add(f);
} else {
files.delete(f);
}
}
"
/>
</div>
<div style="width: 100%; text-align: center">
<Button
class="m-auto mr-3"
style="width: 80px"
label="OK"
@click="() => exportTemplate()"
/>
<Button
class="m-auto"
style="width: 80px"
label="Cancel"
@click="() => (exportVisible = false)"
/>
</div>
</div>
</Dialog>
<div style="float: left">
<div v-if="prf.list.length === 0">
{{ t('profile.welcome') }}
</div>
<div class="mt-4 flex flex-row flex-wrap align-middle gap-4">
<Button
:label="t('profile.create', { game: t('game.ongeki') })"
icon="pi pi-file-plus"
class="ongeki-button profile-button"
@click="() => prf.create('ongeki')"
/>
<Button
:label="t('profile.create', { game: t('game.chunithm') })"
icon="pi pi-file-plus"
class="chunithm-button profile-button"
@click="() => prf.create('chunithm')"
/>
</div>
<div class="mt-4 flex flex-row flex-wrap align-middle gap-4">
<Button
label="Import template"
icon="pi pi-file-import"
class="import-button profile-button"
@click="() => importPick()"
/>
<Button
:disabled="prf.current === null"
label="Export template"
icon="pi pi-file-export"
class="profile-button"
@click="() => openExportDialog()"
/>
</div>
<div class="mt-12 flex flex-col flex-wrap align-middle gap-4">
<div v-for="p in prf.list">
<ProfileListEntry :p="p" />
</div>
</div>
</div>
<div style="float: right" class="mr-5 mt-3 flex flex-col gap-4 items-end">
<div>
<div class="pi pi-language mr-2"></div>
<Select
:model-value="client.locale"
@update:model-value="async (v) => await client.setLocale(v)"
style="width: 200px"
:options="[
{ title: 'English', value: 'en' },
// { title: '日本語', value: 'ja' },
]"
size="small"
option-label="title"
option-value="value"
></Select>
</div>
<SelectButton
style="height: 50px"
v-model="client.scaleModel"
:options="[
{ title: 'S', size: '0.8em', value: 's' },
{ title: 'M', size: '1.0em', value: 'm' },
{ title: 'L', size: '1.2em', value: 'l' },
{ title: 'XL', size: '1.4em', value: 'xl' },
]"
:allow-empty="false"
option-label="title"
option-value="value"
><template #option="slotProps">
<div :style="{ fontSize: slotProps.option.size }">
{{ slotProps.option.title }}
</div>
</template></SelectButton
>
<SelectButton
style="height: 50px"
:model-value="client.theme"
@update:model-value="(v) => client.setTheme(v)"
:options="[
{ title: 'System', value: 'system', icon: 'pi pi-home' },
{ title: 'Light', value: 'light', icon: 'pi pi-sun' },
{ title: 'Dark', value: 'dark', icon: 'pi pi-moon' },
]"
:allow-empty="false"
option-label="title"
option-value="value"
><template #option="slotProps">
<div :class="slotProps.option.icon"></div> </template
></SelectButton>
</div>
</template>
@ -57,4 +239,14 @@ const prf = usePrfStore();
background-color: var(--p-yellow-300) !important;
border-color: var(--p-yellow-300) !important;
}
.import-button {
background-color: var(--p-purple-400) !important;
border-color: var(--p-purple-400) !important;
}
.import-button:hover,
.import-button:active {
background-color: var(--p-purple-300) !important;
border-color: var(--p-purple-300) !important;
}
</style>

View File

@ -7,6 +7,9 @@ import * as path from '@tauri-apps/api/path';
import { invoke } from '../invoke';
import { useGeneralStore, usePrfStore } from '../stores';
import { ProfileMeta } from '../types';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const general = useGeneralStore();
const prf = usePrfStore();
@ -60,11 +63,21 @@ const deleteProfile = async () => {
const promptDeleteProfile = async () => {
confirmDialog.require({
message: `Are you sure you want to delete ${props.p?.game}-${props.p?.name}?`,
header: 'Delete profile',
message: t('profile.reallyDelete', {
profile: `${props.p?.game}-${props.p?.name}`,
}),
header: t('profile.delete'),
accept: deleteProfile,
});
};
const dataExists = ref(false);
path.join(general.dataDir, `profile-${props.p!.game}-${props.p!.name}`).then(
async (p) => {
dataExists.value = await invoke('file_exists', { path: p });
}
);
</script>
<template>
@ -124,10 +137,27 @@ const promptDeleteProfile = async () => {
@click="isEditing = true"
/>
<Button
rounded
icon="pi pi-cog"
severity="help"
aria-label="open-config-directory"
size="small"
class="self-center"
style="width: 2rem; height: 2rem"
@click="
path
.join(general.configDir, `profile-${p!.game}-${p!.name}`)
.then(async (path) => {
await invoke('open_file', { path });
})
"
/>
<Button
v-if="dataExists"
rounded
icon="pi pi-folder"
severity="help"
aria-label="open-directory"
aria-label="open-data-directory"
size="small"
class="self-center"
style="width: 2rem; height: 2rem"
@ -135,9 +165,7 @@ const promptDeleteProfile = async () => {
path
.join(general.dataDir, `profile-${p!.game}-${p!.name}`)
.then(async (path) => {
if (await invoke('file_exists', { path })) {
await invoke('open_file', { path });
}
await invoke('open_file', { path });
})
"
/>

View File

@ -8,6 +8,9 @@ import { getCurrentWindow } from '@tauri-apps/api/window';
import Onboarding from './Onboarding.vue';
import { invoke } from '../invoke';
import { useClientStore, usePrfStore } from '../stores';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
const client = useClientStore();
@ -24,22 +27,22 @@ const startline = async (force: boolean, refresh: boolean) => {
if (start_check.length > 0) {
const message = start_check.map((o) => {
if ('MissingRemotePackage' in o) {
return `Package missing: ${o.MissingRemotePackage}`;
return `${t('start.error.package')}: ${o.MissingRemotePackage}`;
} else if ('MissingLocalPackage' in o) {
return `Package missing: ${o.MissingLocalPackage}`;
return `${t('start.error.package')}: ${o.MissingLocalPackage}`;
} else if ('MissingDependency' in o) {
return `Dependency missing: ${(o.MissingDependency as string[]).join(' ')}`;
return `${t('start.error.dependency')}: ${(o.MissingDependency as string[]).join(' ')}`;
} else if ('MissingTool' in o) {
return `Tool missing: ${o.MissingTool}`;
return `${t('start.error.tool')}: ${o.MissingTool}`;
} else {
return 'Unknown error';
return t('start.error.unknown');
}
});
confirmDialog.require({
message: message.join('\n'),
header: 'Start check failed',
acceptLabel: 'Run anyway',
rejectLabel: 'Cancel',
header: t('start.failed'),
acceptLabel: t('start.accept'),
rejectLabel: t('cancel'),
accept: () => {
startline(true, refresh);
},
@ -62,16 +65,16 @@ const kill = async () => {
const disabledTooltip = computed(() => {
if (prf.current?.data.sgt.target.length === 0) {
return 'The game path must be specified';
return t('start.tooltip.game');
}
if (prf.current?.data.sgt.amfs.length === 0) {
return 'The amfs path must be specified';
return t('start.tooltip.amfs');
}
if (
prf.current?.data.sgt.hook === null ||
prf.current?.data.sgt.hook === undefined
) {
return 'A segatools hook package is necessary';
return t('start.tooltip.segatools');
}
return null;
});
@ -96,32 +99,50 @@ const createShortcut = async () => {
}
};
const menuItems = [
{
label: 'Refresh and start',
icon: 'pi pi-sync',
tooltip: 'test',
command: async () => await startline(false, true),
},
{
label: 'Start unchecked',
icon: 'pi pi-exclamation-circle',
command: async () => await startline(true, false),
},
{
label: 'Create desktop shortcut',
icon: 'pi pi-link',
command: createShortcut,
},
{
label: 'Help',
icon: 'pi pi-question-circle',
command: () => {
onboardingFirstTime.value = false;
onboardingVisible.value = true;
const menuItems = computed(() => {
const base = [
{
label: t('start.button.unchecked'),
icon: 'pi pi-exclamation-circle',
command: async () => await startline(true, false),
},
},
];
{
label: t('start.button.shortcut'),
icon: 'pi pi-link',
command: createShortcut,
},
{
label: t('start.button.help'),
icon: 'pi pi-question-circle',
command: () => {
onboardingFirstTime.value = false;
onboardingVisible.value = true;
},
},
];
if (prf.current === null) {
return [];
}
if (prf.current.meta.game === 'chunithm') {
return base;
}
if (prf.current.meta.game === 'ongeki') {
return [
{
label: t('start.button.refresh'),
icon: 'pi pi-sync',
command: async () => await startline(false, true),
},
...base,
{
label: t('start.button.cache'),
icon: 'pi pi-trash',
command: async () => {},
},
];
}
});
const menu = ref();
const showContextMenu = (event: Event) => {
@ -177,7 +198,7 @@ const tryStart = () => {
v-else-if="startStatus === 'preparing'"
disabled
icon="pi pi-spin pi-spinner"
label="START"
:label="t('start.button.start')"
aria-label="start"
size="small"
class="m-2.5"
@ -186,7 +207,7 @@ const tryStart = () => {
v-else
:disabled="false"
icon="pi pi-ban"
label="STOP"
:label="t('start.button.stop')"
aria-label="stop"
size="small"
class="m-2.5"

View File

@ -12,6 +12,9 @@ import { invoke } from '../../invoke';
import { usePkgStore, usePrfStore } from '../../stores';
import { Feature } from '../../types';
import { hasFeature, pkgKey } from '../../util';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const pkgs = usePkgStore();
const prf = usePrfStore();
@ -60,14 +63,18 @@ load();
<template>
<OptionCategory title="Aime">
<OptionRow
title="Aime type"
tooltip="Additional Aime plugins can be downloaded from the package store."
:title="t('cfg.aime.type')"
:tooltip="
t('cfg.segatools.installTooltip', {
thing: t('cfg.aime.modules'),
})
"
>
<Select
v-model="prf.current!.data.sgt.aime"
:options="[
{ title: 'hardware', value: 'Disabled' },
{ title: 'segatools built-in emulation', value: 'BuiltIn' },
{ title: t('cfg.hardware'), value: 'Disabled' },
{ title: t('cfg.segatools.builtIn'), value: 'BuiltIn' },
...pkgs.byFeature(Feature.Aime).map((p) => {
return {
title: pkgKey(p),
@ -83,8 +90,8 @@ load();
></Select>
</OptionRow>
<OptionRow
title="Aime code"
tooltip="Only applicable with the segatools built-in emulation or with compatible third-party packages"
:title="t('cfg.aime.code')"
:tooltip="t('cfg.aime.codeTooltip')"
v-if="prf.current!.data.sgt.aime !== 'Disabled'"
>
<InputText
@ -97,7 +104,7 @@ load();
/>
</OptionRow>
<div v-if="prf.current!.data.sgt.aime?.hasOwnProperty('AMNet')">
<OptionRow title="Server name">
<OptionRow :title="t('cfg.aime.serverName')">
<InputText
class="shrink"
size="small"
@ -106,7 +113,7 @@ load();
v-model="prf.current!.data.sgt.amnet.name"
/>
</OptionRow>
<OptionRow title="Server address">
<OptionRow :title="t('cfg.network.address')">
<InputText
class="shrink"
size="small"
@ -116,22 +123,21 @@ load();
/>
</OptionRow>
<OptionRow
title="Use AiMeDB for physical cards"
tooltip="Whether physical cards should use AiMeDB to retrieve access codes. If the game is using a hosted network, enable this option to load the same account data/profile as you would get on a physical cab."
:title="t('cfg.aime.aimedb')"
:tooltip="t('cfg.aime.aimedbTooltip')"
>
<ToggleSwitch v-model="prf.current!.data.sgt.amnet.physical" />
</OptionRow>
</div>
<OptionRow
title="Aime serial port"
tooltip="Ports can be checked in Devices and Printers or at googlechromelabs.github.io/serial-terminal
For AIC Pico, the AIME port should be selected."
:title="t('cfg.aime.serialPort')"
:tooltip="t('cfg.aime.serialPortTooltip')"
v-if="prf.current!.data.sgt.aime === 'Disabled'"
>
<Select
v-model="prf.current!.data.sgt.aime_port"
:options="[
{ title: 'default', value: null },
{ title: t('default'), value: null },
...Object.entries(coms ?? {}).map(([title, value]) => {
return {
title,
@ -139,7 +145,7 @@ load();
};
}),
]"
placeholder="default"
:placeholder="t('default')"
option-label="title"
option-value="value"
></Select>

View File

@ -8,11 +8,14 @@ import OptionCategory from '../OptionCategory.vue';
import OptionRow from '../OptionRow.vue';
import { invoke } from '../../invoke';
import { usePrfStore } from '../../stores';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const capabilities: Ref<string[]> = ref([]);
const displayList: Ref<{ title: string; value: string }[]> = ref([
{
title: 'Primary',
title: t('cfg.display.primary'),
value: 'default',
},
]);
@ -25,7 +28,7 @@ const extraDisplayOptionsDisabled = computed(() => {
const loadDisplays = () => {
const newList = [
{
title: 'Primary',
title: t('cfg.display.primary'),
value: 'default',
},
];
@ -74,7 +77,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
<OptionCategory title="Display">
<OptionRow
v-if="capabilities.includes('display')"
title="Target display"
:title="t('cfg.display.target')"
>
<Select
v-model="prf.current!.data.display.target"
@ -108,7 +111,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
v-model="prf.current!.data.display.rez[1]"
/>
</OptionRow>
<OptionRow title="Display mode">
<OptionRow :title="t('cfg.display.mode')">
<SelectButton
v-model="prf.current!.data.display.mode"
:options="[
@ -122,7 +125,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
/>
</OptionRow>
<OptionRow
title="Display rotation"
:title="t('cfg.display.rotation')"
v-if="capabilities.includes('display')"
>
<SelectButton
@ -130,12 +133,18 @@ const canSkipPrimarySwitch = game === 'ongeki';
:options="
isVertical
? [
{ title: 'Portrait', value: 90 },
{ title: 'Portrait (flipped)', value: 270 },
{ title: t('cfg.display.portrait'), value: 90 },
{
title: `${t('cfg.display.portrait')} (${t('cfg.display.flipped')})`,
value: 270,
},
]
: [
{ title: 'Landscape', value: 0 },
{ title: 'Landscape (flipped)', value: 180 },
{ title: t('cfg.display.landscape'), value: 0 },
{
title: `${t('cfg.display.landscape')} (${t('cfg.display.flipped')})`,
value: 180,
},
]
"
:allow-empty="true"
@ -147,7 +156,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
<OptionRow
v-if="capabilities.includes('display')"
class="number-input"
title="Refresh Rate"
:title="t('cfg.display.refreshRate')"
>
<InputNumber
v-if="game === 'ongeki'"
@ -173,9 +182,9 @@ const canSkipPrimarySwitch = game === 'ongeki';
/>
</OptionRow>
<OptionRow
title="Borderless fullscreen"
:title="t('cfg.display.borderlessFullscreen')"
v-if="capabilities.includes('display')"
tooltip="Match display resolution with the game."
:tooltip="t('cfg.display.borderlessFullscreenTooltip')"
>
<ToggleSwitch
:disabled="
@ -186,7 +195,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
/>
</OptionRow>
<OptionRow
title="Skip switching primary display"
:title="t('cfg.display.dontSwitchPrimary')"
v-if="
capabilities.includes('display') &&
prf.current?.data.display.target !== 'default' &&
@ -194,7 +203,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
displayList.length > 2) &&
canSkipPrimarySwitch
"
dangerous-tooltip="Only enable this option if switching the primary display causes issues. The monitors must have a matching refresh rate."
:dangerous-tooltip="t('cfg.display.dontSwitchPrimaryTooltip')"
>
<ToggleSwitch
:disabled="extraDisplayOptionsDisabled"
@ -202,7 +211,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
/>
</OptionRow>
<OptionRow
title="Display index"
:title="t('cfg.display.index')"
class="number-input"
v-if="
capabilities.includes('display') &&

View File

@ -5,27 +5,27 @@ import KeyboardKey from '../KeyboardKey.vue';
import OptionCategory from '../OptionCategory.vue';
import OptionRow from '../OptionRow.vue';
import { usePrfStore } from '../../stores';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
</script>
<template>
<OptionCategory title="Keyboard">
<OptionRow
title="Enable"
tooltip="Only applicable if the IO module is set to segatools built-in (keyboard) or a compatible third-party module (like mu3io.NET)"
>
<OptionCategory :title="t('cfg.keyboard.title')">
<OptionRow :title="t('enable')" :tooltip="t('cfg.keyboard.tooltip')">
<ToggleSwitch v-model="prf.current!.data.keyboard!.data.enabled" />
</OptionRow>
<OptionRow
title="Lever mode"
:title="t('cfg.keyboard.leverMode')"
v-if="prf.current!.data.keyboard!.game === 'Ongeki'"
>
<SelectButton
v-model="prf.current!.data.keyboard!.data.use_mouse"
:options="[
{ title: 'XInput', value: false },
{ title: 'Mouse', value: true },
{ title: t('cfg.keyboard.mouse'), value: true },
]"
:allow-empty="false"
option-label="title"

View File

@ -4,18 +4,24 @@ import FileEditor from '../FileEditor.vue';
import OptionCategory from '../OptionCategory.vue';
import OptionRow from '../OptionRow.vue';
import { usePrfStore } from '../../stores';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
</script>
<template>
<OptionCategory title="Misc">
<OptionRow title="OpenSSL bug workaround for Intel ≥10th gen">
<OptionCategory :title="t('cfg.misc.title')">
<OptionRow
:title="t('cfg.misc.intel')"
:dangerous-tooltip="t('cfg.misc.intelTooltip')"
>
<ToggleSwitch v-model="prf.current!.data.sgt.intel" />
</OptionRow>
<OptionRow
title="More segatools options"
tooltip="Advanced options not covered by STARTLINER"
:title="t('cfg.misc.other')"
:tooltip="t('cfg.misc.otherTooltip')"
>
<!-- <Button icon="pi pi-refresh" size="small" /> -->
<FileEditor filename="segatools-base.ini" />

View File

@ -6,18 +6,21 @@ import FilePicker from '../FilePicker.vue';
import OptionCategory from '../OptionCategory.vue';
import OptionRow from '../OptionRow.vue';
import { usePrfStore } from '../../stores';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
</script>
<template>
<OptionCategory title="Network">
<OptionRow title="Network type">
<OptionCategory :title="t('cfg.network.title')">
<OptionRow :title="t('cfg.network.type')">
<SelectButton
v-model="prf.current!.data.network.network_type"
:options="[
{ title: 'Remote', value: 'Remote' },
{ title: 'Local (ARTEMiS)', value: 'Artemis' },
{ title: t('cfg.network.remote'), value: 'Remote' },
{ title: t('cfg.network.localArtemis'), value: 'Artemis' },
]"
:allow-empty="false"
option-label="title"
@ -25,8 +28,8 @@ const prf = usePrfStore();
/>
</OptionRow>
<OptionRow
v-if="prf.current!.data.network.network_type == 'Artemis'"
title="ARTEMiS path"
v-if="prf.current!.data.network.network_type === 'Artemis'"
:title="t('cfg.network.artemisPath')"
>
<FilePicker
:directory="false"
@ -47,7 +50,7 @@ const prf = usePrfStore();
</OptionRow> -->
<OptionRow
v-if="prf.current!.data.network.network_type == 'Remote'"
title="Server address"
:title="t('cfg.network.address')"
>
<InputText
class="shrink"
@ -58,7 +61,7 @@ const prf = usePrfStore();
/> </OptionRow
><OptionRow
v-if="prf.current!.data.network.network_type == 'Remote'"
title="Keychip"
:title="t('cfg.network.keychip')"
>
<InputText
class="shrink"
@ -67,7 +70,7 @@ const prf = usePrfStore();
placeholder="A123-01234567890"
v-model="prf.current!.data.network.keychip"
/> </OptionRow
><OptionRow title="Subnet">
><OptionRow :title="t('cfg.network.subnet')">
<InputText
class="shrink"
size="small"
@ -76,7 +79,7 @@ const prf = usePrfStore();
v-model="prf.current!.data.network.subnet"
/>
</OptionRow>
<OptionRow title="Address suffix">
<OptionRow :title="t('cfg.network.addrSuffix')">
<InputNumber
class="shrink"
size="small"

View File

@ -11,6 +11,9 @@ import { invoke } from '../../invoke';
import { usePkgStore, usePrfStore } from '../../stores';
import { Feature } from '../../types';
import { pkgKey } from '../../util';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
const pkgs = usePkgStore();
@ -54,10 +57,10 @@ const checkSegatoolsIni = async (target: string) => {
</script>
<template>
<OptionCategory title="General">
<OptionCategory :title="t('cfg.segatools.general')">
<OptionRow
:title="names.exe"
tooltip="STARTLINER expects unpacked executables put into otherwise clean data."
:tooltip="t('cfg.segatools.targetTooltip')"
>
<FilePicker
:directory="false"
@ -104,7 +107,11 @@ const checkSegatoolsIni = async (target: string) => {
</OptionRow>
<OptionRow
:title="names.hook"
tooltip="Hooks can be downloaded from the package store."
:tooltip="
t('cfg.segatools.installTooltip', {
thing: t('cfg.segatools.hooks'),
})
"
>
<Select
v-model="prf.current!.data.sgt.hook"
@ -126,7 +133,11 @@ const checkSegatoolsIni = async (target: string) => {
</OptionRow>
<OptionRow
:title="names.io"
tooltip="IO plugins can be downloaded from the package store."
:tooltip="
t('cfg.segatools.installTooltip', {
thing: t('cfg.segatools.ioModules'),
})
"
>
<Select
v-model="prf.current!.data.sgt.io2"

View File

@ -1,92 +1,43 @@
<script setup lang="ts">
import { computed } from 'vue';
import SelectButton from 'primevue/selectbutton';
import ToggleSwitch from 'primevue/toggleswitch';
import OptionCategory from '../OptionCategory.vue';
import OptionRow from '../OptionRow.vue';
import { useClientStore } from '../../stores';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const client = useClientStore();
const offlineModel = computed({
get() {
return client.offlineMode;
},
async set(value: boolean) {
await client.setOfflineMode(value);
},
});
const updatesModel = computed({
get() {
return client.enableAutoupdates;
},
async set(value: boolean) {
await client.setAutoupdates(value);
},
});
const verboseModel = computed({
get() {
return client.verbose;
},
async set(value: boolean) {
await client.setVerbose(value);
},
});
const themeModel = computed({
get() {
return client.theme;
},
async set(value: 'light' | 'dark' | 'system') {
await client.setTheme(value);
},
});
</script>
<template>
<OptionCategory title="STARTLINER">
<OptionRow title="UI scaling">
<SelectButton
v-model="client.scaleModel"
:options="[
{ title: 'S', value: 's' },
{ title: 'M', value: 'm' },
{ title: 'L', value: 'l' },
{ title: 'XL', value: 'xl' },
]"
:allow-empty="false"
option-label="title"
option-value="value"
<OptionRow
:title="t('cfg.startliner.offlineMode')"
:tooltip="`${t('cfg.startliner.offlineModeTooltip')} ${t('cfg.afterRestart')}`"
>
<ToggleSwitch
:model-value="client.offlineMode"
@update:model-value="
async (v) => await client.setOfflineMode(v)
"
/>
</OptionRow>
<OptionRow
title="Offline mode"
tooltip="Disables the package store. Applies after a restart."
>
<ToggleSwitch v-model="offlineModel" />
</OptionRow>
<OptionRow title="Enable automatic updates">
<ToggleSwitch v-model="updatesModel" />
<OptionRow :title="t('cfg.startliner.autoUpdate')">
<ToggleSwitch
:model-value="client.enableAutoupdates"
@update:model-value="
async (v) => await client.setAutoupdates(v)
"
></ToggleSwitch>
</OptionRow>
<OptionRow
title="Enable detailed logs"
tooltip="Applies after a restart."
:title="t('cfg.startliner.verbose')"
:tooltip="t('cfg.afterRestart')"
>
<ToggleSwitch v-model="verboseModel" />
</OptionRow>
<OptionRow title="Theme">
<SelectButton
v-model="themeModel"
:options="[
{ title: 'System', value: 'system' },
{ title: 'Light', value: 'light' },
{ title: 'Dark', value: 'dark' },
]"
:allow-empty="false"
option-label="title"
option-value="value"
<ToggleSwitch
:model-value="client.verbose"
@update:model-value="async (v) => await client.setVerbose(v)"
/>
</OptionRow>
</OptionCategory>

26
src/i18n.ts Normal file
View File

@ -0,0 +1,26 @@
import en from './i18n/en';
import ja from './i18n/ja';
import { createI18n } from 'vue-i18n';
export type Locale = 'en' | 'ja';
const loadLocaleMessages = async (locale: Locale) => {
return (await import(`./i18n/${locale}.ts`)).default;
};
const i18n = createI18n({
legacy: false,
locale: 'en',
fallbackLocale: 'en',
messages: { en, ja },
});
const setLocale = async (locale: Locale) => {
if (locale !== 'en') {
const messages = await loadLocaleMessages(locale);
i18n.global.setLocaleMessage(locale, messages);
}
i18n.global.locale.value = locale;
};
export { i18n, setLocale };

153
src/i18n/en.ts Normal file
View File

@ -0,0 +1,153 @@
export default {
ok: 'OK',
cancel: 'Cancel',
enable: 'Enable',
disable: 'Disable',
default: 'Default',
start: {
failed: 'Start check failed',
accept: 'Run anyway',
error: {
package: 'Package missing',
dependency: 'Dependency missing',
tool: 'Tool missing',
unknown: 'Unknown error',
},
tooltip: {
game: 'The game path must be specified',
amfs: 'The amfs path must be specified',
segatools: 'A segatools hook package is necessary',
},
button: {
start: 'START',
stop: 'STOP',
unchecked: 'Start unchecked',
shortcut: 'Create desktop shortcut',
help: 'Help',
refresh: 'Refresh and start',
cache: 'Clear cache',
},
},
game: {
ongeki: 'O.N.G.E.K.I.',
chunithm: 'CHUNITHM',
},
profile: {
welcome: 'Welcome to STARTLINER! Start by creating a profile.',
create: '{game} profile',
delete: 'Delete profile',
reallyDelete: 'Are you sure you want to delete {profile}?',
template: 'STARTLINER template',
},
store: {
installRecommended: 'Install recommended packages',
installed: 'Show installed',
deprecated: 'Show deprecated',
nsfw: 'Show NSFW',
incompatible: 'This package is currently incompatible with STARTLINER.',
missing: 'Missing',
},
patch: {
loading: 'Loading...',
noneFound:
"No compatible patches found. Make sure you're using unpacked and unpatched files.",
// Example patch name override
'standard-no-encryption': 'No encryption',
},
cfg: {
afterRestart: 'Applied after a restart',
hardware: 'Hardware',
segatools: {
general: 'General',
builtIn: 'Segatools built-in emulation',
targetTooltip:
'STARTLINER expects unpacked executables put into otherwise clean data.',
hooks: 'Hooks',
ioModules: 'IO modules',
installTooltip: '{thing} can be downloaded from the package store.',
},
display: {
primary: 'Primary',
target: 'Target display',
mode: 'Mode',
rotation: 'Rotation',
refreshRate: 'Refresh rate',
borderlessFullscreen: 'Borderless fullscreen',
borderlessFullscreenTooltip:
'Match display resolution with the game.',
dontSwitchPrimary: 'Skip switching primary display',
dontSwitchPrimaryTooltip:
'Only enable this option if switching the primary display causes issues. The monitors must have a matching refresh rate.',
index: 'Display index',
portrait: 'Portrait',
landscape: 'Landscape',
flipped: 'flipped',
},
network: {
title: 'Network',
type: 'Network type',
remote: 'Remote',
localArtemis: 'Local (ARTEMiS)',
artemisPath: 'ARTEMiS path',
address: 'Server address',
keychip: 'Keychip',
subnet: 'Subnet',
addrSuffix: 'Address suffix',
},
aime: {
type: 'Aime type',
modules: 'Aime modules',
code: 'Aime code',
codeTooltip:
'Only applicable with the segatools built-in emulation or with compatible third-party packages',
aimedb: 'Use AiMeDB for physical cards',
aimedbTooltip:
'Whether physical cards should use AiMeDB to retrieve access codes. If the game is using a hosted network, enable this option to load the same account data/profile as you would get on a physical cab.',
serialPort: 'Aime serial port',
serialPortTooltip: `Ports can be checked in Devices and Printers or at googlechromelabs.github.io/serial-terminal
For AIC Pico, the AIME port should be selected.`,
serverName: 'Server name',
},
misc: {
title: 'Miscellaneous',
intel: 'OpenSSL bug workaround for Intel ≥10th gen',
intelTooltip: 'It is recommended to patch amdaemon instead.',
other: 'Other segatools options',
otherTooltip:
'Advanced or situational options not covered by STARTLINER',
},
extensions: {
title: 'Extensions',
bepInExConsole: 'BepInEx console',
audioMode: 'Audio mode',
audioTooltip:
'Exclusive 2-channel mode requires 7EVENDAYSHOLIDAYS-ExclusiveAudio',
audioShared: 'Shared',
audio6Ch: 'Exclusive 6-channel',
audio2Ch: 'Exclusive 2-channel',
sampleRate: 'Sample rate',
blacklist: 'Song ID blacklist',
blacklistTooltip:
'Scores on charts within this ID range will not be saved nor uploaded',
bonusTracks: 'Unlock bonus tracks',
bonusTracksTooltip:
'Disabling this option can help declutter the song list',
saekawa: 'Saekawa configuration file',
inohara: 'Inohara configuration file',
},
keyboard: {
title: 'Keyboard',
tooltip:
'Only applicable if the IO module is set to segatools built-in (keyboard) or a compatible third-party module (like mu3io.NET)',
leverMode: 'Lever mode',
mouse: 'Mouse',
},
startliner: {
offlineMode: 'Offline mode',
offlineModeTooltip: 'Disables the package store.',
autoUpdate: 'Automatic updates',
verbose: 'Detailed logs',
},
},
};

14
src/i18n/ja.ts Normal file
View File

@ -0,0 +1,14 @@
export default {
game: {
ongeki: 'オンゲキ',
chunithm: 'チュウニズム',
},
profile: {
create: '{game}のプロフィル',
},
cfg: {
aime: {
type: 'Aimeタイプ',
},
},
};

View File

@ -6,14 +6,15 @@ import PrimeVue from 'primevue/config';
import ConfirmationService from 'primevue/confirmationservice';
import Tooltip from 'primevue/tooltip';
import App from './components/App.vue';
import { i18n } from './i18n';
import { changePrimaryColor } from './util';
const pinia = createPinia();
const app = createApp(App);
const Preset = definePreset(Theme, {});
app.use(pinia);
app.use(i18n);
app.use(PrimeVue, {
theme: {
preset: Preset,

View File

@ -4,6 +4,7 @@ import { listen } from '@tauri-apps/api/event';
import * as path from '@tauri-apps/api/path';
import { PhysicalSize, getCurrentWindow } from '@tauri-apps/api/window';
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
import { Locale, setLocale as actualSetLocale } from './i18n';
import { invoke, invoke_nopopup } from './invoke';
import { Dirs, Feature, Game, Package, Profile, ProfileMeta } from './types';
import {
@ -371,6 +372,7 @@ export const useClientStore = defineStore('client', () => {
const verbose = ref(false);
const theme: Ref<'light' | 'dark' | 'system'> = ref('system');
const onboarded: Ref<Game[]> = ref([]);
const locale: Ref<Locale> = ref('en');
const _scaleValue = (value: ScaleType) =>
value === 's' ? 1 : value === 'm' ? 1.25 : value === 'l' ? 1.5 : 2;
@ -433,6 +435,11 @@ export const useClientStore = defineStore('client', () => {
if (input.onboarded) {
onboarded.value = input.onboarded;
}
if (input.locale) {
locale.value = input.locale;
}
await setLocale(locale.value);
await setTheme(theme.value);
} catch (e) {
console.error(`Error reading client options: ${e}`);
@ -466,6 +473,7 @@ export const useClientStore = defineStore('client', () => {
},
theme: theme.value,
onboarded: onboarded.value,
locale: locale.value,
})
);
};
@ -518,6 +526,12 @@ export const useClientStore = defineStore('client', () => {
await save();
};
const setLocale = async (loc: Locale) => {
locale.value = loc;
await save();
await actualSetLocale(loc);
};
getCurrentWindow().onResized(async ({ payload }) => {
// For whatever reason this is 0 when minimized
if (payload.width > 0) {
@ -532,6 +546,7 @@ export const useClientStore = defineStore('client', () => {
verbose,
theme,
onboarded,
locale,
timeout,
scaleModel,
_scaleValue,
@ -544,5 +559,6 @@ export const useClientStore = defineStore('client', () => {
setVerbose,
setTheme,
setOnboarded,
setLocale,
};
});

View File

@ -107,7 +107,10 @@ export interface BepInExConfig {
export interface Mu3IniConfig {
audio?: 'Shared' | 'Excl6Ch' | 'Excl2Ch';
// blacklist?: [number, number];
sample_rate: number;
blacklist?: [number, number];
gp: number;
enable_bonus_tracks: boolean;
}
export interface OngekiButtons {
@ -164,7 +167,7 @@ export interface Patch {
id: string;
name: string;
tooltip: string;
type: undefined | 'number';
type: undefined | 'number' | 'hex';
default: number;
min: number;
max: number;

View File

@ -3,11 +3,7 @@ import { Feature, Game, Package } from './types';
export const changePrimaryColor = (game: Game | null) => {
const color =
game === 'ongeki'
? 'pink'
: game === 'chunithm'
? 'yellow'
: 'bluegray';
game === 'ongeki' ? 'pink' : game === 'chunithm' ? 'yellow' : 'purple';
updatePrimaryPalette({
50: `{${color}.50}`,