feat: verbose toggle, info tab, many misc fixes
This commit is contained in:
52
CHANGELOG.md
Normal file
52
CHANGELOG.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
## 0.7.0
|
||||||
|
|
||||||
|
- Hopefully fixed issues with the download button
|
||||||
|
- Added a verbose logging option
|
||||||
|
- Added an info tab
|
||||||
|
- Instead of auto-installing segatools & mempatcher at launch, the package store now shows a "install recommended" button
|
||||||
|
|
||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
- Added support for O.N.G.E.K.I. English Translation
|
||||||
|
- Disabled the icon buttons as they broke at some point
|
||||||
|
|
||||||
|
## 0.6.0
|
||||||
|
|
||||||
|
- Chunithm: added support for DLLs (saekawa, mempatcher)
|
||||||
|
- Chunithm: added a patch interface
|
||||||
|
- Chunithm: added display settings
|
||||||
|
- Chunithm: removed split IR
|
||||||
|
- Both games: added hardware aime reader support
|
||||||
|
- Added an update progress bar
|
||||||
|
|
||||||
|
## 0.5.0
|
||||||
|
|
||||||
|
- Added a keyboard configuration UI (for both games)
|
||||||
|
- Added a prompt after selecting `mu3.exe`/`chusanApp.exe` in the file picker, allowing you to copy much of the data from `segatools.ini`, if it already exists.
|
||||||
|
- Added an option to not switch the primary monitor.
|
||||||
|
- This is not recommended to use but it may help when the primary monitor switcher doesn't work correctly.
|
||||||
|
|
||||||
|
## 0.4.0
|
||||||
|
|
||||||
|
- New error dialog.
|
||||||
|
- Added tooltips for IO, Aime, Hook.
|
||||||
|
- Added a welcome message.
|
||||||
|
- Added a separate auto-update toggle.
|
||||||
|
- Fixed a display bug with the offline mode toggle.
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
- Added UI scaling, offline mode, and an 'update all' button
|
||||||
|
- First public release
|
||||||
|
|
||||||
|
## 0.2.0
|
||||||
|
|
||||||
|
- Added a context menu for the start button with additional launch options
|
||||||
|
- Added an audio mode button for ongeki
|
||||||
|
- Fixed the launcher freezing while the game is running
|
||||||
|
- Probably added auto-updates
|
||||||
|
|
||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
⚠️ this release is incomplete and potentially cursed
|
||||||
|
it's more of a preview of a preview
|
4
TODO.md
4
TODO.md
@ -1,11 +1,9 @@
|
|||||||
### Short-term
|
### Short-term
|
||||||
|
|
||||||
- CHUNITHM support
|
|
||||||
- https://gitea.tendokyu.moe/TeamTofuShop/segatools/issues/63
|
- https://gitea.tendokyu.moe/TeamTofuShop/segatools/issues/63
|
||||||
|
|
||||||
### Long-term
|
### Long-term
|
||||||
|
|
||||||
- Auto-updates
|
|
||||||
- Progress bars and other GUI sugar
|
- Progress bars and other GUI sugar
|
||||||
- IO DLLs and artemis as special packages
|
- artemis as a special package
|
||||||
- Other arcade games (if there is demand)
|
- Other arcade games (if there is demand)
|
||||||
|
35
bun.lock
35
bun.lock
@ -4,10 +4,11 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "startliner",
|
"name": "startliner",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@f3ve/vue-markdown-it": "^0.2.3",
|
||||||
"@mdi/font": "7.4.47",
|
"@mdi/font": "7.4.47",
|
||||||
"@primevue/forms": "^4.3.3",
|
"@primevue/forms": "^4.3.3",
|
||||||
"@primevue/themes": "^4.3.3",
|
"@primevue/themes": "^4.3.3",
|
||||||
"@tailwindcss/vite": "^4.1.2",
|
"@tailwindcss/vite": "^4.1.3",
|
||||||
"@tauri-apps/api": "^2.4.1",
|
"@tauri-apps/api": "^2.4.1",
|
||||||
"@tauri-apps/plugin-cli": "^2.2.0",
|
"@tauri-apps/plugin-cli": "^2.2.0",
|
||||||
"@tauri-apps/plugin-deep-link": "~2.2.1",
|
"@tauri-apps/plugin-deep-link": "~2.2.1",
|
||||||
@ -17,29 +18,31 @@
|
|||||||
"@tauri-apps/plugin-shell": "~2.2.1",
|
"@tauri-apps/plugin-shell": "~2.2.1",
|
||||||
"@tauri-apps/plugin-updater": "^2.7.0",
|
"@tauri-apps/plugin-updater": "^2.7.0",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||||
"pinia": "^3.0.1",
|
"@types/markdown-it": "^14.1.2",
|
||||||
|
"markdown-it": "^14.1.0",
|
||||||
|
"pinia": "^3.0.2",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primevue": "^4.3.3",
|
"primevue": "^4.3.3",
|
||||||
"roboto-fontface": "^0.10.0",
|
"roboto-fontface": "^0.10.0",
|
||||||
"tailwindcss": "^4.1.2",
|
"tailwindcss": "^4.1.3",
|
||||||
"tailwindcss-primeui": "^0.4.0",
|
"tailwindcss-primeui": "^0.4.0",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vuetify": "^3.8.0",
|
"vuetify": "^3.8.1",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2.4.1",
|
"@tauri-apps/cli": "^2.4.1",
|
||||||
"@tsconfig/node22": "^22.0.1",
|
"@tsconfig/node22": "^22.0.1",
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.1",
|
||||||
"@vitejs/plugin-vue": "^5.2.3",
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
"@vue/eslint-config-typescript": "^14.5.0",
|
"@vue/eslint-config-typescript": "^14.5.0",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"npm-run-all2": "^7.0.2",
|
"npm-run-all2": "^7.0.2",
|
||||||
"sass": "1.77.8",
|
"sass": "1.77.8",
|
||||||
"sass-embedded": "^1.86.3",
|
"sass-embedded": "^1.86.3",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.8.3",
|
||||||
"unplugin-fonts": "^1.3.1",
|
"unplugin-fonts": "^1.3.1",
|
||||||
"unplugin-vue-components": "^0.27.5",
|
"unplugin-vue-components": "^0.27.5",
|
||||||
"vite": "^6.2.5",
|
"vite": "^6.2.6",
|
||||||
"vite-plugin-vuetify": "^2.1.1",
|
"vite-plugin-vuetify": "^2.1.1",
|
||||||
"vue-tsc": "^2.2.8",
|
"vue-tsc": "^2.2.8",
|
||||||
},
|
},
|
||||||
@ -132,6 +135,8 @@
|
|||||||
|
|
||||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.5", "", { "dependencies": { "@eslint/core": "^0.10.0", "levn": "^0.4.1" } }, "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A=="],
|
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.5", "", { "dependencies": { "@eslint/core": "^0.10.0", "levn": "^0.4.1" } }, "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A=="],
|
||||||
|
|
||||||
|
"@f3ve/vue-markdown-it": ["@f3ve/vue-markdown-it@0.2.3", "", { "dependencies": { "markdown-it": "^14.1.0" }, "peerDependencies": { "vue": "^3.3.4" } }, "sha512-v0VNd7wb55kwsUUy3n6DLI9+0FYSG0PrCTD3bWuSRo6WS3OHD5wghh/aHzebVdsVkSBXfVpiEUlMA3DrxLs7Lw=="],
|
||||||
|
|
||||||
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||||
|
|
||||||
"@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
|
"@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
|
||||||
@ -292,6 +297,12 @@
|
|||||||
|
|
||||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||||
|
|
||||||
|
"@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="],
|
||||||
|
|
||||||
|
"@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="],
|
||||||
|
|
||||||
|
"@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="],
|
"@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.29.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.29.0", "@typescript-eslint/type-utils": "8.29.0", "@typescript-eslint/utils": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ=="],
|
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.29.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.29.0", "@typescript-eslint/type-utils": "8.29.0", "@typescript-eslint/utils": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ=="],
|
||||||
@ -540,6 +551,8 @@
|
|||||||
|
|
||||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="],
|
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="],
|
||||||
|
|
||||||
|
"linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
|
||||||
|
|
||||||
"local-pkg": ["local-pkg@0.5.1", "", { "dependencies": { "mlly": "^1.7.3", "pkg-types": "^1.2.1" } }, "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ=="],
|
"local-pkg": ["local-pkg@0.5.1", "", { "dependencies": { "mlly": "^1.7.3", "pkg-types": "^1.2.1" } }, "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ=="],
|
||||||
|
|
||||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||||
@ -550,6 +563,10 @@
|
|||||||
|
|
||||||
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
||||||
|
|
||||||
|
"markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="],
|
||||||
|
|
||||||
|
"mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="],
|
||||||
|
|
||||||
"memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="],
|
"memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="],
|
||||||
|
|
||||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||||
@ -620,6 +637,8 @@
|
|||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
|
"punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
|
||||||
|
|
||||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||||
|
|
||||||
"read-package-json-fast": ["read-package-json-fast@4.0.0", "", { "dependencies": { "json-parse-even-better-errors": "^4.0.0", "npm-normalize-package-bin": "^4.0.0" } }, "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg=="],
|
"read-package-json-fast": ["read-package-json-fast@4.0.0", "", { "dependencies": { "json-parse-even-better-errors": "^4.0.0", "npm-normalize-package-bin": "^4.0.0" } }, "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg=="],
|
||||||
@ -726,6 +745,8 @@
|
|||||||
|
|
||||||
"typescript-eslint": ["typescript-eslint@8.29.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.29.0", "@typescript-eslint/parser": "8.29.0", "@typescript-eslint/utils": "8.29.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg=="],
|
"typescript-eslint": ["typescript-eslint@8.29.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.29.0", "@typescript-eslint/parser": "8.29.0", "@typescript-eslint/utils": "8.29.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg=="],
|
||||||
|
|
||||||
|
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
|
||||||
|
|
||||||
"ufo": ["ufo@1.5.4", "", {}, "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="],
|
"ufo": ["ufo@1.5.4", "", {}, "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@f3ve/vue-markdown-it": "^0.2.3",
|
||||||
"@mdi/font": "7.4.47",
|
"@mdi/font": "7.4.47",
|
||||||
"@primevue/forms": "^4.3.3",
|
"@primevue/forms": "^4.3.3",
|
||||||
"@primevue/themes": "^4.3.3",
|
"@primevue/themes": "^4.3.3",
|
||||||
@ -23,6 +24,8 @@
|
|||||||
"@tauri-apps/plugin-shell": "~2.2.1",
|
"@tauri-apps/plugin-shell": "~2.2.1",
|
||||||
"@tauri-apps/plugin-updater": "^2.7.0",
|
"@tauri-apps/plugin-updater": "^2.7.0",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||||
|
"@types/markdown-it": "^14.1.2",
|
||||||
|
"markdown-it": "^14.1.0",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.2",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primevue": "^4.3.3",
|
"primevue": "^4.3.3",
|
||||||
|
1
rust/Cargo.lock
generated
1
rust/Cargo.lock
generated
@ -4730,6 +4730,7 @@ dependencies = [
|
|||||||
"humantime",
|
"humantime",
|
||||||
"junction",
|
"junction",
|
||||||
"log",
|
"log",
|
||||||
|
"open",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rust-ini",
|
"rust-ini",
|
||||||
|
@ -45,6 +45,7 @@ sha256 = "1.6.0"
|
|||||||
serialport = "4.7.1"
|
serialport = "4.7.1"
|
||||||
fern = { version ="0.7.1", features = ["colored"] }
|
fern = { version ="0.7.1", features = ["colored"] }
|
||||||
humantime = "2.2.0"
|
humantime = "2.2.0"
|
||||||
|
open = "5.3.2"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-cli = "2"
|
tauri-plugin-cli = "2"
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
"fs:allow-data-read-recursive",
|
"fs:allow-data-read-recursive",
|
||||||
"fs:allow-data-write-recursive",
|
"fs:allow-data-write-recursive",
|
||||||
"fs:allow-config-read-recursive",
|
"fs:allow-config-read-recursive",
|
||||||
"fs:allow-config-write-recursive"
|
"fs:allow-config-write-recursive",
|
||||||
|
"shell:allow-open"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
|
use std::time::SystemTime;
|
||||||
use crate::model::config::GlobalConfig;
|
use crate::model::config::GlobalConfig;
|
||||||
use crate::model::patch::PatchFileVec;
|
use crate::model::patch::PatchFileVec;
|
||||||
use crate::pkg::{Feature, Status};
|
use crate::pkg::{Feature, Status};
|
||||||
@ -7,6 +8,7 @@ use crate::{model::misc::Game, pkg::PkgKey};
|
|||||||
use crate::pkg_store::PackageStore;
|
use crate::pkg_store::PackageStore;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use fern::colors::{Color, ColoredLevelConfig};
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
|
|
||||||
pub struct GlobalState {
|
pub struct GlobalState {
|
||||||
@ -34,6 +36,8 @@ impl AppData {
|
|||||||
.and_then(|s| Ok(serde_json::from_str::<GlobalConfig>(&s)?))
|
.and_then(|s| Ok(serde_json::from_str::<GlobalConfig>(&s)?))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
Self::init_logger(&cfg);
|
||||||
|
|
||||||
let profile = match cfg.recent_profile {
|
let profile = match cfg.recent_profile {
|
||||||
Some((game, ref name)) => Profile::load(game, name.clone()).ok(),
|
Some((game, ref name)) => Profile::load(game, name.clone()).ok(),
|
||||||
None => None
|
None => None
|
||||||
@ -127,4 +131,36 @@ impl AppData {
|
|||||||
p.fix(&self.pkgs);
|
p.fix(&self.pkgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn init_logger(cfg: &GlobalConfig) {
|
||||||
|
let mut fern_builder;
|
||||||
|
let colors = ColoredLevelConfig::new()
|
||||||
|
.debug(Color::Green)
|
||||||
|
.info(Color::Blue)
|
||||||
|
.warn(Color::Yellow)
|
||||||
|
.error(Color::Red);
|
||||||
|
|
||||||
|
fern_builder = fern::Dispatch::new()
|
||||||
|
.format(move |out, message, record| {
|
||||||
|
out.finish(format_args!(
|
||||||
|
"[{} {} {}] {}",
|
||||||
|
humantime::format_rfc3339_seconds(SystemTime::now()),
|
||||||
|
colors.color(record.level()),
|
||||||
|
record.target(),
|
||||||
|
message
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.chain(std::io::stdout())
|
||||||
|
.chain(fern::log_file(util::data_dir().join("log.txt")).expect("unable to initialize the logger"));
|
||||||
|
|
||||||
|
if cfg.verbose == true {
|
||||||
|
fern_builder = fern_builder.level(log::LevelFilter::Debug);
|
||||||
|
} else {
|
||||||
|
fern_builder = fern_builder.level(log::LevelFilter::Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = fern_builder.apply() {
|
||||||
|
panic!("unable to initialize the logger? {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -323,9 +323,10 @@ pub async fn duplicate_profile(profile: ProfileMeta) -> Result<(), String> {
|
|||||||
pub async fn delete_profile(state: State<'_, Mutex<AppData>>, profile: ProfileMeta) -> Result<(), String> {
|
pub async fn delete_profile(state: State<'_, Mutex<AppData>>, profile: ProfileMeta) -> Result<(), String> {
|
||||||
log::debug!("invoke: delete_profile({:?})", profile);
|
log::debug!("invoke: delete_profile({:?})", profile);
|
||||||
|
|
||||||
std::fs::remove_dir_all(profile.config_dir())
|
util::remove_dir_all(profile.config_dir())
|
||||||
|
.await
|
||||||
.map_err(|e| format!("Unable to delete {:?}: {}", profile.config_dir(), e))?;
|
.map_err(|e| format!("Unable to delete {:?}: {}", profile.config_dir(), e))?;
|
||||||
if let Err(e) = std::fs::remove_dir_all(profile.data_dir()) {
|
if let Err(e) = util::remove_dir_all(profile.data_dir()).await {
|
||||||
log::warn!("Unable to delete: {:?} {}", profile.data_dir(), e);
|
log::warn!("Unable to delete: {:?} {}", profile.data_dir(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,7 +415,8 @@ pub async fn get_global_config(state: State<'_, Mutex<AppData>>, field: GlobalCo
|
|||||||
let appd = state.lock().await;
|
let appd = state.lock().await;
|
||||||
match field {
|
match field {
|
||||||
GlobalConfigField::OfflineMode => Ok(appd.cfg.offline_mode),
|
GlobalConfigField::OfflineMode => Ok(appd.cfg.offline_mode),
|
||||||
GlobalConfigField::EnableAutoupdates => Ok(appd.cfg.enable_autoupdates)
|
GlobalConfigField::EnableAutoupdates => Ok(appd.cfg.enable_autoupdates),
|
||||||
|
GlobalConfigField::Verbose => Ok(appd.cfg.verbose)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,7 +427,8 @@ pub async fn set_global_config(state: State<'_, Mutex<AppData>>, field: GlobalCo
|
|||||||
let mut appd = state.lock().await;
|
let mut appd = state.lock().await;
|
||||||
match field {
|
match field {
|
||||||
GlobalConfigField::OfflineMode => appd.cfg.offline_mode = value,
|
GlobalConfigField::OfflineMode => appd.cfg.offline_mode = value,
|
||||||
GlobalConfigField::EnableAutoupdates => appd.cfg.enable_autoupdates = value
|
GlobalConfigField::EnableAutoupdates => appd.cfg.enable_autoupdates = value,
|
||||||
|
GlobalConfigField::Verbose => appd.cfg.verbose = value,
|
||||||
};
|
};
|
||||||
appd.write().map_err(|e| e.to_string())
|
appd.write().map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
@ -472,6 +475,18 @@ pub async fn file_exists(path: String) -> Result<bool, ()> {
|
|||||||
Ok(std::fs::exists(path).unwrap_or(false))
|
Ok(std::fs::exists(path).unwrap_or(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Easier than trying to get the barely-documented tauri permissions system to work
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn open_file(path: String) -> Result<(), String> {
|
||||||
|
open::that(path).map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_changelog() -> Result<String, ()> {
|
||||||
|
Ok(include_str!("../../CHANGELOG.md").to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn list_com_ports() -> Result<BTreeMap<String, i32>, String> {
|
pub async fn list_com_ports() -> Result<BTreeMap<String, i32>, String> {
|
||||||
let ports = serialport::available_ports().unwrap_or(Vec::new());
|
let ports = serialport::available_ports().unwrap_or(Vec::new());
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use std::{collections::HashSet, path::PathBuf};
|
use std::{collections::HashSet, path::PathBuf};
|
||||||
|
use futures::Stream;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::{AppHandle, Emitter};
|
use tauri::{AppHandle, Emitter};
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
@ -6,14 +8,20 @@ use anyhow::{anyhow, Result};
|
|||||||
use crate::pkg::{Package, PkgKey, Remote};
|
use crate::pkg::{Package, PkgKey, Remote};
|
||||||
|
|
||||||
pub struct DownloadHandler {
|
pub struct DownloadHandler {
|
||||||
set: HashSet<String>,
|
paths: HashSet<PathBuf>,
|
||||||
app: AppHandle
|
app: AppHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct DownloadTick {
|
||||||
|
pkg_key: PkgKey,
|
||||||
|
ratio: f32
|
||||||
|
}
|
||||||
|
|
||||||
impl DownloadHandler {
|
impl DownloadHandler {
|
||||||
pub fn new(app: AppHandle) -> DownloadHandler {
|
pub fn new(app: AppHandle) -> DownloadHandler {
|
||||||
DownloadHandler {
|
DownloadHandler {
|
||||||
set: HashSet::new(),
|
paths: HashSet::new(),
|
||||||
app
|
app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -22,11 +30,11 @@ impl DownloadHandler {
|
|||||||
let rmt = pkg.rmt.as_ref()
|
let rmt = pkg.rmt.as_ref()
|
||||||
.ok_or_else(|| anyhow!("Attempted to download a package without remote data"))?
|
.ok_or_else(|| anyhow!("Attempted to download a package without remote data"))?
|
||||||
.clone();
|
.clone();
|
||||||
if self.set.contains(zip_path.to_string_lossy().as_ref()) {
|
if self.paths.contains(zip_path) {
|
||||||
// Todo when there is a clear cache button, it should clear the set
|
Ok(())
|
||||||
Err(anyhow!("Already downloading"))
|
|
||||||
} else {
|
} else {
|
||||||
self.set.insert(zip_path.to_string_lossy().to_string());
|
// TODO clear cache button should clear this
|
||||||
|
self.paths.insert(zip_path.clone());
|
||||||
tauri::async_runtime::spawn(Self::download_zip_proc(self.app.clone(), zip_path.clone(), pkg.key(), rmt));
|
tauri::async_runtime::spawn(Self::download_zip_proc(self.app.clone(), zip_path.clone(), pkg.key(), rmt));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -42,10 +50,15 @@ impl DownloadHandler {
|
|||||||
|
|
||||||
let mut cache_file_w = File::create(&zip_path_part).await?;
|
let mut cache_file_w = File::create(&zip_path_part).await?;
|
||||||
let mut byte_stream = reqwest::get(&rmt.download_url).await?.bytes_stream();
|
let mut byte_stream = reqwest::get(&rmt.download_url).await?.bytes_stream();
|
||||||
|
let first_hint = byte_stream.size_hint().0 as f32;
|
||||||
|
|
||||||
log::info!("downloading: {}", rmt.download_url);
|
log::info!("downloading: {}", rmt.download_url);
|
||||||
while let Some(item) = byte_stream.next().await {
|
while let Some(item) = byte_stream.next().await {
|
||||||
let i = item?;
|
let i = item?;
|
||||||
|
app.emit("download-tick", DownloadTick {
|
||||||
|
pkg_key: pkg_key.clone(),
|
||||||
|
ratio: 1.0f32 - (byte_stream.size_hint().0 as f32) / first_hint
|
||||||
|
})?;
|
||||||
cache_file_w.write_all(&mut i.as_ref()).await?;
|
cache_file_w.write_all(&mut i.as_ref()).await?;
|
||||||
}
|
}
|
||||||
cache_file_w.sync_all().await?;
|
cache_file_w.sync_all().await?;
|
||||||
|
@ -9,11 +9,10 @@ mod modules;
|
|||||||
mod profiles;
|
mod profiles;
|
||||||
mod patcher;
|
mod patcher;
|
||||||
|
|
||||||
use std::{sync::OnceLock, time::SystemTime};
|
use std::sync::OnceLock;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use closure::closure;
|
use closure::closure;
|
||||||
use appdata::{AppData, ToggleAction};
|
use appdata::{AppData, ToggleAction};
|
||||||
use fern::colors::{Color, ColoredLevelConfig};
|
|
||||||
use model::misc::Game;
|
use model::misc::Game;
|
||||||
use pkg::PkgKey;
|
use pkg::PkgKey;
|
||||||
use pkg_store::Payload;
|
use pkg_store::Payload;
|
||||||
@ -48,42 +47,7 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
|
|
||||||
util::init_dirs(&apph);
|
util::init_dirs(&apph);
|
||||||
|
|
||||||
let mut fern_builder;
|
let mut app_data = AppData::new(app.handle().clone());
|
||||||
{
|
|
||||||
let colors = ColoredLevelConfig::new()
|
|
||||||
.debug(Color::Green)
|
|
||||||
.info(Color::Blue)
|
|
||||||
.warn(Color::Yellow)
|
|
||||||
.error(Color::Red);
|
|
||||||
|
|
||||||
fern_builder = fern::Dispatch::new()
|
|
||||||
.format(move |out, message, record| {
|
|
||||||
out.finish(format_args!(
|
|
||||||
"[{} {} {}] {}",
|
|
||||||
humantime::format_rfc3339_seconds(SystemTime::now()),
|
|
||||||
colors.color(record.level()),
|
|
||||||
record.target(),
|
|
||||||
message
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.chain(std::io::stdout())
|
|
||||||
.chain(fern::log_file(util::data_dir().join("log.txt")).expect("unable to initialize the logger"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
{
|
|
||||||
fern_builder = fern_builder.level(log::LevelFilter::Debug);
|
|
||||||
}
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
{
|
|
||||||
if std::env::var("DEBUG_LOG").is_ok() {
|
|
||||||
fern_builder = fern_builder.level(log::LevelFilter::Debug);
|
|
||||||
} else {
|
|
||||||
fern_builder = fern_builder.level(log::LevelFilter::Info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fern_builder.apply()?;
|
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"running from {}",
|
"running from {}",
|
||||||
@ -93,7 +57,6 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut app_data = AppData::new(app.handle().clone());
|
|
||||||
let start_immediately;
|
let start_immediately;
|
||||||
|
|
||||||
if let Ok(matches) = app.cli().matches() {
|
if let Ok(matches) = app.cli().matches() {
|
||||||
@ -244,6 +207,8 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
cmd::list_platform_capabilities,
|
cmd::list_platform_capabilities,
|
||||||
cmd::list_directories,
|
cmd::list_directories,
|
||||||
cmd::file_exists,
|
cmd::file_exists,
|
||||||
|
cmd::open_file,
|
||||||
|
cmd::get_changelog,
|
||||||
|
|
||||||
cmd::list_com_ports,
|
cmd::list_com_ports,
|
||||||
|
|
||||||
@ -326,7 +291,7 @@ async fn update(app: tauri::AppHandle) -> tauri_plugin_updater::Result<()> {
|
|||||||
update.download_and_install(
|
update.download_and_install(
|
||||||
|chunk_length, content_length| {
|
|chunk_length, content_length| {
|
||||||
downloaded += chunk_length;
|
downloaded += chunk_length;
|
||||||
_ = app.emit("update-progress", (chunk_length as f64) / (content_length.unwrap_or(u64::MAX) as f64));
|
_ = app.emit("update-progress", (downloaded as f64) / (content_length.unwrap_or(u64::MAX) as f64));
|
||||||
},
|
},
|
||||||
|| {
|
|| {
|
||||||
log::info!("download finished");
|
log::info!("download finished");
|
||||||
|
@ -2,10 +2,12 @@ use serde::{Deserialize, Serialize};
|
|||||||
use super::misc::Game;
|
use super::misc::Game;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct GlobalConfig {
|
pub struct GlobalConfig {
|
||||||
pub recent_profile: Option<(Game, String)>,
|
pub recent_profile: Option<(Game, String)>,
|
||||||
pub offline_mode: bool,
|
pub offline_mode: bool,
|
||||||
pub enable_autoupdates: bool,
|
pub enable_autoupdates: bool,
|
||||||
|
pub verbose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GlobalConfig {
|
impl Default for GlobalConfig {
|
||||||
@ -13,13 +15,16 @@ impl Default for GlobalConfig {
|
|||||||
Self {
|
Self {
|
||||||
recent_profile: Default::default(),
|
recent_profile: Default::default(),
|
||||||
offline_mode: false,
|
offline_mode: false,
|
||||||
enable_autoupdates: true
|
enable_autoupdates: true,
|
||||||
|
verbose: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum GlobalConfigField {
|
pub enum GlobalConfigField {
|
||||||
OfflineMode,
|
OfflineMode,
|
||||||
EnableAutoupdates
|
EnableAutoupdates,
|
||||||
|
Verbose
|
||||||
}
|
}
|
@ -5,10 +5,9 @@ use crate::pkg::PkgKey;
|
|||||||
use super::profile::ProfileModule;
|
use super::profile::ProfileModule;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Copy)]
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Copy)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum Game {
|
pub enum Game {
|
||||||
#[serde(rename = "ongeki")]
|
|
||||||
Ongeki,
|
Ongeki,
|
||||||
#[serde(rename = "chunithm")]
|
|
||||||
Chunithm,
|
Chunithm,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,10 +88,9 @@ pub enum StartCheckError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone)]
|
#[derive(Default, Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct ConfigHook {
|
pub struct ConfigHook {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub allnet_auth: Option<ConfigHookAuth>,
|
pub allnet_auth: Option<ConfigHookAuth>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub aime: Option<ConfigHookAime>,
|
pub aime: Option<ConfigHookAime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ pub enum Aime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct AMNet {
|
pub struct AMNet {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub addr: String,
|
pub addr: String,
|
||||||
@ -26,18 +27,17 @@ impl Default for AMNet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug, Default )]
|
||||||
|
#[serde(default)]
|
||||||
pub struct Segatools {
|
pub struct Segatools {
|
||||||
pub target: PathBuf,
|
pub target: PathBuf,
|
||||||
pub hook: Option<PkgKey>,
|
pub hook: Option<PkgKey>,
|
||||||
pub io: Option<PkgKey>,
|
pub io: Option<PkgKey>,
|
||||||
#[serde(default)]
|
|
||||||
pub aime: Aime,
|
pub aime: Aime,
|
||||||
pub amfs: PathBuf,
|
pub amfs: PathBuf,
|
||||||
pub option: PathBuf,
|
pub option: PathBuf,
|
||||||
pub appdata: PathBuf,
|
pub appdata: PathBuf,
|
||||||
pub intel: bool,
|
pub intel: bool,
|
||||||
#[serde(default)]
|
|
||||||
pub amnet: AMNet,
|
pub amnet: AMNet,
|
||||||
pub aime_port: Option<i32>,
|
pub aime_port: Option<i32>,
|
||||||
}
|
}
|
||||||
@ -69,7 +69,8 @@ pub enum DisplayMode {
|
|||||||
Fullscreen
|
Fullscreen
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct Display {
|
pub struct Display {
|
||||||
pub target: String,
|
pub target: String,
|
||||||
pub rez: (i32, i32),
|
pub rez: (i32, i32),
|
||||||
@ -77,11 +78,7 @@ pub struct Display {
|
|||||||
pub rotation: Option<i32>,
|
pub rotation: Option<i32>,
|
||||||
pub frequency: i32,
|
pub frequency: i32,
|
||||||
pub borderless_fullscreen: bool,
|
pub borderless_fullscreen: bool,
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub dont_switch_primary: bool,
|
pub dont_switch_primary: bool,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub monitor_index_override: Option<i32>,
|
pub monitor_index_override: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,6 +110,7 @@ pub enum NetworkType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Default, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Default, Debug)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct Network {
|
pub struct Network {
|
||||||
pub network_type: NetworkType,
|
pub network_type: NetworkType,
|
||||||
|
|
||||||
@ -127,11 +125,13 @@ pub struct Network {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Default, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Default, Debug)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct BepInEx {
|
pub struct BepInEx {
|
||||||
pub console: bool,
|
pub console: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct Wine {
|
pub struct Wine {
|
||||||
pub runtime: PathBuf,
|
pub runtime: PathBuf,
|
||||||
pub prefix: PathBuf,
|
pub prefix: PathBuf,
|
||||||
@ -155,20 +155,17 @@ pub enum Mu3Audio {
|
|||||||
Excl2Ch,
|
Excl2Ch,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct Mu3Ini {
|
pub struct Mu3Ini {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub audio: Option<Mu3Audio>,
|
pub audio: Option<Mu3Audio>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub blacklist: Option<(i32, i32)>,
|
pub blacklist: Option<(i32, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_true() -> bool { true }
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct OngekiKeyboard {
|
pub struct OngekiKeyboard {
|
||||||
#[serde(default = "default_true")] pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub use_mouse: bool,
|
pub use_mouse: bool,
|
||||||
pub coin: i32,
|
pub coin: i32,
|
||||||
pub svc: i32,
|
pub svc: i32,
|
||||||
@ -208,8 +205,9 @@ impl Default for OngekiKeyboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct ChunithmKeyboard {
|
pub struct ChunithmKeyboard {
|
||||||
#[serde(default = "default_true")] pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub coin: i32,
|
pub coin: i32,
|
||||||
pub svc: i32,
|
pub svc: i32,
|
||||||
pub test: i32,
|
pub test: i32,
|
||||||
|
@ -14,10 +14,10 @@ pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgK
|
|||||||
|
|
||||||
if redo_bepinex {
|
if redo_bepinex {
|
||||||
if pfx_dir.join("BepInEx").exists() {
|
if pfx_dir.join("BepInEx").exists() {
|
||||||
tokio::fs::remove_dir_all(pfx_dir.join("BepInEx")).await?;
|
util::remove_dir_all(pfx_dir.join("BepInEx")).await?;
|
||||||
}
|
}
|
||||||
if pfx_dir.join("lang").exists() {
|
if pfx_dir.join("lang").exists() {
|
||||||
tokio::fs::remove_dir_all(pfx_dir.join("lang")).await?;
|
util::remove_dir_all(pfx_dir.join("lang")).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::path::Path;
|
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::{AppHandle, Emitter};
|
use tauri::{AppHandle, Emitter};
|
||||||
@ -207,8 +206,9 @@ impl PackageStore {
|
|||||||
"{}-{}-{}.zip",
|
"{}-{}-{}.zip",
|
||||||
pkg.namespace, pkg.name, rmt.version
|
pkg.namespace, pkg.name, rmt.version
|
||||||
));
|
));
|
||||||
|
let part_path = zip_path.join(".part");
|
||||||
|
|
||||||
if !zip_path.exists() {
|
if !zip_path.exists() && !part_path.exists() {
|
||||||
self.dlh.download_zip(&zip_path, &pkg)?;
|
self.dlh.download_zip(&zip_path, &pkg)?;
|
||||||
log::debug!("deferring {}", key);
|
log::debug!("deferring {}", key);
|
||||||
return Ok(InstallResult::Deferred);
|
return Ok(InstallResult::Deferred);
|
||||||
@ -243,7 +243,7 @@ impl PackageStore {
|
|||||||
if path.exists() && path.join("manifest.json").exists() {
|
if path.exists() && path.join("manifest.json").exists() {
|
||||||
pkg.loc = None;
|
pkg.loc = None;
|
||||||
|
|
||||||
let rv = Self::clean_up_package(&path).await;
|
let rv = util::remove_dir_all(&path).await;
|
||||||
|
|
||||||
if rv.is_ok() {
|
if rv.is_ok() {
|
||||||
self.app.emit("install-end-prelude", Payload {
|
self.app.emit("install-end-prelude", Payload {
|
||||||
@ -269,48 +269,6 @@ impl PackageStore {
|
|||||||
self.store.insert(key, new);
|
self.store.insert(key, new);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn clean_up_dir(path: impl AsRef<Path>, name: &str) -> Result<()> {
|
|
||||||
let path = path.as_ref().join(name);
|
|
||||||
if path.exists() {
|
|
||||||
tokio::fs::remove_dir_all(path)
|
|
||||||
.await
|
|
||||||
.map_err(|e| anyhow!("could not delete {}: {}", name, e))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn clean_up_file(path: impl AsRef<Path>, name: &str, force: bool) -> Result<()> {
|
|
||||||
let path = path.as_ref().join(name);
|
|
||||||
if force || path.exists() {
|
|
||||||
tokio::fs::remove_file(path).await
|
|
||||||
.map_err(|e| anyhow!("Could not delete /{}: {}", name, e))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn clean_up_package(path: impl AsRef<Path>) -> Result<()> {
|
|
||||||
// todo case sensitivity for linux
|
|
||||||
Self::clean_up_dir(&path, "app").await?;
|
|
||||||
Self::clean_up_dir(&path, "option").await?;
|
|
||||||
Self::clean_up_dir(&path, "segatools").await?;
|
|
||||||
Self::clean_up_file(&path, "icon.png", true).await?;
|
|
||||||
Self::clean_up_file(&path, "manifest.json", true).await?;
|
|
||||||
Self::clean_up_file(&path, "README.md", true).await?;
|
|
||||||
Self::clean_up_file(&path, "post_load.ps1", false).await?;
|
|
||||||
// todo search for the proper dll
|
|
||||||
Self::clean_up_file(&path, "saekawa.dll", false).await?;
|
|
||||||
Self::clean_up_file(&path, "mempatcher32.dll", false).await?;
|
|
||||||
Self::clean_up_file(&path, "mempatcher64.dll", false).await?;
|
|
||||||
|
|
||||||
tokio::fs::remove_dir(path.as_ref())
|
|
||||||
.await
|
|
||||||
.map_err(|e| anyhow!("Could not delete {}: {}", path.as_ref().to_string_lossy(), e))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_deps(&self, rmt: Remote, set: &mut HashSet<PkgKey>) -> Result<()> {
|
fn resolve_deps(&self, rmt: Remote, set: &mut HashSet<PkgKey>) -> Result<()> {
|
||||||
for d in rmt.dependencies {
|
for d in rmt.dependencies {
|
||||||
set.insert(d.clone());
|
set.insert(d.clone());
|
||||||
|
@ -94,7 +94,7 @@ impl Profile {
|
|||||||
}
|
}
|
||||||
std::fs::write(&path, s)
|
std::fs::write(&path, s)
|
||||||
.map_err(|e| anyhow!("error when writing to {:?}: {}", path, e))?;
|
.map_err(|e| anyhow!("error when writing to {:?}: {}", path, e))?;
|
||||||
log::info!("profile written to {:?}", path);
|
log::info!("profile saved to {:?}", path);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -154,4 +154,23 @@ impl PathStr for PathBuf {
|
|||||||
|
|
||||||
pub fn bool_to_01(val: bool) -> &'static str {
|
pub fn bool_to_01(val: bool) -> &'static str {
|
||||||
return if val { "1" } else { "0" }
|
return if val { "1" } else { "0" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// rm -r with checks
|
||||||
|
pub async fn remove_dir_all(path: impl AsRef<Path>) -> Result<()> {
|
||||||
|
let canon = path.as_ref().canonicalize()?;
|
||||||
|
|
||||||
|
if canon.to_string_lossy().len() < 10 {
|
||||||
|
return Err(anyhow!("invalid remove_dir_all target: too short"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if canon.starts_with(data_dir().canonicalize()?)
|
||||||
|
|| canon.starts_with(config_dir().canonicalize()?)
|
||||||
|
|| canon.starts_with(cache_dir().canonicalize()?) {
|
||||||
|
tokio::fs::remove_dir_all(path).await
|
||||||
|
.map_err(|e| anyhow!("invalid remove_dir_all target: {:?}", e))?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("invalid remove_dir_all target: not in a data directory"))
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "STARTLINER",
|
"productName": "STARTLINER",
|
||||||
"version": "0.6.1",
|
"version": "0.7.0",
|
||||||
"identifier": "zip.patafour.startliner",
|
"identifier": "zip.patafour.startliner",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "bun run dev",
|
"beforeDevCommand": "bun run dev",
|
||||||
|
@ -13,6 +13,7 @@ import TabPanel from 'primevue/tabpanel';
|
|||||||
import TabPanels from 'primevue/tabpanels';
|
import TabPanels from 'primevue/tabpanels';
|
||||||
import Tabs from 'primevue/tabs';
|
import Tabs from 'primevue/tabs';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
|
import InfoPage from './InfoPage.vue';
|
||||||
import ModList from './ModList.vue';
|
import ModList from './ModList.vue';
|
||||||
import ModStore from './ModStore.vue';
|
import ModStore from './ModStore.vue';
|
||||||
import OptionList from './OptionList.vue';
|
import OptionList from './OptionList.vue';
|
||||||
@ -36,7 +37,8 @@ const client = useClientStore();
|
|||||||
|
|
||||||
pkg.setupListeners();
|
pkg.setupListeners();
|
||||||
|
|
||||||
const currentTab: Ref<string | number> = ref(3);
|
const currentTab: Ref<'users' | 'loc' | 'patches' | 'rmt' | 'cfg' | 'info'> =
|
||||||
|
ref('users');
|
||||||
const pkgSearchTerm = ref('');
|
const pkgSearchTerm = ref('');
|
||||||
|
|
||||||
const isProfileDisabled = computed(() => prf.current === null);
|
const isProfileDisabled = computed(() => prf.current === null);
|
||||||
@ -62,20 +64,11 @@ onMounted(async () => {
|
|||||||
await Promise.all([prf.reloadList(), prf.reload()]);
|
await Promise.all([prf.reloadList(), prf.reload()]);
|
||||||
|
|
||||||
if (prf.current !== null) {
|
if (prf.current !== null) {
|
||||||
currentTab.value = 0;
|
currentTab.value = 'loc';
|
||||||
await pkg.reloadAll();
|
await pkg.reloadAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch_promise.then(async () => {
|
await fetch_promise;
|
||||||
await invoke('install_package', {
|
|
||||||
key: 'segatools-mu3hook',
|
|
||||||
force: false,
|
|
||||||
});
|
|
||||||
await invoke('install_package', {
|
|
||||||
key: 'segatools-chusanhook',
|
|
||||||
force: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const errorVisible = ref(false);
|
const errorVisible = ref(false);
|
||||||
@ -149,42 +142,47 @@ listen<{ message: string; header: string }>('invoke-error', (event) => {
|
|||||||
:value="currentTab"
|
:value="currentTab"
|
||||||
v-on:update:value="
|
v-on:update:value="
|
||||||
(value) => {
|
(value) => {
|
||||||
currentTab = value;
|
currentTab = value as any;
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
class="h-screen"
|
class="h-screen"
|
||||||
>
|
>
|
||||||
<div class="fixed w-full flex z-100">
|
<div class="fixed w-full flex z-100">
|
||||||
<TabList class="grow" :show-navigators="false">
|
<TabList class="grow" :show-navigators="false">
|
||||||
<Tab :value="3"><div class="pi pi-users"></div></Tab>
|
<Tab value="users"><div class="pi pi-users"></div></Tab>
|
||||||
<Tab :disabled="isProfileDisabled" :value="0"
|
<Tab :disabled="isProfileDisabled" value="loc"
|
||||||
><div class="pi pi-box"></div
|
><div class="pi pi-box"></div
|
||||||
></Tab>
|
></Tab>
|
||||||
<Tab v-if="prf.current?.meta.game === 'chunithm'" :value="4"
|
<Tab
|
||||||
|
v-if="prf.current?.meta.game === 'chunithm'"
|
||||||
|
value="patches"
|
||||||
><div class="pi pi-ticket"></div
|
><div class="pi pi-ticket"></div
|
||||||
></Tab>
|
></Tab>
|
||||||
<Tab
|
<Tab
|
||||||
v-if="pkg.networkStatus === 'online'"
|
v-if="pkg.networkStatus === 'online'"
|
||||||
:disabled="isProfileDisabled"
|
:disabled="isProfileDisabled"
|
||||||
:value="1"
|
value="rmt"
|
||||||
><div class="pi pi-download"></div
|
><div class="pi pi-download"></div
|
||||||
></Tab>
|
></Tab>
|
||||||
<Tab :disabled="isProfileDisabled" :value="2"
|
<Tab :disabled="isProfileDisabled" value="cfg"
|
||||||
><div class="pi pi-cog"></div
|
><div class="pi pi-cog"></div
|
||||||
></Tab>
|
></Tab>
|
||||||
|
<Tab value="info"
|
||||||
|
><div class="pi pi-info-circle"></div
|
||||||
|
></Tab>
|
||||||
|
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
|
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<div
|
<div
|
||||||
class="flex"
|
class="flex"
|
||||||
v-if="[0, 1, 2].includes(currentTab as number)"
|
v-if="['loc', 'rmt', 'cfg'].includes(currentTab)"
|
||||||
>
|
>
|
||||||
<InputIcon class="self-center mr-2">
|
<InputIcon class="self-center mr-2">
|
||||||
<i class="pi pi-search" />
|
<i class="pi pi-search" />
|
||||||
</InputIcon>
|
</InputIcon>
|
||||||
<InputText
|
<InputText
|
||||||
v-if="currentTab === 2"
|
v-if="currentTab === 'cfg'"
|
||||||
style="min-width: 0; width: 25dvw"
|
style="min-width: 0; width: 25dvw"
|
||||||
class="self-center"
|
class="self-center"
|
||||||
size="small"
|
size="small"
|
||||||
@ -234,28 +232,19 @@ listen<{ message: string; header: string }>('invoke-error', (event) => {
|
|||||||
</TabList>
|
</TabList>
|
||||||
</div>
|
</div>
|
||||||
<TabPanels class="w-full grow mt-[3rem]">
|
<TabPanels class="w-full grow mt-[3rem]">
|
||||||
<TabPanel :value="0">
|
<TabPanel value="loc">
|
||||||
<ModList :search="pkgSearchTerm" />
|
<ModList :search="pkgSearchTerm" />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel :value="1">
|
<TabPanel value="rmt">
|
||||||
<ModStore :search="pkgSearchTerm" />
|
<ModStore :search="pkgSearchTerm" />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel :value="2">
|
<TabPanel value="cfg">
|
||||||
<OptionList />
|
<OptionList />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel :value="3">
|
<TabPanel value="users">
|
||||||
<ProfileList />
|
<ProfileList />
|
||||||
<br /><br /><br />
|
|
||||||
<footer>
|
|
||||||
<Button
|
|
||||||
icon="pi pi-discord"
|
|
||||||
as="a"
|
|
||||||
target="_blank"
|
|
||||||
href="https://discord.gg/jxvzHjjEmc"
|
|
||||||
/>
|
|
||||||
</footer>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel :value="4">
|
<TabPanel value="patches">
|
||||||
<PatchList
|
<PatchList
|
||||||
v-if="
|
v-if="
|
||||||
pkg.hasLocal('mempatcher-mempatcher') &&
|
pkg.hasLocal('mempatcher-mempatcher') &&
|
||||||
@ -268,8 +257,11 @@ listen<{ message: string; header: string }>('invoke-error', (event) => {
|
|||||||
and enabled.
|
and enabled.
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel value="info">
|
||||||
|
<InfoPage />
|
||||||
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
<div v-if="currentTab === 5 || currentTab === 3">
|
<div v-if="currentTab === 'users' || currentTab === 'info'">
|
||||||
<img
|
<img
|
||||||
v-if="prf.current?.meta.game === 'ongeki'"
|
v-if="prf.current?.meta.game === 'ongeki'"
|
||||||
src="/sticker-ongeki.svg"
|
src="/sticker-ongeki.svg"
|
||||||
|
56
src/components/InfoPage.vue
Normal file
56
src/components/InfoPage.vue
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import Button from 'primevue/button';
|
||||||
|
import ScrollPanel from 'primevue/scrollpanel';
|
||||||
|
import { invoke } from '../invoke';
|
||||||
|
import { VueMarkdownIt } from '@f3ve/vue-markdown-it';
|
||||||
|
|
||||||
|
const changelog = ref('');
|
||||||
|
|
||||||
|
invoke('get_changelog').then((s) => (changelog.value = s as string));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>About</h1>
|
||||||
|
STARTLINER is a simple launcher, configuration tool and mod manager for
|
||||||
|
O.N.G.E.K.I. and CHUNITHM.
|
||||||
|
<h1>Changelog</h1>
|
||||||
|
<ScrollPanel style="height: 200px">
|
||||||
|
<div class="changelog">
|
||||||
|
<vue-markdown-it
|
||||||
|
:source="changelog"
|
||||||
|
:options="{ typographer: true, breaks: true }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ScrollPanel>
|
||||||
|
<footer class="mt-10 flex gap-3">
|
||||||
|
<Button
|
||||||
|
icon="pi pi-discord"
|
||||||
|
as="a"
|
||||||
|
target="_blank"
|
||||||
|
href="https://discord.gg/jxvzHjjEmc"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-github"
|
||||||
|
as="a"
|
||||||
|
target="_blank"
|
||||||
|
href="https://gitea.tendokyu.moe/akanyan/STARTLINER"
|
||||||
|
/>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css">
|
||||||
|
h1 {
|
||||||
|
font-size: 1.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changelog h2 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
.changelog ul {
|
||||||
|
list-style-type: circle;
|
||||||
|
}
|
||||||
|
.changelog li {
|
||||||
|
margin-left: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -15,7 +15,8 @@ const props = defineProps({
|
|||||||
|
|
||||||
const pkgs = usePkgStore();
|
const pkgs = usePkgStore();
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
const empty = ref(true);
|
const groupCallIndex = ref(0);
|
||||||
|
const empty = ref(false);
|
||||||
const gameSublist: Ref<string[]> = ref([]);
|
const gameSublist: Ref<string[]> = ref([]);
|
||||||
|
|
||||||
invoke('get_game_packages', {
|
invoke('get_game_packages', {
|
||||||
@ -45,7 +46,10 @@ const group = computed(() => {
|
|||||||
({ namespace }) => namespace
|
({ namespace }) => namespace
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
empty.value = Object.keys(res).length === 0;
|
if (groupCallIndex.value > 0) {
|
||||||
|
empty.value = Object.keys(res).length === 0;
|
||||||
|
}
|
||||||
|
groupCallIndex.value += 1;
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -81,5 +85,5 @@ const missing = computed(() => {
|
|||||||
<Fieldset v-for="(namespace, key) in group" :legend="key.toString()">
|
<Fieldset v-for="(namespace, key) in group" :legend="key.toString()">
|
||||||
<ModListEntry v-for="p in namespace" :pkg="p" />
|
<ModListEntry v-for="p in namespace" :pkg="p" />
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
<div v-if="empty" class="text-3xl">∅</div>
|
<div v-if="empty === true" class="text-3xl fadein">∅</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import Button from 'primevue/button';
|
||||||
import ToggleSwitch from 'primevue/toggleswitch';
|
import ToggleSwitch from 'primevue/toggleswitch';
|
||||||
import InstallButton from './InstallButton.vue';
|
import InstallButton from './InstallButton.vue';
|
||||||
import LinkButton from './LinkButton.vue';
|
import LinkButton from './LinkButton.vue';
|
||||||
import ModTitlecard from './ModTitlecard.vue';
|
import ModTitlecard from './ModTitlecard.vue';
|
||||||
import UpdateButton from './UpdateButton.vue';
|
import UpdateButton from './UpdateButton.vue';
|
||||||
|
import { invoke } from '../invoke';
|
||||||
import { usePkgStore, usePrfStore } from '../stores';
|
import { usePkgStore, usePrfStore } from '../stores';
|
||||||
import { Feature, Package } from '../types';
|
import { Feature, Package } from '../types';
|
||||||
import { hasFeature } from '../util';
|
import { hasFeature } from '../util';
|
||||||
@ -38,7 +40,7 @@ const model = computed({
|
|||||||
v-model="model"
|
v-model="model"
|
||||||
/>
|
/>
|
||||||
<InstallButton :pkg="pkg" />
|
<InstallButton :pkg="pkg" />
|
||||||
<!-- <Button
|
<Button
|
||||||
rounded
|
rounded
|
||||||
icon="pi pi-folder"
|
icon="pi pi-folder"
|
||||||
severity="help"
|
severity="help"
|
||||||
@ -46,8 +48,10 @@ const model = computed({
|
|||||||
size="small"
|
size="small"
|
||||||
class="ml-2 shrink-0"
|
class="ml-2 shrink-0"
|
||||||
style="width: 2rem; height: 2rem"
|
style="width: 2rem; height: 2rem"
|
||||||
v-on:click="pkg?.loc && open(pkg.loc.path ?? '')"
|
v-on:click="
|
||||||
/> -->
|
pkg?.loc?.path && invoke('open_file', { path: pkg.loc.path })
|
||||||
|
"
|
||||||
|
/>
|
||||||
<LinkButton v-if="pkgs.networkStatus === 'online'" :pkg="pkg" />
|
<LinkButton v-if="pkgs.networkStatus === 'online'" :pkg="pkg" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Ref, ref } from 'vue';
|
import { Ref, computed, ref } from 'vue';
|
||||||
|
import Button from 'primevue/button';
|
||||||
import Divider from 'primevue/divider';
|
import Divider from 'primevue/divider';
|
||||||
import MultiSelect from 'primevue/multiselect';
|
import MultiSelect from 'primevue/multiselect';
|
||||||
import ToggleSwitch from 'primevue/toggleswitch';
|
import ToggleSwitch from 'primevue/toggleswitch';
|
||||||
@ -40,6 +41,39 @@ const list = () => {
|
|||||||
empty.value = res.length === 0;
|
empty.value = res.length === 0;
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const shouldShowRecommended = computed(() => {
|
||||||
|
if (prf.current!.meta.game === 'ongeki') {
|
||||||
|
return !pkgs.allLocal.some((p) => pkgKey(p) === 'segatools-mu3hook');
|
||||||
|
}
|
||||||
|
if (prf.current!.meta.game === 'chunithm') {
|
||||||
|
return (
|
||||||
|
!pkgs.allLocal.some((p) => pkgKey(p) === 'segatools-chusanhook') ||
|
||||||
|
!pkgs.allLocal.some((p) => pkgKey(p) === 'mempatcher-mempatcher')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getRecommendedTooltip = () => {
|
||||||
|
if (prf.current!.meta.game === 'ongeki') {
|
||||||
|
return 'segatools-mu3hook';
|
||||||
|
}
|
||||||
|
if (prf.current!.meta.game === 'chunithm') {
|
||||||
|
return 'segatools-chusanhook and mempatcher';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const installRecommended = () => {
|
||||||
|
if (prf.current!.meta.game === 'ongeki') {
|
||||||
|
pkgs.installFromKey('segatools-mu3hook');
|
||||||
|
}
|
||||||
|
if (prf.current!.meta.game === 'chunithm') {
|
||||||
|
pkgs.installFromKey('segatools-chusanhook');
|
||||||
|
pkgs.installFromKey('mempatcher-mempatcher');
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -78,6 +112,14 @@ const list = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<Button
|
||||||
|
v-if="shouldShowRecommended"
|
||||||
|
label="Install recommended packages"
|
||||||
|
v-tooltip="getRecommendedTooltip"
|
||||||
|
icon="pi pi-plus"
|
||||||
|
class="mb-3"
|
||||||
|
@click="installRecommended"
|
||||||
|
/>
|
||||||
<div v-for="p in list()" class="flex flex-row">
|
<div v-for="p in list()" class="flex flex-row">
|
||||||
<ModStoreEntry :pkg="p" />
|
<ModStoreEntry :pkg="p" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,7 +22,6 @@ const prf = usePrfStore();
|
|||||||
icon="pi pi-plus"
|
icon="pi pi-plus"
|
||||||
class="chunithm-button profile-button"
|
class="chunithm-button profile-button"
|
||||||
@click="() => prf.create('chunithm')"
|
@click="() => prf.create('chunithm')"
|
||||||
v-tooltip="'!!! Experimental !!!'"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-12 flex flex-col flex-wrap align-middle gap-4">
|
<div class="mt-12 flex flex-col flex-wrap align-middle gap-4">
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
import InputText from 'primevue/inputtext';
|
import InputText from 'primevue/inputtext';
|
||||||
|
import * as path from '@tauri-apps/api/path';
|
||||||
import { invoke } from '../invoke';
|
import { invoke } from '../invoke';
|
||||||
import { usePrfStore } from '../stores';
|
import { useGeneralStore, usePrfStore } from '../stores';
|
||||||
import { ProfileMeta } from '../types';
|
import { ProfileMeta } from '../types';
|
||||||
|
|
||||||
|
const general = useGeneralStore();
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
const isEditing = ref(false);
|
const isEditing = ref(false);
|
||||||
|
|
||||||
@ -110,7 +112,7 @@ const deleteProfile = async () => {
|
|||||||
style="width: 2rem; height: 2rem"
|
style="width: 2rem; height: 2rem"
|
||||||
@click="isEditing = true"
|
@click="isEditing = true"
|
||||||
/>
|
/>
|
||||||
<!-- <Button
|
<Button
|
||||||
rounded
|
rounded
|
||||||
icon="pi pi-folder"
|
icon="pi pi-folder"
|
||||||
severity="help"
|
severity="help"
|
||||||
@ -121,9 +123,13 @@ const deleteProfile = async () => {
|
|||||||
@click="
|
@click="
|
||||||
path
|
path
|
||||||
.join(general.dataDir, `profile-${p!.game}-${p!.name}`)
|
.join(general.dataDir, `profile-${p!.game}-${p!.name}`)
|
||||||
.then(open)
|
.then(async (path) => {
|
||||||
|
if (await invoke('file_exists', { path })) {
|
||||||
|
await invoke('open_file', { path });
|
||||||
|
}
|
||||||
|
})
|
||||||
"
|
"
|
||||||
/> -->
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ const startline = async (force: boolean, refresh: boolean) => {
|
|||||||
} else if ('MissingLocalPackage' in o) {
|
} else if ('MissingLocalPackage' in o) {
|
||||||
return `Package missing: ${o.MissingLocalPackage}`;
|
return `Package missing: ${o.MissingLocalPackage}`;
|
||||||
} else if ('MissingDependency' in o) {
|
} else if ('MissingDependency' in o) {
|
||||||
return `Dependency missing: ${o.MissingDependency}`;
|
return `Dependency missing: ${(o.MissingDependency as string[]).join(' ')}`;
|
||||||
} else if ('MissingTool' in o) {
|
} else if ('MissingTool' in o) {
|
||||||
return `Tool missing: ${o.MissingTool}`;
|
return `Tool missing: ${o.MissingTool}`;
|
||||||
} else {
|
} else {
|
||||||
|
@ -119,6 +119,7 @@ const checkSegatoolsIni = async (target: string) => {
|
|||||||
return { title: pkgKey(p), value: pkgKey(p) };
|
return { title: pkgKey(p), value: pkgKey(p) };
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
|
placeholder="none"
|
||||||
option-label="title"
|
option-label="title"
|
||||||
option-value="value"
|
option-value="value"
|
||||||
></Select>
|
></Select>
|
||||||
|
@ -25,6 +25,15 @@ const updatesModel = computed({
|
|||||||
await client.setAutoupdates(value);
|
await client.setAutoupdates(value);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const verboseModel = computed({
|
||||||
|
get() {
|
||||||
|
return client.verbose;
|
||||||
|
},
|
||||||
|
async set(value: boolean) {
|
||||||
|
await client.setVerbose(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -45,12 +54,18 @@ const updatesModel = computed({
|
|||||||
</OptionRow>
|
</OptionRow>
|
||||||
<OptionRow
|
<OptionRow
|
||||||
title="Offline mode"
|
title="Offline mode"
|
||||||
tooltip="Disables the package store. Requires a restart."
|
tooltip="Disables the package store. Applies after a restart."
|
||||||
>
|
>
|
||||||
<ToggleSwitch v-model="offlineModel" />
|
<ToggleSwitch v-model="offlineModel" />
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
<OptionRow title="Enable automatic updates">
|
<OptionRow title="Enable automatic updates">
|
||||||
<ToggleSwitch v-model="updatesModel" />
|
<ToggleSwitch v-model="updatesModel" />
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
|
<OptionRow
|
||||||
|
title="Enable detailed logs"
|
||||||
|
tooltip="Applies after a restart."
|
||||||
|
>
|
||||||
|
<ToggleSwitch v-model="verboseModel" />
|
||||||
|
</OptionRow>
|
||||||
</OptionCategory>
|
</OptionCategory>
|
||||||
</template>
|
</template>
|
||||||
|
@ -193,8 +193,17 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
pkg.js.busy = false;
|
pkg.js.busy = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
//if (rv === 'Deferred') { /* download progress */ }
|
async installFromKey(key: string) {
|
||||||
|
try {
|
||||||
|
await invoke('install_package', {
|
||||||
|
key,
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateAll() {
|
async updateAll() {
|
||||||
@ -346,6 +355,7 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
|
|
||||||
const offlineMode = ref(false);
|
const offlineMode = ref(false);
|
||||||
const enableAutoupdates = ref(true);
|
const enableAutoupdates = ref(true);
|
||||||
|
const verbose = ref(false);
|
||||||
|
|
||||||
const scaleValue = (value: ScaleType) =>
|
const scaleValue = (value: ScaleType) =>
|
||||||
value === 's' ? 1 : value === 'm' ? 1.25 : value === 'l' ? 1.5 : 2;
|
value === 's' ? 1 : value === 'm' ? 1.25 : value === 'l' ? 1.5 : 2;
|
||||||
@ -401,11 +411,15 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
offlineMode.value = await invoke('get_global_config', {
|
offlineMode.value = await invoke('get_global_config', {
|
||||||
field: 'OfflineMode',
|
field: 'offline_mode',
|
||||||
});
|
});
|
||||||
|
|
||||||
enableAutoupdates.value = await invoke('get_global_config', {
|
enableAutoupdates.value = await invoke('get_global_config', {
|
||||||
field: 'EnableAutoupdates',
|
field: 'enable_autoupdates',
|
||||||
|
});
|
||||||
|
|
||||||
|
verbose.value = await invoke('get_global_config', {
|
||||||
|
field: 'verbose',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -438,17 +452,22 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
|
|
||||||
const setOfflineMode = async (value: boolean) => {
|
const setOfflineMode = async (value: boolean) => {
|
||||||
offlineMode.value = value;
|
offlineMode.value = value;
|
||||||
await invoke('set_global_config', { field: 'OfflineMode', value });
|
await invoke('set_global_config', { field: 'offline_mode', value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const setAutoupdates = async (value: boolean) => {
|
const setAutoupdates = async (value: boolean) => {
|
||||||
enableAutoupdates.value = value;
|
enableAutoupdates.value = value;
|
||||||
await invoke('set_global_config', {
|
await invoke('set_global_config', {
|
||||||
field: 'EnableAutoupdates',
|
field: 'enable_autoupdates',
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setVerbose = async (value: boolean) => {
|
||||||
|
verbose.value = value;
|
||||||
|
await invoke('set_global_config', { field: 'verbose', value });
|
||||||
|
};
|
||||||
|
|
||||||
getCurrentWindow().onResized(async ({ payload }) => {
|
getCurrentWindow().onResized(async ({ payload }) => {
|
||||||
// For whatever reason this is 0 when minimized
|
// For whatever reason this is 0 when minimized
|
||||||
if (payload.width > 0) {
|
if (payload.width > 0) {
|
||||||
@ -460,6 +479,7 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
scaleFactor,
|
scaleFactor,
|
||||||
offlineMode,
|
offlineMode,
|
||||||
enableAutoupdates,
|
enableAutoupdates,
|
||||||
|
verbose,
|
||||||
timeout,
|
timeout,
|
||||||
scaleModel,
|
scaleModel,
|
||||||
load,
|
load,
|
||||||
@ -467,5 +487,6 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
queueSave,
|
queueSave,
|
||||||
setOfflineMode,
|
setOfflineMode,
|
||||||
setAutoupdates,
|
setAutoupdates,
|
||||||
|
setVerbose,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user