21 Commits

Author SHA1 Message Date
edef5cc6dc fix: also replace download URLs 2025-04-30 07:35:54 +00:00
2dad0de4f1 fix: update rainycolor's domain 2025-04-30 06:59:38 +00:00
14a65eb5bb fix: keyboard unbinding and IR fixes 2025-04-29 19:59:21 +00:00
0add9200a6 feat: new grouping options 2025-04-28 22:00:33 +00:00
ee49da3665 fix: category sort 2025-04-28 16:47:45 +00:00
f478ad9216 feat: add package creator 2025-04-28 16:44:04 +00:00
c59dbcc35c feat: add polish localization 2025-04-27 20:37:46 +00:00
91d38b58c4 fix: localization fixes 2025-04-27 20:30:22 +00:00
240f60b283 fix: some more polish 2025-04-27 18:53:01 +00:00
6a32ad65a5 feat: onboarding i18n 2025-04-27 07:35:38 +00:00
6cc7a537b6 feat: remember current tab 2025-04-27 05:56:04 +00:00
bf4c06ee2d fix: begin fixing linux support 2025-04-23 17:17:59 +02:00
f26d83f291 fix: misc cleanup 2025-04-23 14:24:35 +00:00
8b2c1a04ee fix: remove chuniio from segatools-chunithm.ini 2025-04-23 13:37:19 +00:00
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
63 changed files with 2713 additions and 855 deletions

View File

@ -1,3 +1,58 @@
## 0.18.3
- Updated Rainycolor's domain・真
## 0.18.2
- Updated Rainycolor's domain
## 0.18.1
- Keys can now be unbinded with Esc
- Fixed CHUNITHM IR behavior on actual keyboards
## 0.18.0
- Added new grouping options to the package list
## 0.17.0
- Added a package creation prompt
- Added a default package icon
## 0.16.0
- Fixed the clear cache button not working
- Fixed Linux builds
- Moved the store tab to the left
- "Reapply mods and start" renamed from "Refresh and start" to better convey the meaning
- "Reapply mods and start" is no longer necessary when enabling packages from the `local` namespace
- Various internationalization additions
- STARTLINER now remembers the recently open tab and re-opens it on the next session
- Added "Beta" to the title as STARTLINER is approaching feature-completeness
- Added full Polish localization :smciota:
## 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+)

View File

@ -1,10 +1,15 @@
Looking for
- maimai DX players willing to help develop/test maimai DX support
- translators (any language other than English)
# STARTLINER
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 mod installer and updater, powered by [Rainycolor Watercolor](https://rainycolor.org),
- 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).

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"
}

View File

@ -1,3 +0,0 @@
If you're stuck on this screen, restart the game.
If the problem persists, <a href="https://gitea.tendokyu.moe/Dniel97/SEGAguide/wiki/FAQ#game-is-stuck-at-checking-distribution-server" target="_blank">check your network configuration</a>

View File

@ -1,8 +0,0 @@
You can access this page any time by right-clicking the START button.
Additional resources:
- <a href="https://gitea.tendokyu.moe/Dniel97/SEGAguide/wiki/FAQ" target="_blank">SEGAguide</a>
- <a href="https://two-torial.xyz/" target="_blank">two-torial</a>
## Have fun

View File

@ -1,3 +0,0 @@
You also have to calibrate the lever, or you may get the error 3301.
Go to lever settings (<span class="bg-black text-white">レバー設定</span>), move the lever to both edges, then press "end" (<span class="bg-black text-white">終了</span>) and "save" (<span class="bg-black text-white">保存する</span>).

View File

@ -1,3 +0,0 @@
You might get stuck on this screen for several minutes. _This is normal_. The game just takes a long time to load data.
If you install <code>7EVENDAYSHOLIDAYS/LoadBoost</code>, subsequent launches will be much faster.

View File

@ -1,7 +0,0 @@
You might get stuck on the following screen:
<div class="p-2 mt-1 mb-1 bg-black text-white">Aグループの基準機から設定を取得</div>
In which case, you should go to the test menu, and in game settings <span class="bg-black text-white">ゲーム設定</span> switch from "follow the standard machine" <span class="bg-black text-white">基準機に従う</span> to "standard machine" <span class="bg-black text-white">基準機</span>.
The test menu can be accessed with %TESTMENU%.

BIN
public/no-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

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

@ -1,11 +1,12 @@
use ini::Ini;
use log;
use std::collections::{BTreeMap, HashMap};
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::path::PathBuf;
use tokio::sync::Mutex;
use tokio::fs;
use tauri::{AppHandle, Manager, State};
use crate::model::config::GlobalConfigField;
use crate::model::local::PackageManifest;
use crate::model::misc::Game;
use crate::model::patch::Patch;
use crate::modules::package::prepare_dlls;
@ -65,13 +66,20 @@ 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())?;
#[cfg(target_os = "windows")]
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")]
@ -159,6 +167,65 @@ pub async fn toggle_package(state: State<'_, tokio::sync::Mutex<AppData>>, key:
.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn create_package(
name: String,
description: String,
website: String,
r#type: String,
games: Vec<Game>
) -> Result<(), String> {
log::debug!("invoke: create_package");
let dir = util::pkg_dir_of("local", &name);
if dir.exists() {
return Err("Package already exists".to_owned());
}
let mut installers = Vec::new();
if r#type == "segatools" {
let mut map = BTreeMap::new();
map.insert(
"identifier".to_owned(),
serde_json::Value::String("segatools".to_owned())
);
installers.push(map);
} else if r#type == "native" {
let mut map = BTreeMap::new();
map.insert(
"identifier".to_owned(),
serde_json::Value::String("native_mod".to_owned())
);
map.insert(
"dll-game".to_owned(),
serde_json::Value::String("some.dll".to_owned())
);
map.insert(
"dll-amdaemon".to_owned(),
serde_json::Value::String("another.dll".to_owned())
);
installers.push(map);
}
let manifest = PackageManifest {
name,
version_number: "1.0.0".to_owned(),
description,
website_url: website,
dependencies: BTreeSet::new(),
installers,
games: Some(games)
};
std::fs::create_dir(&dir).map_err(|e| e.to_string())?;
let json = serde_json::to_string_pretty(&manifest).map_err(|e| e.to_string())?;
std::fs::write(dir.join("manifest.json"), json).map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
pub async fn reload_all_packages(state: State<'_, tokio::sync::Mutex<AppData>>) -> Result<(), String> {
log::debug!("invoke: reload_all_packages");
@ -184,12 +251,16 @@ pub async fn get_all_packages(state: State<'_, Mutex<AppData>>) -> Result<HashMa
#[tauri::command]
pub async fn get_game_packages(state: State<'_, Mutex<AppData>>, game: Game) -> Result<Vec<PkgKey>, ()> {
log::debug!("invoke: get_game_packages {game}");
pub async fn get_game_packages(state: State<'_, Mutex<AppData>>, game: Option<Game>) -> Result<Vec<PkgKey>, ()> {
log::debug!("invoke: get_game_packages {game:?}");
let appd = state.lock().await;
Ok(appd.pkgs.get_game_list(game))
if let Some(game) = game {
Ok(appd.pkgs.get_game_list(game))
} else {
Ok(Vec::new())
}
}
#[tauri::command]
@ -398,10 +469,61 @@ pub async fn load_segatools_ini(state: State<'_, Mutex<AppData>>, path: PathBuf)
}
#[tauri::command]
pub async fn create_shortcut(app: AppHandle, profile_meta: ProfileMeta) -> Result<(), String> {
pub async fn create_shortcut(_app: AppHandle, profile_meta: ProfileMeta) -> Result<(), String> {
log::debug!("invoke: create_shortcut({:?})", profile_meta);
util::create_shortcut(app, &profile_meta).map_err(|e| e.to_string())
#[cfg(target_os = "windows")]
return util::create_shortcut(_app, &profile_meta).map_err(|e| e.to_string());
#[cfg(not(target_os = "windows"))]
return Err("unsupported".to_owned());
}
#[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 clear_cache(state: State<'_, Mutex<AppData>>) -> Result<(), String> {
log::debug!("invoke: clear_cache");
let appd = state.lock().await;
if let Some(p) = &appd.profile {
let dir = p.data_dir().join("mu3-mods-cache");
let path = dir.join("data_cache.bin");
if path.exists() {
std::fs::remove_file(path).map_err(|e| e.to_string())?;
}
let path = dir.join("data_fumen_analysis_cache.bin");
if path.exists() {
std::fs::remove_file(path).map_err(|e| e.to_string())?;
}
}
Ok(())
}
#[tauri::command]
@ -409,7 +531,7 @@ pub async fn list_platform_capabilities() -> Result<Vec<String>, ()> {
log::debug!("invoke: list_platform_capabilities");
#[cfg(target_os = "windows")]
return Ok(vec!["display".to_owned()]);
return Ok(vec!["display".to_owned(), "shortcut".to_owned(), "chunithm".to_owned()]);
#[cfg(target_os = "linux")]
return Ok(vec!["wine".to_owned()]);

View File

@ -193,6 +193,7 @@ pub async fn run(_args: Vec<String>) {
cmd::install_package,
cmd::delete_package,
cmd::toggle_package,
cmd::create_package,
cmd::list_profiles,
cmd::init_profile,
@ -205,6 +206,9 @@ 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::clear_cache,
cmd::get_global_config,
cmd::set_global_config,
@ -333,7 +337,7 @@ async fn update(app: tauri::AppHandle) -> tauri_plugin_updater::Result<()> {
fn open_window(apph: AppHandle) -> anyhow::Result<()> {
let config = apph.config().clone();
tauri::WebviewWindowBuilder::new(&apph, "main", tauri::WebviewUrl::App("index.html".into()))
.title(format!("STARTLINER {}", config.version.unwrap_or_default()))
.title(format!("STARTLINER {} Beta", config.version.unwrap_or_default()))
.inner_size(900f64, 600f64)
.min_inner_size(900f64, 600f64)
.build()?;

View File

@ -6,10 +6,11 @@ use super::misc::Game;
// manifest.json
#[derive(Deserialize)]
#[derive(Serialize, Deserialize)]
pub struct PackageManifest {
pub name: String,
pub version_number: String,
pub website_url: String,
pub description: String,
pub dependencies: BTreeSet<PkgKeyVersion>,

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

@ -108,7 +108,10 @@ impl Display {
Game::Ongeki => 60,
},
borderless_fullscreen: true,
#[cfg(target_os = "windows")]
dont_switch_primary: false,
#[cfg(not(target_os = "windows"))]
dont_switch_primary: true,
monitor_index_override: None,
}
}
@ -141,7 +144,7 @@ pub struct BepInEx {
pub console: bool,
}
#[derive(Deserialize, Serialize, Clone)]
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(default)]
pub struct Wine {
pub runtime: PathBuf,

View File

@ -0,0 +1,8 @@
use ini::Ini;
use crate::model::{misc::Game, profile::Display};
impl Display {
pub fn line_up(&self, _game: Game, _ini: &mut Ini) {
// nop
}
}

View File

@ -117,12 +117,16 @@ impl Keyboard {
}
}
Keyboard::Chunithm(kb) => {
let mut enabled_ir = false;
if kb.enabled {
for (i, cell) in kb.cell.iter().enumerate() {
ini.with_section(Some("slider")).set(format!("cell{}", i + 1), cell.to_string());
}
for (i, ir) in kb.ir.iter().enumerate() {
ini.with_section(Some("ir")).set(format!("ir{}", i + 1), ir.to_string());
ini.with_section(Some("ir")).set(format!("ir{}", i + 1), (*ir).to_string());
if i > 0 && *ir != 0 {
enabled_ir = true;
}
}
ini.with_section(Some("io3"))
.set("test", kb.test.to_string())
@ -140,8 +144,13 @@ impl Keyboard {
.set("service", "0")
.set("coin", "0");
}
ini.with_section(Some("io3"))
.set("ir", "0");
if enabled_ir {
ini.with_section(Some("io3"))
.set("ir", "0");
} else {
ini.with_section(Some("io3"))
.set("ir", kb.ir[0].to_string());
}
}
}

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

@ -7,4 +7,7 @@ pub mod keyboard;
pub mod mempatcher;
#[cfg(target_os = "windows")]
pub mod display_windows;
pub mod display_windows;
#[cfg(target_os = "linux")]
pub mod display_linux;

View File

@ -1,17 +1,26 @@
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;
use crate::profiles::types::ProfilePaths;
pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgKey>, redo_bepinex: bool) -> Result<()> {
pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgKey>, mut redo_bepinex: bool) -> Result<()> {
log::debug!("begin prepare packages");
let pfx_dir = p.data_dir();
let opt_dir = pfx_dir.join("option");
for m in pkgs {
let (namespace, _) = m.split()?;
if namespace == "local" {
log::info!("package with the 'local' namespace enabled -- force refreshing");
redo_bepinex = true;
}
}
if redo_bepinex {
if pfx_dir.join("BepInEx").exists() {
util::remove_dir_all(pfx_dir.join("BepInEx")).await?;
@ -60,6 +69,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 +82,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

@ -12,6 +12,7 @@ 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?;
@ -30,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.to_ascii_lowercase());
if plist.sha256.to_ascii_lowercase() == 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>
}
@ -107,7 +109,7 @@ impl Package {
loc: None,
rmt: Some(Remote {
package_url: p.package_url,
download_url: v.download_url,
download_url: v.download_url.replace("https://rainy.patafour.zip/", "https://www.rainycolor.org/"),
icon: v.icon,
deprecated: p.is_deprecated,
nsfw: p.has_nsfw_content,
@ -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

@ -132,7 +132,7 @@ impl PackageStore {
prelude::*,
};
let response = reqwest::get(format!("https://rainy.patafour.zip/c/{game}/api/v1/package/")).await?;
let response = reqwest::get(format!("https://www.rainycolor.org/c/{game}/api/v1/package/")).await?;
let reader = response
.bytes_stream()

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::package::prepare_packages, pkg::PkgKey, pkg_store::PackageStore, util};
use tauri::Emitter;
use std::process::Stdio;
use crate::model::profile::BepInEx;
@ -10,8 +10,23 @@ use std::fs::File;
use tokio::process::Command;
use tokio::task::JoinSet;
#[cfg(target_os = "windows")]
use crate::modules::display_windows::DisplayInfo;
pub mod template;
pub mod types;
#[cfg(target_os = "linux")]
pub trait RawArg {
fn raw_arg<S: AsRef<std::ffi::OsStr>>(&mut self, arg: S) -> &mut Command;
}
#[cfg(target_os = "linux")]
impl RawArg for Command {
fn raw_arg<S: AsRef<std::ffi::OsStr>>(&mut self, arg: S) -> &mut Command {
return self.arg::<S>(arg);
}
}
impl Profile {
pub fn new(mut meta: ProfileMeta) -> Result<Self> {
meta.name = fixed_name(&meta, true);
@ -35,7 +50,7 @@ impl Profile {
} 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()
};
@ -43,12 +58,6 @@ impl Profile {
std::fs::create_dir_all(p.config_dir())?;
std::fs::create_dir_all(p.data_dir())?;
if meta.game == Game::Ongeki {
if let Err(e) = Self::load_existing_mu3_ini(&p.data, &p.meta) {
log::error!("unable to load existing mu3.ini: {e}");
}
}
match meta.game {
Game::Ongeki => std::fs::write(p.config_dir().join("segatools-base.ini"), include_bytes!("../../static/segatools-ongeki.ini"))?,
Game::Chunithm => std::fs::write(p.config_dir().join("segatools-base.ini"), include_bytes!("../../static/segatools-chunithm.ini"))?,
@ -83,6 +92,9 @@ impl Profile {
} 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() })?;
}
@ -178,6 +190,7 @@ impl Profile {
self.data.patches = source.patches;
}
}
#[cfg(target_os = "windows")]
pub fn prepare_display(&self) -> Result<Option<DisplayInfo>> {
let info = match &self.data.display {
None => None,
@ -186,7 +199,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?;
}
@ -226,8 +239,8 @@ impl Profile {
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"))
)?;
}
@ -254,8 +267,8 @@ impl Profile {
}
#[cfg(target_os = "linux")]
{
game_builder = Command::new(&self.wine.runtime);
amd_builder = Command::new(&self.wine.runtime);
game_builder = Command::new(&self.data.wine.runtime);
amd_builder = Command::new(&self.data.wine.runtime);
game_builder.arg(sgt_dir.join(self.meta.game.inject_exe()));
amd_builder.arg("cmd.exe");
@ -351,8 +364,8 @@ impl Profile {
#[cfg(target_os = "linux")]
{
amd_builder.env("WINEPREFIX", &self.wine.prefix);
game_builder.env("WINEPREFIX", &self.wine.prefix);
amd_builder.env("WINEPREFIX", &self.data.wine.prefix);
game_builder.env("WINEPREFIX", &self.data.wine.prefix);
}
let amd_log = File::create(self.data_dir().join("amdaemon.exe.log"))?;
@ -431,12 +444,14 @@ impl Profile {
}
fn load_existing_mu3_ini(data: &ProfileData, meta: &ProfileMeta) -> Result<()> {
let mu3_ini_target_path = data.sgt.target.parent().ok_or_else(|| anyhow!("invalid target directory"))?.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);
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(())
}

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

@ -152,6 +152,7 @@ impl PathStr for PathBuf {
}
}
#[allow(dead_code)]
pub fn bool_to_01(val: bool) -> &'static str {
return if val { "1" } else { "0" }
}

View File

@ -64,19 +64,4 @@ controllerLedOutputOpeNITHM=0
; [60]-[62]: right side partition LEDs
;
; Board 2 is the slider and has 31 LEDs:
; [0]-[31]: slider LEDs right to left BRG, alternating between keys and dividers
; -----------------------------------------------------------------------------
; Custom IO settings
; -----------------------------------------------------------------------------
[chuniio]
; Uncomment this if you have custom chuniio implementation comprised of a single 32bit DLL.
; (will use chu2to3 engine internally)
;path=
; Uncomment both of these if you have custom chuniio implementation comprised of two DLLs.
; x86 chuniio to path32, x64 to path64. Both are necessary.
;path32=
;path64=
; [0]-[31]: slider LEDs right to left BRG, alternating between keys and dividers

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

@ -127,28 +127,6 @@
},
],
},
{
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] }
]
}
]
},
{
filename: 'chusanApp.exe',
version: '2.30.00',
@ -326,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.12.0",
"version": "0.18.3",
"identifier": "zip.patafour.startliner",
"build": {
"beforeDevCommand": "bun run dev",

View File

@ -20,15 +20,16 @@ import OptionList from './OptionList.vue';
import PatchList from './PatchList.vue';
import ProfileList from './ProfileList.vue';
import StartButton from './StartButton.vue';
import { invoke } from '../invoke';
import {
useClientStore,
useGeneralStore,
usePkgStore,
usePrfStore,
} from '../stores';
import { Dirs } from '../types';
import { messageSplit, shouldPreferDark } from '../util';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
document.documentElement.classList.toggle('use-dark-mode', shouldPreferDark());
@ -37,10 +38,10 @@ const prf = usePrfStore();
const general = useGeneralStore();
const client = useClientStore();
client.load();
pkg.setupListeners();
const currentTab: Ref<'users' | 'loc' | 'patches' | 'rmt' | 'cfg' | 'info'> =
ref('users');
const pkgSearchTerm = ref('');
const isProfileDisabled = computed(() => prf.current === null);
@ -56,17 +57,11 @@ listen<undefined>('update-end', (_) => {
});
onMounted(async () => {
invoke('list_directories').then((d) => {
general.dirs = d as Dirs;
client.load();
});
const fetch_promise = pkg.fetch(true);
await Promise.all([prf.reloadList(), prf.reload()]);
if (prf.current !== null) {
currentTab.value = 'loc';
await pkg.reloadAll();
}
@ -211,34 +206,34 @@ listen<DownloadingStatus>('download-progress', (event) => {
<Tabs
lazy
:value="currentTab"
:value="client.currentTab"
v-on:update:value="
(value) => {
currentTab = value as any;
client.currentTab = value as string;
client.save();
}
"
class="h-screen"
>
<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 || pkg.networkStatus !== 'online'
"
value="rmt"
><div class="pi pi-download"></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>
<Tab
v-if="pkg.networkStatus === 'online'"
:disabled="isProfileDisabled"
value="rmt"
><div class="pi pi-download"></div
></Tab>
<Tab :disabled="isProfileDisabled" value="cfg"
><div class="pi pi-cog"></div
></Tab>
@ -251,17 +246,21 @@ listen<DownloadingStatus>('download-progress', (event) => {
<div class="flex gap-4">
<div
class="flex"
v-if="['loc', 'rmt', 'cfg'].includes(currentTab)"
v-if="
['loc', 'rmt', 'cfg'].includes(
client.currentTab
)
"
>
<InputIcon class="self-center mr-2">
<i class="pi pi-search" />
</InputIcon>
<InputText
v-if="currentTab === 'cfg'"
v-if="client.currentTab === 'cfg'"
style="min-width: 0; width: 25dvw"
class="self-center"
size="small"
placeholder="Search"
:placeholder="t('search')"
v-model="general.cfgSearchTerm"
/>
<InputText
@ -269,7 +268,7 @@ listen<DownloadingStatus>('download-progress', (event) => {
style="min-width: 0; width: 25dvw"
class="self-center"
size="small"
placeholder="Search"
:placeholder="t('search')"
v-model="pkgSearchTerm"
/>
</div>
@ -307,19 +306,19 @@ listen<DownloadingStatus>('download-progress', (event) => {
</TabList>
</div>
<TabPanels class="w-full grow mt-[3rem]">
<TabPanel value="loc">
<TabPanel value="loc" v-if="!isProfileDisabled">
<ModList :search="pkgSearchTerm" />
</TabPanel>
<TabPanel value="rmt">
<TabPanel value="rmt" v-if="!isProfileDisabled">
<ModStore :search="pkgSearchTerm" />
</TabPanel>
<TabPanel value="cfg">
<TabPanel value="cfg" v-if="!isProfileDisabled">
<OptionList />
</TabPanel>
<TabPanel value="users">
<ProfileList />
</TabPanel>
<TabPanel value="patches">
<TabPanel value="patches" v-if="!isProfileDisabled">
<PatchList
v-if="
pkg.hasLocal('mempatcher-mempatcher') &&
@ -350,7 +349,12 @@ listen<DownloadingStatus>('download-progress', (event) => {
<InfoPage />
</TabPanel>
</TabPanels>
<div v-if="currentTab === 'users' || currentTab === 'info'">
<div
v-if="
client.currentTab === 'users' ||
client.currentTab === 'info'
"
>
<img
v-if="prf.current?.meta.game === 'ongeki'"
src="/sticker-ongeki.svg"

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

@ -4,6 +4,9 @@ import InputText from 'primevue/inputtext';
import { fromKeycode, toKeycode } from '../keyboard';
import { usePrfStore } from '../stores';
import { OngekiButtons } from '../types';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
@ -61,6 +64,10 @@ const handleKey = (
}
}
if (event.code === 'Escape') {
keycode = 0;
}
if (index !== undefined) {
data[button][index] = keycode;
} else {
@ -160,13 +167,24 @@ const fontSize = computed(() => {
<InputText
:style="{
width: small ? '2.8rem' : '5rem',
height: small ? '2.8rem' : tall ? '10rem' : '5rem',
height:
small && tall
? '5rem'
: small
? '2.8rem'
: tall
? '10rem'
: '5rem',
fontSize,
backgroundColor: color,
}"
unstyled
class="text-center buttoninputtext"
v-tooltip="tooltip ? `${tooltip}: ${modelValue}` : undefined"
v-tooltip="
tooltip
? `${tooltip}: ${modelValue} ${tooltip.startsWith('ir') ? `\n${t('cfg.keyboard.irTooltip')}` : ''}`
: undefined
"
@contextmenu.prevent="() => {}"
@keydown="(ev: KeyboardEvent) => handleKey(button, ev, index)"
@mousedown="

View File

@ -1,61 +1,312 @@
<script setup lang="ts">
import { Ref, computed, ref } from 'vue';
import Button from 'primevue/button';
import Dialog from 'primevue/dialog';
import Fieldset from 'primevue/fieldset';
import InputText from 'primevue/inputtext';
import MultiSelect from 'primevue/multiselect';
import SelectButton from 'primevue/selectbutton';
import ToggleSwitch from 'primevue/toggleswitch';
import { emit } from '@tauri-apps/api/event';
import ModListEntry from './ModListEntry.vue';
import ModTitlecard from './ModTitlecard.vue';
import { invoke } from '../invoke';
import { usePkgStore, usePrfStore } from '../stores';
import { Package } from '../types';
import { useClientStore, usePkgStore, usePrfStore } from '../stores';
import { Feature, Game, Package } from '../types';
import { pkgKey } from '../util';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
search: String,
});
const pkgs = usePkgStore();
const client = useClientStore();
const prf = usePrfStore();
const empty = ref(false);
const gameSublist: Ref<string[]> = ref([]);
invoke('get_game_packages', {
game: prf.current?.meta.game,
}).then((list) => {
gameSublist.value = list as string[];
const loadPackages = () => {
invoke('get_game_packages', {
game: prf.current?.meta.game ?? null,
}).then((list) => {
gameSublist.value = list as string[];
});
};
loadPackages();
const allCategories = computed(() => {
const res = new Set<string>();
for (const pkg of pkgs.allLocal) {
for (const cat of pkg.rmt?.categories ?? []) {
res.add(cat);
}
}
return [...res.values()].sort((a, b) => a.localeCompare(b));
});
const group = computed(() => {
const res = Object.assign(
{},
Object.groupBy(
pkgs.allLocal
.filter((p) => gameSublist.value.includes(pkgKey(p)))
.filter(
(p) =>
props.search === undefined ||
p.name
.toLowerCase()
.includes(props.search.toLowerCase()) ||
p.namespace
.toLowerCase()
.includes(props.search.toLowerCase())
)
.sort((p1, p2) => p1.namespace.localeCompare(p2.namespace))
.sort((p1, p2) => p1.name.localeCompare(p2.name)),
({ namespace }) => namespace
)
);
empty.value = Object.keys(res).length === 0;
const local = computed(() => {
return pkgs.allLocal
.filter((p) => gameSublist.value.includes(pkgKey(p)))
.filter((p) => p.namespace === 'local');
});
const groupedList = computed(() => {
const searchedPkgs = pkgs.allLocal
.filter((p) => gameSublist.value.includes(pkgKey(p)))
.filter((p) => p.namespace !== 'local')
.filter(
(p) =>
props.search === undefined ||
p.name.toLowerCase().includes(props.search.toLowerCase()) ||
p.namespace.toLowerCase().includes(props.search.toLowerCase())
);
let grouped;
if (client.pkgListMode === 'namespace') {
grouped = Object.groupBy(searchedPkgs, ({ namespace }) => namespace);
} else if (client.pkgListMode === 'type') {
grouped = {
standard: [] as Package[],
native: [] as Package[],
segatools: [] as Package[],
unsupported: [] as Package[] | undefined,
};
grouped.unsupported = [];
for (const pkg of searchedPkgs) {
const loc = pkg.loc;
if (!loc || !loc.status || typeof loc.status === 'string') {
grouped.unsupported.push(pkg);
} else {
if (
loc.status.OK[0] &
(Feature.GameDLL | Feature.Mempatcher | Feature.AmdDLL)
) {
grouped.native.push(pkg);
} else if (loc.status.OK[0] & Feature.Mod) {
grouped.standard.push(pkg);
}
if (
loc.status.OK[0] &
(Feature.AMNet |
Feature.Aime |
Feature.ChuniIO |
Feature.ChusanHook |
Feature.Mu3IO |
Feature.Mu3Hook)
) {
grouped.segatools.push(pkg);
}
}
}
if (grouped.unsupported.length === 0) {
delete grouped.unsupported;
}
} else {
grouped = {} as { [key: string]: Package[] };
for (const pkg of searchedPkgs) {
for (const cat of pkg.rmt?.categories ?? []) {
if (client.hiddenCategories.includes(cat)) {
continue;
}
if (!(cat in grouped)) {
grouped[cat] = [] as Package[];
}
grouped[cat].push(pkg);
}
}
}
let res: [string, Package[]][] = [];
for (const [k, v] of Object.entries(grouped)) {
if (v !== undefined) {
res.push([k, v]);
}
}
if (
client.pkgListMode === 'namespace' ||
client.pkgListMode === 'category'
) {
res.sort((a, b) => `${a[0]}`.localeCompare(`${b[0]}`));
} else if (client.pkgListMode === 'type') {
for (const entry of res) {
entry[0] = t(`pkglist.${entry[0]}`);
}
}
return res;
});
const missing = computed(() => {
return prf.current?.data.mods.filter((m) => !pkgs.hasLocal(m)) ?? [];
});
const dialogVisible = ref(false);
const defaultModel = {
name: '',
description: '',
website: '',
type: 'rainy',
games: [] as string[],
};
const creatorModel = ref({ ...defaultModel });
const gameModel = (game: Game) =>
computed({
get() {
return (creatorModel.value.games as string[]).includes(game);
},
set(v: boolean) {
creatorModel.value.games = creatorModel.value.games.filter(
(g) => g !== game
);
if (v) {
creatorModel.value.games.push(game);
}
},
});
const gameModelOngeki = gameModel('ongeki');
const gameModelChunithm = gameModel('chunithm');
</script>
<template>
<Fieldset legend="Missing" v-if="(missing?.length ?? 0) > 0">
<Dialog
modal
:visible="dialogVisible"
:closable="false"
:header="t('creator.header')"
:style="{ width: '500px', scale: client.scaleValue }"
class="creation-dialog"
>
<div style="position: absolute; left: 250px; top: 25px">
<a
href="https://gitea.tendokyu.moe/akanyan/STARTLINER/wiki/Package-format"
target="_blank"
style="text-decoration: underline"
class="self-center"
>{{ t('creator.packageFormat') }}</a
>
</div>
<h2>{{ t('creator.basic') }}</h2>
<div class="flex flex-col gap-3">
<InputText
size="small"
style="width: 100%"
:placeholder="t('creator.name')"
v-model="creatorModel.name"
/>
<InputText
size="small"
style="width: 100%"
:placeholder="t('creator.description')"
v-model="creatorModel.description"
/>
<InputText
size="small"
style="width: 100%"
:placeholder="t('creator.website')"
v-model="creatorModel.website"
/>
</div>
<h2>{{ t('creator.type') }}</h2>
<div class="flex flex-col items-center">
<SelectButton
:options="[
{ title: t('creator.rainy'), value: 'rainy' },
{ title: t('creator.native'), value: 'native' },
{ title: t('creator.segatools'), value: 'segatools' },
]"
v-model="creatorModel.type"
:allow-empty="false"
option-label="title"
option-value="value"
/>
</div>
<h2>{{ t('creator.games') }}</h2>
<div class="flex flex-col gap-4">
<div class="flex flex-row">
<div class="grow">{{ t('game.ongeki') }}</div>
<ToggleSwitch v-model="gameModelOngeki" />
</div>
<div class="flex flex-row">
<div class="grow">{{ t('game.chunithm') }}</div>
<ToggleSwitch v-model="gameModelChunithm" />
</div>
</div>
<div class="flex flex-row mt-5">
<Button
class="ml-auto mr-1"
style="width: 80px"
:label="t('ok')"
:disabled="creatorModel.games.length === 0"
@click="
async () => {
await invoke('create_package', creatorModel);
await pkgs.reloadAll();
loadPackages();
dialogVisible = false;
creatorModel = { ...defaultModel };
}
"
/>
<Button
class="mr-auto ml-1"
style="width: 80px"
:label="t('cancel')"
@click="
() => (
(dialogVisible = false),
(creatorModel = { ...defaultModel })
)
"
/>
</div>
</Dialog>
<div class="flex flex-row">
<SelectButton
:options="[
{ title: t('pkglist.namespace'), value: 'namespace' },
{ title: t('pkglist.type'), value: 'type' },
{ title: t('pkglist.category'), value: 'category' },
]"
v-model="client.pkgListMode"
v-on:update:model-value="
client.save();
emit('reload-icons');
"
:allow-empty="false"
option-label="title"
option-value="value"
/>
<div
class="grow text-right mr-2 self-center"
v-if="client.pkgListMode === 'category'"
>
{{ t('pkglist.exclusions') }}
</div>
<MultiSelect
v-if="client.pkgListMode === 'category'"
style="width: 30%"
:showToggleAll="false"
v-model="client.hiddenCategories"
v-on:value-change="
client.save();
emit('reload-icons');
"
:options="allCategories"
class="w-full grow"
/>
</div>
<Fieldset :legend="t('pkglist.missing')" v-if="(missing?.length ?? 0) > 0">
<div class="flex items-center" v-for="p in missing">
<ModTitlecard
show-namespace
@ -78,8 +329,28 @@ const missing = computed(() => {
/>
</div>
</Fieldset>
<Fieldset v-for="(namespace, key) in group" :legend="key.toString()">
<ModListEntry v-for="p in namespace" :pkg="p" />
<Fieldset :legend="t('pkglist.local')">
<ModListEntry v-for="p in local" :pkg="p" />
<Button
rounded
icon="pi pi-plus"
severity="success"
aria-label="install"
size="small"
class="self-center"
style="width: 2rem; height: 2rem"
v-on:click="() => (dialogVisible = true)"
/>
</Fieldset>
<Fieldset v-for="[namespace, pkgs] in groupedList" :legend="namespace">
<ModListEntry v-for="p in pkgs" :pkg="p" />
</Fieldset>
<div v-if="empty === true" class="text-3xl fadein"></div>
</template>
<style lang="css" scoped>
.creation-dialog h2 {
margin-top: 0.6em;
margin-bottom: 0.4em;
font-size: 110%;
}
</style>

View File

@ -7,12 +7,16 @@ import LinkButton from './LinkButton.vue';
import ModTitlecard from './ModTitlecard.vue';
import UpdateButton from './UpdateButton.vue';
import { invoke } from '../invoke';
import { usePkgStore, usePrfStore } from '../stores';
import { useClientStore, 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();
const client = useClientStore();
const props = defineProps({
pkg: Object as () => Package,
@ -36,14 +40,15 @@ if (unsupported.value === true && model.value === true) {
<template>
<div class="flex items-center">
<ModTitlecard show-version show-icon show-description :pkg="pkg" />
<ModTitlecard
show-version
show-icon
show-description
:show-namespace="client.pkgListMode !== 'namespace'"
: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();
@ -20,7 +23,7 @@ const props = defineProps({
const gameSublist: Ref<string[]> = ref([]);
invoke('get_game_packages', {
game: prf.current?.meta.game,
game: prf.current?.meta.game ?? null,
}).then((list) => {
gameSublist.value = list as string[];
});
@ -43,10 +46,10 @@ const list = () => {
};
const shouldShowRecommended = computed(() => {
if (prf.current!.meta.game === 'ongeki') {
if (prf.current?.meta.game === 'ongeki') {
return !pkgs.allLocal.some((p) => pkgKey(p) === 'segatools-mu3hook');
}
if (prf.current!.meta.game === 'chunithm') {
if (prf.current?.meta.game === 'chunithm') {
return (
!pkgs.allLocal.some((p) => pkgKey(p) === 'segatools-chusanhook') ||
!pkgs.allLocal.some((p) => pkgKey(p) === 'mempatcher-mempatcher')
@ -55,21 +58,21 @@ const shouldShowRecommended = computed(() => {
return false;
});
const getRecommendedTooltip = () => {
if (prf.current!.meta.game === 'ongeki') {
const recommendedTooltip = computed(() => {
if (prf.current?.meta.game === 'ongeki') {
return 'segatools-mu3hook';
}
if (prf.current!.meta.game === 'chunithm') {
return 'segatools-chusanhook and mempatcher';
if (prf.current?.meta.game === 'chunithm') {
return 'segatools-chusanhook + mempatcher';
}
return '';
};
});
const installRecommended = () => {
if (prf.current!.meta.game === 'ongeki') {
if (prf.current?.meta.game === 'ongeki') {
pkgs.installFromKey('segatools-mu3hook');
}
if (prf.current!.meta.game === 'chunithm') {
if (prf.current?.meta.game === 'chunithm') {
pkgs.installFromKey('segatools-chusanhook');
pkgs.installFromKey('mempatcher-mempatcher');
}
@ -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>
@ -96,7 +101,7 @@ const installRecommended = () => {
<MultiSelect
size="small"
:showToggleAll="false"
placeholder="Include categories"
:placeholder="t('store.includeCategories')"
v-model="pkgs.includeCategories"
:options="[...pkgs.availableCategories]"
class="w-full"
@ -104,7 +109,7 @@ const installRecommended = () => {
<MultiSelect
size="small"
:showToggleAll="false"
placeholder="Exclude categories"
:placeholder="t('store.excludeCategories')"
v-model="pkgs.excludeCategories"
:options="[...pkgs.availableCategories]"
class="w-full"
@ -114,8 +119,8 @@ const installRecommended = () => {
<Divider />
<Button
v-if="shouldShowRecommended"
label="Install recommended packages"
v-tooltip="getRecommendedTooltip"
:label="t('store.installRecommended')"
v-tooltip="recommendedTooltip"
icon="pi pi-plus"
class="mb-3"
@click="installRecommended"

View File

@ -1,9 +1,14 @@
<script setup lang="ts">
import { computed } from 'vue';
import { ref } from 'vue';
import Chip from 'primevue/chip';
import { convertFileSrc } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
import { invoke } from '../invoke';
import { Feature, Package } from '../types';
import { hasFeature, needsUpdate } from '../util';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
pkg: Object as () => Package,
@ -14,23 +19,34 @@ const props = defineProps({
showIcon: Boolean,
});
const iconSrc = computed(() => {
const icon = props.pkg?.loc?.icon ?? props.pkg?.rmt?.icon;
const icon = ref('/no-icon.png');
if (icon === undefined) {
return '';
} else if (icon.startsWith('https://')) {
return icon;
const reloadIcons = async () => {
const src = props.pkg?.loc?.icon ?? props.pkg?.rmt?.icon;
if (src === undefined) {
icon.value = '/no-icon.png';
} else if (src.startsWith('https://')) {
icon.value = src;
} else {
return convertFileSrc(icon);
const convt = convertFileSrc(src);
if (await invoke('file_exists', { path: src })) {
icon.value = convt;
} else {
icon.value = '/no-icon.png';
}
}
});
};
reloadIcons();
listen('reload-icons', reloadIcons);
</script>
<template>
<img
v-if="showIcon"
:src="iconSrc"
:src="icon"
class="self-center rounded-sm"
width="32px"
height="32px"
@ -89,7 +105,12 @@ const iconSrc = computed(() => {
v-if="showNamespace && pkg?.namespace"
class="text-sm opacity-75"
>
by&nbsp;{{ pkg.namespace }}
&nbsp;{{
t('by', { namespace: pkg.namespace }).replaceAll(
' ',
'&nbsp;'
)
}}
</span>
<span class="m-2">
<span

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ComputedRef, computed, onMounted, ref } from 'vue';
import { ComputedRef, computed, ref } from 'vue';
import Button from 'primevue/button';
import Carousel from 'primevue/carousel';
import Dialog from 'primevue/dialog';
@ -7,6 +7,9 @@ import { fromKeycode } from '../keyboard';
import { useClientStore, usePrfStore } from '../stores';
import { prettyPrint } from '../util';
import { VueMarkdownIt } from '@f3ve/vue-markdown-it';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
const client = useClientStore();
@ -24,38 +27,73 @@ interface Datum {
const game = computed(() => prf.current?.meta.game);
const processText = (s: string) => {
const processText = computed(() => (s: string) => {
// Why do I have to do this
s = s
.split('\n')
.map((l) => l.trim())
.join('\n');
if (prf.current!.data.keyboard?.data.enabled) {
const testKey = prf.current!.data.keyboard?.data.test;
const readable = fromKeycode(testKey);
if (readable !== null) {
return s.replace(
'%TESTMENU%',
`${readable} or a button on the back of the controller`
`${readable} ${t('onboarding.or')} ${t('onboarding.backButton')}`
);
}
}
return s.replace('%TESTMENU%', 'a button on the back of the controller');
};
return s.replace('%TESTMENU%', t('onboarding.backButton'));
});
const loadPage = async (title: string) => {
const loadPage = computed(() => (title: string, messages?: object) => {
return {
text: await (await fetch(`/help-${title}.md`)).text(),
text: t(`onboarding.${title}`, {
endlink: '</a>',
black: '<span class="bg-black text-white">',
end: '</span>',
...messages,
}),
image: `help-${title}.png`,
};
};
let systemProcessing: Datum;
let standardOngeki: Datum;
let standardChunithm: Datum;
let lever: Datum;
let server: Datum;
let finaleOngeki: Datum;
let finaleChunithm: Datum;
});
const data: ComputedRef<Datum[]> = computed(() => {
const res = [];
const [standard, systemProcessing, lever, server, finale] = [
loadPage.value('standard', {
bigblack: '<div class="p-2 mt-1 mb-1 bg-black text-white">',
endbig: '</div>',
}),
loadPage.value('ongeki-system-processing'),
loadPage.value('ongeki-lever'),
loadPage.value('chunithm-server', {
link: '<a href="https://gitea.tendokyu.moe/Dniel97/SEGAguide/wiki/FAQ#game-is-stuck-at-checking-distribution-server" target="_blank">',
}),
loadPage.value('finale', {
segaguide:
'<a href="https://gitea.tendokyu.moe/Dniel97/SEGAguide/wiki/FAQ" target="_blank">',
twotorial: '<a href="https://two-torial.xyz/" target="_blank">',
}),
];
const standardOngeki = {
...standard,
image: '/help-standard-ongeki.png',
};
const standardChunithm = {
...standard,
image: '/help-standard-chunithm.png',
};
const finaleOngeki = {
...finale,
image: '/help-finale-ongeki.png',
};
const finaleChunithm = {
...finale,
image: '/help-finale-chunithm.png',
};
switch (prf.current?.meta.game) {
case 'ongeki':
res.push(systemProcessing);
@ -75,40 +113,18 @@ const data: ComputedRef<Datum[]> = computed(() => {
return res;
});
onMounted(async () => {
[standardOngeki, systemProcessing, lever, server, finaleOngeki] =
await Promise.all([
loadPage('standard'),
loadPage('ongeki-system-processing'),
loadPage('ongeki-lever'),
loadPage('chunithm-server'),
loadPage('finale'),
]);
standardOngeki = {
...standardOngeki,
image: '/help-standard-ongeki.png',
};
standardChunithm = {
...standardOngeki,
image: '/help-standard-chunithm.png',
};
finaleOngeki = {
...finaleOngeki,
image: '/help-finale-ongeki.png',
};
finaleChunithm = {
...finaleOngeki,
image: '/help-finale-chunithm.png',
};
const context = ref({
index: 0,
});
const counter = ref(0);
const exitLabel = computed(() => {
return props.firstTime === true && counter.value < data.value.length - 1
? 'Skip'
: 'Close';
return props.firstTime === true &&
context.value.index < data.value.length - 1
? t('skip')
: t('close');
});
const page = ref(0);
</script>
<template>
@ -122,13 +138,15 @@ const exitLabel = computed(() => {
: `${game ? prettyPrint(game) : '<game>'} help`
"
:style="{ width: '760px', scale: client.scaleValue }"
v-on:show="() => ((context.index = 0), (page = 0))"
>
<Carousel
:value="data"
:num-visible="1"
:num-scroll="1"
:page="counter"
v-on:update:page="(p) => (counter = p)"
:context="context"
:page="page"
v-on:update:page="(p) => ((context.index = p), (page = p))"
>
<template #item="slotProps">
<div class="md-container markdown">
@ -150,10 +168,10 @@ const exitLabel = computed(() => {
</Carousel>
<div style="width: 100%; text-align: center">
<Button
v-if="counter < data.length - 1"
v-if="context.index < data.length - 1"
class="m-auto mr-4"
label="Next"
@click="() => (counter += 1)"
:label="t('next')"
@click="() => (page += 1)"
/>
<Button
class="m-auto"

View File

@ -14,6 +14,9 @@ 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();
@ -58,9 +61,9 @@ prf.reload();
<MiscOptions />
<OptionCategory
title="Extensions"
v-if="prf.current!.meta.game === 'chunithm'"
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"
@ -68,31 +71,31 @@ prf.reload();
/> </OptionRow
></OptionCategory>
<OptionCategory
title="Extensions"
v-if="prf.current!.meta.game === 'ongeki'"
: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 7EVENDAYSHOLIDAYS-ExclusiveAudio"
:title="t('cfg.extensions.audioMode')"
:tooltip="t('cfg.extensions.audioTooltip')"
>
<SelectButton
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="false"
option-label="title"
@ -100,7 +103,7 @@ prf.reload();
/></OptionRow>
<OptionRow
title="Sample rate"
:title="t('cfg.extensions.sampleRate')"
v-if="
prf.current?.data.mods.includes(
'7EVENDAYSHOLIDAYS-ExclusiveAudio'
@ -126,8 +129,8 @@ prf.reload();
prf.current?.data.mods.includes('7EVENDAYSHOLIDAYS-Blacklist')
"
class="number-input"
title="Song ID Blacklist"
tooltip="Scores on charts within this ID range will not be saved nor uploaded"
:title="t('cfg.extensions.blacklist')"
:tooltip="t('cfg.extensions.blacklistTooltip')"
><InputNumber
class="shrink"
size="small"
@ -165,8 +168,8 @@ prf.reload();
/>
</OptionRow>
<OptionRow
title="Unlock Bonus Tracks"
tooltip="Disabling this option can help declutter the song list"
:title="t('cfg.extensions.bonusTracks')"
:tooltip="t('cfg.extensions.bonusTracksTooltip')"
v-if="
prf.current?.data.mods.includes(
'7EVENDAYSHOLIDAYS-UnlockAllMusic'

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, te } = 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}`;
const name = te(nameKey) ? t(nameKey) : props.patch?.name;
const tooltipKey = `patch.${props.patch?.id}-tooltip`;
const tooltip = te(tooltipKey) ? t(tooltipKey) : props.patch?.tooltip;
</script>
<template>
<OptionRow
:title="patch?.name"
:tooltip="patch?.tooltip"
:greytext="patch?.id"
>
<OptionRow :title="name" :tooltip="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

@ -1,11 +1,15 @@
<script setup lang="ts">
import { Ref, ref } from 'vue';
// import Select from 'primevue/select';
import * as path from '@tauri-apps/api/path';
import OptionCategory from './OptionCategory.vue';
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();
@ -29,21 +33,22 @@ invoke('list_patches', { target: prf.current!.data.sgt.target }).then(
target: amd,
})) as Patch[];
})();
const errorMessage =
"No compatible patches found. Make sure you're using unpacked and unpatched files.";
</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 }}
{{ t('patch.noneFound') }}
</div>
</OptionCategory>
<OptionCategory title="amdaemon.exe" always-found>
@ -52,9 +57,22 @@ const errorMessage =
v-for="p in amdPatches"
:patch="p"
/>
<div v-if="gamePatches === null">Loading...</div>
<div v-if="gamePatches === null">{{ t('patch.loading') }}</div>
<div v-if="amdPatches !== null && amdPatches.length === 0">
{{ errorMessage }}
{{ t('patch.noneFound') }}
<!-- <br />
<Select
class="mt-3"
style="width: 400px"
:options="[
{},
{},
]"
:placeholder="t('patch.forceLoad')"
size="small"
option-label="title"
option-value="value"
></Select> -->
</div>
</OptionCategory>
</template>

View File

@ -1,33 +1,224 @@
<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 hasChunithm = ref(false);
const exportVisible = ref(false);
const exportKeychip = ref(false);
const files = new Set<string>();
(async () => {
hasChunithm.value = (
(await invoke('list_platform_capabilities')) as string[]
).includes('chunithm');
})();
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(await 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="`${t('profile.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">{{ t('profile.export') }} keychip</div>
<ToggleSwitch v-model="exportKeychip" />
</div>
<div class="flex flex-row" v-for="f in fileListCurrent">
<div class="grow">{{ t('profile.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
v-if="hasChunithm"
: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="t('profile.importTemplate')"
icon="pi pi-file-import"
class="import-button profile-button"
@click="() => importPick()"
/>
<Button
:disabled="prf.current === null"
:label="t('profile.exportTemplate')"
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' },
{ title: 'Polski', value: 'pl' },
]"
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 +248,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,18 +63,22 @@ 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 });
}
general.dataDir.then((dataDir) =>
path
.join(dataDir, `profile-${props.p!.game}-${props.p!.name}`)
.then(async (p) => {
dataExists.value = await invoke('file_exists', { path: p });
})
);
</script>
@ -140,11 +147,15 @@ path.join(general.dataDir, `profile-${props.p!.game}-${props.p!.name}`).then(
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 });
})
async () =>
path
.join(
await general.configDir,
`profile-${p!.game}-${p!.name}`
)
.then(async (path) => {
await invoke('open_file', { path });
})
"
/>
<Button
@ -157,11 +168,15 @@ path.join(general.dataDir, `profile-${props.p!.game}-${props.p!.name}`).then(
class="self-center"
style="width: 2rem; height: 2rem"
@click="
path
.join(general.dataDir, `profile-${p!.game}-${p!.name}`)
.then(async (path) => {
await invoke('open_file', { path });
})
async () =>
path
.join(
await general.dataDir,
`profile-${p!.game}-${p!.name}`
)
.then(async (path) => {
await invoke('open_file', { path });
})
"
/>
</div>

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,66 @@ 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 hasShortcut = ref(false);
(async () => {
hasShortcut.value = (
(await invoke('list_platform_capabilities')) as string[]
).includes('shortcut');
})();
const menuItems = computed(() => {
let base = [
{
label: t('start.button.unchecked'),
icon: 'pi pi-exclamation-circle',
command: async () => await startline(true, false),
},
},
];
];
let baseTail = [
{
label: t('start.button.help'),
icon: 'pi pi-question-circle',
command: () => {
onboardingFirstTime.value = false;
onboardingVisible.value = true;
},
},
];
if (prf.current === null) {
return [];
}
if (hasShortcut.value === true) {
base = [
...base,
{
label: t('start.button.shortcut'),
icon: 'pi pi-link',
command: createShortcut,
},
];
}
if (prf.current.meta.game === 'chunithm') {
return [...base, ...baseTail];
}
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 () => await invoke('clear_cache'),
},
...baseTail,
];
}
});
const menu = ref();
const showContextMenu = (event: Event) => {
@ -177,7 +214,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 +223,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

@ -1,5 +1,5 @@
<script setup lang="ts">
import { Ref, computed, ref } from 'vue';
import { Ref, computed, onMounted, ref } from 'vue';
import InputNumber from 'primevue/inputnumber';
import Select from 'primevue/select';
import SelectButton from 'primevue/selectbutton';
@ -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',
},
];
@ -62,19 +65,23 @@ const loadDisplays = () => {
.catch(() => {});
};
loadDisplays();
onMounted(() => {
loadDisplays();
});
const game = prf.current!.meta.game;
const isVertical = game === 'ongeki';
const adjustableRez = game === 'ongeki';
const canSkipPrimarySwitch = game === 'ongeki';
const game = computed(() => prf.current!.meta.game);
const isVertical = computed(() => prf.current!.meta.game === 'ongeki');
const adjustableRez = computed(() => prf.current!.meta.game === 'ongeki');
const canSkipPrimarySwitch = computed(
() => prf.current!.meta.game === 'ongeki'
);
</script>
<template>
<OptionCategory title="Display">
<OptionCategory :title="t('cfg.display.title')">
<OptionRow
v-if="capabilities.includes('display')"
title="Target display"
:title="t('cfg.display.target')"
>
<Select
v-model="prf.current!.data.display.target"
@ -87,7 +94,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
</OptionRow>
<OptionRow
class="number-input"
title="Game resolution"
:title="t('cfg.display.resolution')"
v-if="adjustableRez"
>
<InputNumber
@ -108,13 +115,13 @@ 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="[
{ title: 'Window', value: 'Window' },
{ title: 'Borderless window', value: 'Borderless' },
{ title: 'Fullscreen', value: 'Fullscreen' },
{ title: t('cfg.display.window'), value: 'Window' },
{ title: t('cfg.display.borderless'), value: 'Borderless' },
{ title: t('cfg.display.fullscreen'), value: 'Fullscreen' },
]"
:allow-empty="false"
option-label="title"
@ -122,7 +129,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
/>
</OptionRow>
<OptionRow
title="Display rotation"
:title="t('cfg.display.rotation')"
v-if="capabilities.includes('display')"
>
<SelectButton
@ -130,12 +137,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 +160,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 +186,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,28 +199,31 @@ 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' &&
(prf.current!.data.display.dont_switch_primary ||
displayList.length > 2) &&
canSkipPrimarySwitch
!capabilities.includes('display') ||
(prf.current?.data.display.target !== 'default' &&
(prf.current!.data.display.dont_switch_primary ||
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"
:disabled="
extraDisplayOptionsDisabled &&
capabilities.includes('display')
"
v-model="prf.current!.data.display.dont_switch_primary"
/>
</OptionRow>
<OptionRow
title="Display index"
:title="t('cfg.display.index')"
class="number-input"
v-if="
capabilities.includes('display') &&
prf.current?.data.display.target !== 'default' &&
prf.current!.data.display.dont_switch_primary
!capabilities.includes('display') ||
(prf.current?.data.display.target !== 'default' &&
prf.current!.data.display.dont_switch_primary)
"
>
<InputNumber
@ -216,8 +232,12 @@ const canSkipPrimarySwitch = game === 'ongeki';
:min="game === 'chunithm' ? 0 : 1"
:max="32"
:use-grouping="false"
placeholder="1"
v-model="prf.current!.data.display.monitor_index_override"
:disabled="extraDisplayOptionsDisabled"
:disabled="
extraDisplayOptionsDisabled &&
capabilities.includes('display')
"
:allow-empty="true"
/>
</OptionRow>

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"
@ -95,7 +95,7 @@ const prf = usePrfStore();
</div>
</div>
<div v-if="prf.current?.meta.game === 'chunithm'">
<div class="absolute left-1/2 top-1/5">
<div class="absolute left-9/17 top-1/12">
<div
class="flex flex-row flex-nowrap gap-2 self-center w-full"
>
@ -108,6 +108,7 @@ const prf = usePrfStore();
button="ir"
:index="idx - 1"
:tooltip="`ir${idx}`"
tall
small
color="rgba(0, 255, 0, 0.2)"
/>

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

@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue';
import { Ref, computed, ref } from 'vue';
import Select from 'primevue/select';
import { useConfirm } from 'primevue/useconfirm';
import { emit } from '@tauri-apps/api/event';
@ -11,11 +11,22 @@ 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();
const confirmDialog = useConfirm();
const capabilities: Ref<string[]> = ref([]);
invoke('list_platform_capabilities').then(async (v: unknown) => {
if (Array.isArray(v)) {
capabilities.value.push(...v);
}
});
const names = computed(() => {
switch (prf.current?.meta.game) {
case 'ongeki': {
@ -32,8 +43,6 @@ const names = computed(() => {
io: 'chuniio',
};
}
case undefined:
throw new Error('Option tab without a profile');
}
});
@ -54,16 +63,16 @@ 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."
:title="names?.exe"
:tooltip="t('cfg.segatools.targetTooltip')"
>
<FilePicker
:directory="false"
:promptname="names.exe"
:promptname="names?.exe"
extension="exe"
:value="prf.current!.data.sgt.target"
:value="prf.current?.data.sgt.target"
:callback="
(value: string) => (
(prf.current!.data.sgt.target = value),
@ -77,7 +86,7 @@ const checkSegatoolsIni = async (target: string) => {
<FilePicker
:directory="true"
placeholder="amfs"
:value="prf.current!.data.sgt.amfs"
:value="prf.current?.data.sgt.amfs"
:callback="
(value: string) => (prf.current!.data.sgt.amfs = value)
"
@ -103,8 +112,12 @@ const checkSegatoolsIni = async (target: string) => {
></FilePicker>
</OptionRow>
<OptionRow
:title="names.hook"
tooltip="Hooks can be downloaded from the package store."
:title="names?.hook"
:tooltip="
t('cfg.segatools.installTooltip', {
thing: t('cfg.segatools.hooks'),
})
"
>
<Select
v-model="prf.current!.data.sgt.hook"
@ -125,15 +138,18 @@ const checkSegatoolsIni = async (target: string) => {
></Select>
</OptionRow>
<OptionRow
:title="names.io"
tooltip="IO plugins can be downloaded from the package store."
:title="names?.io"
:tooltip="`${t('cfg.segatools.ioModulesDesc')}
${t('cfg.segatools.installTooltip', {
thing: t('cfg.segatools.ioModules'),
})}`"
>
<Select
v-model="prf.current!.data.sgt.io2"
:options="[
{ title: 'native io4', value: 'hardware' },
{ title: t('cfg.segatools.io4'), value: 'hardware' },
{
title: 'segatools built-in (keyboard)',
title: t('cfg.segatools.ioBuiltIn'),
value: 'segatools_built_in',
},
...pkgs
@ -153,5 +169,29 @@ const checkSegatoolsIni = async (target: string) => {
option-value="value"
></Select>
</OptionRow>
<OptionRow
v-if="capabilities.includes('wine')"
:title="t('cfg.wine.runtime')"
>
<FilePicker
:directory="false"
:value="prf.current!.data.wine.runtime"
:callback="
(value: string) => (prf.current!.data.wine.runtime = value)
"
></FilePicker>
</OptionRow>
<OptionRow
v-if="capabilities.includes('wine')"
:title="t('cfg.wine.prefix')"
>
<FilePicker
:directory="true"
:value="prf.current!.data.wine.prefix"
:callback="
(value: string) => (prf.current!.data.wine.prefix = value)
"
></FilePicker>
</OptionRow>
</OptionCategory>
</template>

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>

27
src/i18n.ts Normal file
View File

@ -0,0 +1,27 @@
import en from './i18n/en';
import { createI18n } from 'vue-i18n';
export type Locale = 'en' | 'ja' | 'pl';
const loadLocaleMessages = async (locale: Locale) => {
return (await import(`./i18n/${locale}.ts`)).default;
};
const i18n = createI18n({
legacy: false,
locale: 'en',
fallbackLocale: 'en',
warnHtmlInMessage: false,
warnHtmlMessage: false,
messages: { en, ja: {}, pl: {} },
});
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 };

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

@ -0,0 +1,248 @@
export default {
ok: 'OK',
cancel: 'Cancel',
enable: 'Enable',
disable: 'Disable',
default: 'Default',
search: 'Search',
next: 'Next',
skip: 'Skip',
close: 'Close',
by: 'by {namespace}',
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: 'Skip checks and start',
shortcut: 'Create desktop shortcut',
help: 'Help',
refresh: 'Reapply mods and start',
cache: 'Clear mod 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',
importTemplate: 'Import template',
exportTemplate: 'Export template',
export: 'Export',
},
creator: {
header: 'Package creator',
basic: 'Basic information',
name: 'Name',
description: 'Description',
website: 'Website',
type: 'Package type',
rainy: 'Standard',
segatools: 'Segatools',
native: 'Native',
games: 'Games',
packageFormat: 'Package format spec',
},
store: {
installRecommended: 'Install recommended packages',
installed: 'Show installed',
deprecated: 'Show deprecated',
nsfw: 'Show NSFW',
incompatible: 'This package is currently incompatible with STARTLINER.',
includeCategories: 'Include categories',
excludeCategories: 'Exclude categories',
},
pkglist: {
missing: 'Missing',
local: 'Local packages',
namespace: 'By namespace',
type: 'By type',
category: 'By category',
standard: 'Standard mods',
native: 'Native mods',
segatools: 'segatools',
unsupported: 'Unsupported',
exclusions: 'Exclusions:',
},
patch: {
loading: 'Loading...',
noneFound:
"No compatible patches found. Make sure you're using unpacked and unpatched files.",
forceLoad: 'Force load',
// Example patch name override
// 'standard-no-encryption': 'No encryption',
// 'standard-no-encryption-tooltip': 'Will also disable TLS',
// It is also possible to add a tooltip where there normally is none
// 'standard-maximum-tracks-tooltip': 'The number of tracks per credit',
// For more info check https://gitea.tendokyu.moe/akanyan/STARTLINER/wiki/Translation-%26-Localization
},
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',
ioModulesDesc: 'This should match your desired input method.',
ioBuiltIn: 'segatools built-in (keyboard)',
io4: 'Native IO4',
installTooltip: '{thing} can be downloaded from the package store.',
},
display: {
title: 'Display',
resolution: 'Game resolution',
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',
window: 'Window',
borderless: 'Borderless window',
fullscreen: 'Fullscreen',
},
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',
irTooltip:
'When playing on an actual keyboard, only bind ir1; leave the rest unbound',
},
wine: {
prefix: 'Wine prefix',
runtime: 'Wine runtime',
},
startliner: {
offlineMode: 'Offline mode',
offlineModeTooltip: 'Disables the package store.',
autoUpdate: 'Automatic updates',
verbose: 'Detailed logs',
},
},
onboarding: {
or: 'or',
backButton: 'a button on the back of the controller',
standard: `
You might get stuck on the following screen:
{bigblack}Aグループの基準機から設定を取得{endbig}
In which case, you should go to the test menu, and in game settings {black}ゲーム設定{end} switch from "follow the standard machine" {black}基準機に従う{end} to "standard machine" {black}基準機{end}.
The test menu can be accessed with %TESTMENU%.
`,
'ongeki-system-processing': `
You might get stuck on this screen for several minutes. _This is normal_. The game just takes a long time to load data.
If you install <code>7EVENDAYSHOLIDAYS/LoadBoost</code>, subsequent launches will be much faster.
`,
'ongeki-lever': `
You also have to calibrate the lever, or you may get the error 3301.
Go to lever settings ({black}レバー設定{end}), move the lever to both edges, then press "end" ({black}終了{end}) and "save" ({black}保存する{end}).
`,
'chunithm-server': `
If you're stuck on this screen, restart the game.
If the problem persists, {link}check your network configuration{endlink}
`,
finale: `
You can access this page any time by right-clicking the START button.
Additional resources:
- {segaguide}SEGAguide{endlink}
- {twotorial}two-torial{endlink}
## Have fun
`,
},
};

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タイプ',
},
},
};

288
src/i18n/pl.ts Normal file
View File

@ -0,0 +1,288 @@
export default {
ok: 'OK',
cancel: 'Anuluj',
enable: 'Włącz',
disable: 'Wyłącz',
default: 'Domyślne',
search: 'Wyszukaj',
next: 'Dalej',
skip: 'Pomiń',
close: 'Zamknij',
by: 'od {namespace}',
start: {
failed: 'Uruchomienie nie powiodło się',
accept: 'Uruchom mimo to',
error: {
package: 'Brakujący pakiet',
dependency: 'Brakująca dependencja',
tool: 'Brakujące narzędzie',
unknown: 'Nieznany błąd',
},
tooltip: {
game: 'Należy najpierw wskazać lokalizację gry',
amfs: 'Należy najpierw wskazać lokalizację amfs',
segatools: 'Należy dodać hook segatools',
},
button: {
start: 'START',
stop: 'STOP',
unchecked: 'Uruchom bez sprawdzania',
shortcut: 'Utwórz skrót',
help: 'Pomoc',
refresh: 'Uruchom po re-aplikacji modów',
cache: 'Wyczyść mod cache',
},
},
game: {
ongeki: 'O.N.G.E.K.I.',
chunithm: 'CHUNITHM',
},
profile: {
welcome: 'Witaj w STARTLINERZE! Zacznij od utworzenia profilu',
create: 'Profil {game}',
delete: 'Usuń profil',
reallyDelete: 'Czy na pewno chcesz usunąć {profile}?',
template: 'Szablon',
importTemplate: 'Importuj szablon',
exportTemplate: 'Eksportuj szablon',
export: 'Eksportuj',
},
creator: {
header: 'Kreator pakietów',
basic: 'Podstawowe informacje',
name: 'Nazwa',
description: 'Opis',
website: 'Strona internetowa',
type: 'Typ',
rainy: 'Standardowy',
segatools: 'Segatools',
native: 'Natywny',
games: 'Gry',
packageFormat: 'Specyfikacja formatu',
},
store: {
installRecommended: 'Dodaj zalecane pakiety',
installed: 'Pokaż zainstalowane',
deprecated: 'Pokaż przestarzałe',
nsfw: 'Pokaż mityczny O.N.G.E.K.I. Sex Mod dlaczego ta opcja w ogóle tu jest',
incompatible:
'Ten pakiet jest obecnie niekompatybilny ze STARTLINEREM.',
includeCategories: 'Włącz kategorie',
excludeCategories: 'Wyłącz kategorie',
},
pkglist: {
missing: 'Niedostępne',
local: 'Lokalne pakiety',
namespace: 'Po przestrzeni nazw',
type: 'Po typie',
category: 'Po kategorii',
standard: 'Standardowe mody',
native: 'Natywne mody',
segatools: 'segatools',
unsupported: 'Niewspierane',
exclusions: 'Czarna lista:',
},
patch: {
loading: 'Wczytuję...',
noneFound:
'Brak kompatybilnych łatek. Upewnij się, że używasz czystych odpakowanych plików.',
forceLoad: 'Wymuś załadowanie',
'standard-shared-audio':
'Wymuś współdzielony tryb dźwięku; częstotliwość w systemie musi wynosić 48kHz',
'standard-shared-audio-tooltip':
'Poprawia kompatybilność, ale może zwiększyć opóźnienie',
'standard-2ch': 'Wymuś stereo',
'standard-2ch-tooltip': 'Może powodować bass overload',
'standard-song-timer': 'Wyłącz timer wyboru utworu',
'standard-map-timer': 'Timer wyboru mapy',
'standard-map-timer-tooltip':
'Jeśli ustawiony na wartość ujemną, timer wyniesie 968 + wartość (np. 968 + -1 = 967)',
'standard-ticket-timer': 'Timer wyboru biletu',
'standard-ticket-timer-tooltip':
'Jeśli ustawiony na wartość ujemną, timer wyniesie 968 + wartość (np. 968 + -1 = 967)',
'standard-course-timer': 'Timer wyboru dana',
'standard-course-timer-tooltip':
'Jeśli ustawiony na wartość ujemną, timer wyniesie 968 + wartość (np. 968 + -1 = 967)',
'standard-unlimited-tracks': 'Nieograniczona maksymalna liczba utworów',
'standard-unlimited-tracks-tooltip':
'Konieczne do grania więcej niż 7 utworów na kredyt',
'standard-maximum-tracks': 'Maksymalna liczba utworów',
'standard-no-encryption': 'Wyłącz szyfrowanie',
'standard-no-encryption-tooltip': 'Wyłączy również TLS',
'standard-no-tls': 'Wyłącz TLS',
'standard-no-tls-tooltip': 'Obejście problemów z serwerem tytułowym',
'standard-head-to-head': 'Napraw head to head',
'standard-head-to-head-tooltip':
'Naprawia nieskończoną synchronizację podczas próby połączenia w trybie head to head',
'standard-bypass-1080p': 'Obejdź sprawdzenie 1080p',
'standard-bypass-120hz': 'Obejdź sprawdzenie 120Hz',
'standard-force-free-play-text': 'Wymuś tekst kredytu FREE PLAY',
'standard-force-free-play-text-tooltip':
'Zastępuje liczbę kredytów tekstem FREE PLAY',
'standard-custom-free-play-length': 'Długość tekstu FREE PLAY',
'standard-custom-free-play-length-tooltip':
'Zmienia długość tekstu wyświetlanego, gdy włączony jest wymuszony tekst kredytu FREE PLAY',
'standard-custom-free-play-text': 'Customowy tekst FREE PLAY',
'standard-custom-free-play-text-tooltip': 'Zastąp tekst FREE PLAY',
'standard-localhost':
'Zezwól na serwer pod adresem 127.0.0.1/localhost',
'standard-credit-freeze': 'Zamroź kredyty',
'standard-credit-freeze-tooltip':
'Zapobiega używaniu kredytów. Co najmniej jeden kredyt musi być dostępny, aby rozpocząć grę lub zakupić bilety premium.',
'standard-openssl-fix': 'Napraw błąd OpenSSL SHA',
'standard-openssl-fix-tooltip':
'Naprawia crash na procesorach Intel 10. generacji i nowszych',
},
cfg: {
afterRestart: 'Wymaga restartu.',
hardware: 'Prawdziwy czytnik',
segatools: {
general: 'Ogólne',
builtIn: 'Wbudowany emulator',
targetTooltip:
'STARTLINER oczekuje czystych danych, pomijając odpakowane exe.',
hooks: 'Hooki',
ioModules: 'Moduły IO',
ioModulesDesc:
'Powinien odpowiadać twojej preferowanej metodzie wejścia.',
ioBuiltIn: 'Wbudowany emulator (klawiatura)',
io4: 'Natywne IO4',
installTooltip: '{thing} można pobrać z pobierajki pakietów.',
},
display: {
title: 'Ekran',
resolution: 'Rozdzielczość',
primary: 'Główny',
target: 'Docelowy wyświetlacz',
mode: 'Tryb',
rotation: 'Obrót',
refreshRate: 'Częstotliwość odświeżania',
borderlessFullscreen: 'Bezramkowy tryb pełnoekranowy',
borderlessFullscreenTooltip:
'Dopasuj rozdzielczość wyświetlacza do gry.',
dontSwitchPrimary: 'Pomiń przełączanie głównego wyświetlacza',
dontSwitchPrimaryTooltip:
'Włącz tę opcję tylko wtedy, gdy przełączanie głównego wyświetlacza powoduje problemy. Monitory muszą mieć dopasowaną częstotliwość odświeżania.',
index: 'Indeks wyświetlacza',
portrait: 'Pion',
landscape: 'Poziom',
flipped: 'Odwrócony',
window: 'Okno',
borderless: 'Okno bez ramki',
fullscreen: 'Pełny ekran',
},
network: {
title: 'Sieć',
type: 'Typ sieci',
remote: 'Zdalny',
localArtemis: 'Lokalny (ARTEMiS)',
artemisPath: 'Lokalizacja ARTEMiSa',
address: 'Adres serwera',
keychip: 'Keychip',
subnet: 'Podsieć',
addrSuffix: 'Sufiks adresu',
},
aime: {
type: 'Typ Aime',
modules: 'Moduły Aime',
code: 'Kod Aime',
codeTooltip:
'Dotyczy tylko wbudowanej emulacji lub zgodnych pakietów',
aimedb: 'Użyj AiMeDB dla kart fizycznych',
aimedbTooltip:
'Decyduje czy karty fizyczne powinny używać AiMeDB do pobierania kodów dostępu. Jeśli łączysz się z hostowaną siecią, włącz tę opcję, aby załadować te same dane konta, jakie uzyskałxbyś na fizycznym cabie.',
serialPort: 'Port szeregowy Aime',
serialPortTooltip: `Porty można sprawdzić w Urządzeniach i drukarkach lub na googlechromelabs.github.io/serial-terminal
Dla AIC Pico powinien być wybrany port AIME.`,
serverName: 'Nazwa serwera',
},
misc: {
title: 'Różne',
intel: 'Obejście buga OpenSSL dla procesorów Intel ≥10 generacji',
intelTooltip: 'Zaleca się zamiast tego załatać amdaemon.',
other: 'Inne opcje segatools',
otherTooltip:
'Zaawansowane lub sytuacyjne opcje, które nie są objęte przez STARTLINERA',
},
extensions: {
title: 'Rozszerzenia',
bepInExConsole: 'Konsola BepInExa',
audioMode: 'Tryb audio',
audioTooltip:
'Tryb ekskluzywny 2-kanałowy wymaga 7EVENDAYSHOLIDAYS-ExclusiveAudio',
audioShared: 'Współdzielony',
audio6Ch: 'Ekskluzywny 6-kanałowy',
audio2Ch: 'Ekskluzywny 2-kanałowy',
sampleRate: 'Częstotliwość',
blacklist: 'Czarna lista utworów',
blacklistTooltip:
'Utwory w tym zakresie ID nie będą zapisywane ani przesyłane',
bonusTracks: 'Odblokuj Bonusowe Utwory',
bonusTracksTooltip:
'Wyłączenie tej opcji może pomóc w uporządkowaniu listy utworów',
saekawa: 'Plik konfiguracyjny Saekawy',
inohara: 'Plik konfiguracyjny Inohary',
},
keyboard: {
title: 'Klawiatura',
tooltip:
'Dotyczy tylko wtedy, gdy moduł IO jest ustawiony na wbudowaną emulację lub zgodny moduł (np. mu3io.NET)',
leverMode: 'Tryb wajchy',
mouse: 'Mysz',
irTooltip:
'Jeśli grasz na klawiaturze, ustaw tylko ir1; pozostałe zostaw wyłączone',
},
wine: {
prefix: 'Wine prefix',
runtime: 'Lokalizacja Wine',
},
startliner: {
offlineMode: 'Tryb offline',
offlineModeTooltip: 'Wyłącza pobierajkę pakietów.',
autoUpdate: 'Automatyczne aktualizacje',
verbose: 'Szczegółowe logi',
},
},
onboarding: {
or: 'lub',
backButton: 'przycisku z tyłu',
standard: `
Możesz utknąć na następującym ekranie:
{bigblack}Aグループの基準機から設定を取得{endbig}
Wówczas musisz przejść do menu testowego i w ustawieniach gry {black}ゲーム設定{end} przełączyć z "podążaj za standardem" {black}基準機に従う{end} na "standard" {black}基準機{end}.
Do menu testowego możesz dostać się za pomocą %TESTMENU%.
`,
'ongeki-system-processing': `
Możesz utknąć na tym ekranie przez kilka(naście) minut. _To jest normalne_. Gra po prostu potrzebuje dużo czasu na załadowanie danych.
Jeśli zainstalujesz <code>7EVENDAYSHOLIDAYS/LoadBoost</code>, kolejne uruchomienia będą znacznie szybsze.
`,
'ongeki-lever': `
Musisz również skalibrować wajchę; w przeciwnym razie możesz otrzymać błąd 3301.
Przejdź do ustawień wajchy ({black}レバー設定{end}), przesuń wajchę do obu krawędzi, a następnie naciśnij "koniec" ({black}終了{end}) i "zapisz" ({black}保存する{end}).
`,
'chunithm-server': `
Jeśli utkniesz na tym ekranie, zrestartuj grę.
Jeśli problem będzie się powtarzał, {link}sprawdź swoją konfigurację sieciową{endlink}.
`,
finale: `
Możesz uzyskać dostęp do tej strony w każdej chwili, klikając prawym przyciskiem myszy przycisk START.
Dodatkowe zasoby:
- {segaguide}SEGAguide{endlink}
- {twotorial}two-torial{endlink}
## Miłej zabawy
`,
},
};

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 {
@ -31,23 +32,24 @@ export const useGeneralStore = defineStore('general', () => {
},
});
const configDir = computed(() => {
const loadDirs = async () => {
if (dirs.value === null) {
throw new Error('Invalid directory access');
const d = (await invoke('list_directories')) as Dirs;
dirs.value = d;
}
return dirs.value.config_dir;
};
const configDir = computed(async () => {
await loadDirs();
return dirs.value!.config_dir;
});
const dataDir = computed(() => {
if (dirs.value === null) {
throw new Error('Invalid directory access');
}
return dirs.value.data_dir;
const dataDir = computed(async () => {
await loadDirs();
return dirs.value!.data_dir;
});
const cacheDir = computed(() => {
if (dirs.value === null) {
throw new Error('Invalid directory access');
}
return dirs.value.cache_dir;
const cacheDir = computed(async () => {
await loadDirs();
return dirs.value!.cache_dir;
});
return {
@ -320,8 +322,8 @@ export const usePrfStore = defineStore('prf', () => {
const generalStore = useGeneralStore();
const configDir = computed(async () => {
return await path.join(
generalStore.configDir,
return path.join(
await generalStore.configDir,
`profile-${current.value?.meta.game}-${current.value?.meta.name}`
);
});
@ -371,6 +373,11 @@ 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 currentTab: Ref<string> = ref('users');
const pkgListMode: Ref<'namespace' | 'type' | 'category'> =
ref('namespace');
const hiddenCategories: Ref<string[]> = ref([]);
const _scaleValue = (value: ScaleType) =>
value === 's' ? 1 : value === 'm' ? 1.25 : value === 'l' ? 1.5 : 2;
@ -410,7 +417,7 @@ export const useClientStore = defineStore('client', () => {
const input = JSON.parse(
await readTextFile(
await path.join(
generalStore.configDir,
await generalStore.configDir,
'client-options.json'
)
)
@ -433,6 +440,23 @@ export const useClientStore = defineStore('client', () => {
if (input.onboarded) {
onboarded.value = input.onboarded;
}
if (input.locale) {
locale.value = input.locale;
}
if (input.currentTab) {
currentTab.value = input.currentTab;
}
if (input.pkgListMode) {
pkgListMode.value = input.pkgListMode;
}
if (input.hiddenCategories) {
hiddenCategories.value = input.hiddenCategories;
}
await setLocale(locale.value);
await setTheme(theme.value);
} catch (e) {
console.error(`Error reading client options: ${e}`);
@ -457,7 +481,10 @@ export const useClientStore = defineStore('client', () => {
const size = await w.innerSize();
await writeTextFile(
await path.join(generalStore.configDir, 'client-options.json'),
await path.join(
await generalStore.configDir,
'client-options.json'
),
JSON.stringify({
scaleFactor: scaleFactor.value,
windowSize: {
@ -466,6 +493,10 @@ export const useClientStore = defineStore('client', () => {
},
theme: theme.value,
onboarded: onboarded.value,
locale: locale.value,
currentTab: currentTab.value,
pkgListMode: pkgListMode.value,
hiddenCategories: hiddenCategories.value,
})
);
};
@ -518,6 +549,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,8 +569,12 @@ export const useClientStore = defineStore('client', () => {
verbose,
theme,
onboarded,
locale,
timeout,
scaleModel,
currentTab,
pkgListMode,
hiddenCategories,
_scaleValue,
scaleValue,
load,
@ -544,5 +585,6 @@ export const useClientStore = defineStore('client', () => {
setVerbose,
setTheme,
setOnboarded,
setLocale,
};
});

View File

@ -56,6 +56,7 @@ export interface ProfileData {
display: DisplayConfig;
network: NetworkConfig;
bepinex: BepInExConfig;
wine: WineConfig;
mu3_ini: Mu3IniConfig | undefined;
keyboard: KeyboardConfig | undefined;
patches: {
@ -105,6 +106,11 @@ export interface BepInExConfig {
console: boolean;
}
export interface WineConfig {
runtime: string;
prefix: string;
}
export interface Mu3IniConfig {
audio?: 'Shared' | 'Excl6Ch' | 'Excl2Ch';
sample_rate: number;
@ -167,7 +173,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}`,