forked from akanyan/STARTLINER
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
7ea31c7a87 | |||
073ff8cfb8 | |||
d93118683d | |||
7f68b8d28b | |||
4247e19996 | |||
6270fce05f | |||
7db36b7bc0 | |||
f3016eb029 | |||
28269c5d75 | |||
1a68eda8c1 | |||
b9a40d44a8 | |||
b10c797d52 | |||
ff0a37dfdc | |||
d63d81e349 | |||
9ea66dbeab | |||
4e795257ad | |||
41fbef0260 | |||
93e0a7e313 | |||
ca871f069f | |||
8c3f9762a4 |
@ -1,6 +1,6 @@
|
||||
# STARTLINER
|
||||
|
||||
A simple and easy to use launcher, configuration tool and mod manager for [many games](https://silentblue.remywiki.com/ONGEKI:bright_MEMORY) (more to come) using [Rainycolor Watercolor](https://rainy.patafour.zip).
|
||||
A simple and easy to use launcher, configuration tool and mod manager for O.N.G.E.K.I. and CHUNITHM, using [Rainycolor Watercolor](https://rainy.patafour.zip).
|
||||
|
||||
Made with Rust (Tauri) and Vue. Technically multiplatform. Contributions welcome.
|
||||
|
||||
@ -16,14 +16,14 @@ Made with Rust (Tauri) and Vue. Technically multiplatform. Contributions welcome
|
||||
|
||||
## Usage
|
||||
|
||||
Download a prebuilt binary from [Modding Re:Fresh](https://discord.gg/jxvzHjjEmc) or build it yourself:
|
||||
Download a prebuilt binary from [Releases](https://gitea.tendokyu.moe/akanyan/STARTLINER/releases) or build it yourself:
|
||||
|
||||
```sh
|
||||
bun install
|
||||
bun run tauri build
|
||||
```
|
||||
|
||||
Create a profile, then click on things in the configuration tab (game path, `amfs` and network at the least). STARTLINER expects clean data with unpacked binaries. Anything else you can have in the game directory (segatools, BepInEx, etc.) can be present, but will not be used.
|
||||
Create a profile, then click on things in the configuration tab (game path, `amfs` and network at the least). STARTLINER expects clean data with unpacked binaries. Anything else you may have in the game directory (segatools, BepInEx, etc.) can be present, but will not be used.
|
||||
|
||||
Once a profile has been set up, it is possible to bypass the GUI:
|
||||
|
||||
|
246
bun.lock
246
bun.lock
@ -5,40 +5,42 @@
|
||||
"name": "startliner",
|
||||
"dependencies": {
|
||||
"@mdi/font": "7.4.47",
|
||||
"@primevue/forms": "^4.3.1",
|
||||
"@primevue/themes": "^4.3.1",
|
||||
"@tailwindcss/vite": "^4.0.9",
|
||||
"@tauri-apps/api": "^2.3.0",
|
||||
"@tauri-apps/plugin-deep-link": "~2.2.0",
|
||||
"@tauri-apps/plugin-dialog": "~2.2.0",
|
||||
"@tauri-apps/plugin-fs": "^2.2.0",
|
||||
"@primevue/forms": "^4.3.3",
|
||||
"@primevue/themes": "^4.3.3",
|
||||
"@tailwindcss/vite": "^4.1.2",
|
||||
"@tauri-apps/api": "^2.4.1",
|
||||
"@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.0",
|
||||
"@tauri-apps/plugin-shell": "~2.2.1",
|
||||
"@tauri-apps/plugin-updater": "^2.7.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"pinia": "^3.0.1",
|
||||
"primeicons": "^7.0.0",
|
||||
"primevue": "^4.3.1",
|
||||
"primevue": "^4.3.3",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"tailwindcss": "^4.0.9",
|
||||
"tailwindcss": "^4.1.2",
|
||||
"tailwindcss-primeui": "^0.4.0",
|
||||
"vue": "^3.5.13",
|
||||
"vuetify": "^3.7.14",
|
||||
"vuetify": "^3.8.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.3.1",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/node": "^22.13.9",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vue/eslint-config-typescript": "^14.4.0",
|
||||
"@tauri-apps/cli": "^2.4.1",
|
||||
"@tsconfig/node22": "^22.0.1",
|
||||
"@types/node": "^22.14.0",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vue/eslint-config-typescript": "^14.5.0",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"sass": "1.77.8",
|
||||
"sass-embedded": "^1.85.1",
|
||||
"sass-embedded": "^1.86.3",
|
||||
"typescript": "^5.8.2",
|
||||
"unplugin-fonts": "^1.3.1",
|
||||
"unplugin-vue-components": "^0.27.5",
|
||||
"vite": "^6.2.0",
|
||||
"vite-plugin-vuetify": "^2.1.0",
|
||||
"vite": "^6.2.5",
|
||||
"vite-plugin-vuetify": "^2.1.1",
|
||||
"vue-tsc": "^2.2.8",
|
||||
},
|
||||
},
|
||||
@ -166,13 +168,13 @@
|
||||
|
||||
"@primeuix/utils": ["@primeuix/utils@0.5.1", "", {}, "sha512-/bYirtF3gJOGrRQfQ5tUyQOLEria7wg/UCqvpIydTAxLmj/UWgWwh2kAjYVp49eldm1+2sk4+TDkbAz8XcPpew=="],
|
||||
|
||||
"@primevue/core": ["@primevue/core@4.3.1", "", { "dependencies": { "@primeuix/styled": "^0.5.0", "@primeuix/utils": "^0.5.1" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-Z2JfYk7I477qdlWHH5yiqUK0cwNe5joZJLwFtSEFkmBi/ocXvkGNAYk8XYNCz6UTDePUQSHKseKJxMkFHlfRtw=="],
|
||||
"@primevue/core": ["@primevue/core@4.3.3", "", { "dependencies": { "@primeuix/styled": "^0.5.0", "@primeuix/utils": "^0.5.1" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-kSkN5oourG7eueoFPIqiNX3oDT/f0I5IRK3uOY/ytz+VzTZp5yuaCN0Nt42ZQpVXjDxMxDvUhIdaXVrjr58NhQ=="],
|
||||
|
||||
"@primevue/forms": ["@primevue/forms@4.3.1", "", { "dependencies": { "@primeuix/forms": "^0.0.4", "@primeuix/utils": "^0.5.1", "@primevue/core": "4.3.1" } }, "sha512-2IWIrRCV82ey7fUb0dQhnJX8iqddcyZNajsEQpRMw1MO8NchN+vT26H2H1AYDI5uwwmv1FCqr+MiU9wk39Ub2g=="],
|
||||
"@primevue/forms": ["@primevue/forms@4.3.3", "", { "dependencies": { "@primeuix/forms": "^0.0.4", "@primeuix/utils": "^0.5.1", "@primevue/core": "4.3.3" } }, "sha512-GZYMd8wp+7/4DVMoGGUtRkAHw352peT3pgwgzaFYQqNIjxxGw9eI253XTxrppRCowrGJ2jEe80p9WfHi087B1g=="],
|
||||
|
||||
"@primevue/icons": ["@primevue/icons@4.3.1", "", { "dependencies": { "@primeuix/utils": "^0.5.1", "@primevue/core": "4.3.1" } }, "sha512-67GFk/NdbVDuPx4tlbO01BBWujLiZTJJJSce63dvLr7082YukPfrQq4Kru+y5Qmrfkq0uaP1I3+Ut9Skr6ATfQ=="],
|
||||
"@primevue/icons": ["@primevue/icons@4.3.3", "", { "dependencies": { "@primeuix/utils": "^0.5.1", "@primevue/core": "4.3.3" } }, "sha512-ouQaxHyeFB6MSfEGGbjaK5Qv9efS1xZGetZoU5jcPm090MSYLFtroP1CuK3lZZAQals06TZ6T6qcoNukSHpK5w=="],
|
||||
|
||||
"@primevue/themes": ["@primevue/themes@4.3.1", "", { "dependencies": { "@primeuix/styled": "^0.5.0", "@primeuix/themes": "^1.0.0" } }, "sha512-aC7V5BkMoMxEMoq7hIf+PNEaY/AS5EAgVg96Gf3bvV1JqZ36hSQ2CV1SfuNsWeEvIPF/u4L5qvjwC/LGjT4qgw=="],
|
||||
"@primevue/themes": ["@primevue/themes@4.3.3", "", { "dependencies": { "@primeuix/styled": "^0.5.0", "@primeuix/themes": "^1.0.0" } }, "sha512-LiYlSXsHeA8DFm8+yGyiDFQc3SEQwHcESTN1/rV+rrZ+UPuPisHY9fNIGRFQKA5XUQPDTQDQjtwYGx25Jikwhg=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
@ -214,95 +216,101 @@
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.6", "", { "os": "win32", "cpu": "x64" }, "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.0.9", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "tailwindcss": "4.0.9" } }, "sha512-tOJvdI7XfJbARYhxX+0RArAhmuDcczTC46DGCEziqxzzbIaPnfYaIyRT31n4u8lROrsO7Q6u/K9bmQHL2uL1bQ=="],
|
||||
"@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=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.9", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.9", "@tailwindcss/oxide-darwin-arm64": "4.0.9", "@tailwindcss/oxide-darwin-x64": "4.0.9", "@tailwindcss/oxide-freebsd-x64": "4.0.9", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.9", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.9", "@tailwindcss/oxide-linux-arm64-musl": "4.0.9", "@tailwindcss/oxide-linux-x64-gnu": "4.0.9", "@tailwindcss/oxide-linux-x64-musl": "4.0.9", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.9", "@tailwindcss/oxide-win32-x64-msvc": "4.0.9" } }, "sha512-eLizHmXFqHswJONwfqi/WZjtmWZpIalpvMlNhTM99/bkHtUs6IqgI1XQ0/W5eO2HiRQcIlXUogI2ycvKhVLNcA=="],
|
||||
"@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/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.0.9", "", { "os": "android", "cpu": "arm64" }, "sha512-YBgy6+2flE/8dbtrdotVInhMVIxnHJPbAwa7U1gX4l2ThUIaPUp18LjB9wEH8wAGMBZUb//SzLtdXXNBHPUl6Q=="],
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.3", "", { "os": "android", "cpu": "arm64" }, "sha512-cxklKjtNLwFl3mDYw4XpEfBY+G8ssSg9ADL4Wm6//5woi3XGqlxFsnV5Zb6v07dxw1NvEX2uoqsxO/zWQsgR+g=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.0.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-pWdl4J2dIHXALgy2jVkwKBmtEb73kqIfMpYmcgESr7oPQ+lbcQ4+tlPeVXaSAmang+vglAfFpXQCOvs/aGSqlw=="],
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mqkf2tLR5VCrjBvuRDwzKNShRu99gCAVMkVsaEOFvv6cCjlEKXRecPu9DEnxp6STk5z+Vlbh1M5zY3nQCXMXhw=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.0.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-4Dq3lKp0/C7vrRSkNPtBGVebEyWt9QPPlQctxJ0H3MDyiQYvzVYf8jKow7h5QkWNe8hbatEqljMj/Y0M+ERYJg=="],
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-7sGraGaWzXvCLyxrc7d+CCpUN3fYnkkcso3rCzwUmo/LteAl2ZGCDlGvDD8Y/1D3ngxT8KgDj1DSwOnNewKhmg=="],
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.0.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-k7U1RwRODta8x0uealtVt3RoWAWqA+D5FAOsvVGpYoI6ObgmnzqWW6pnVwz70tL8UZ/QXjeMyiICXyjzB6OGtQ=="],
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-E2+PbcbzIReaAYZe997wb9rId246yDkCwAakllAWSGqe6VTg9hHle67hfH6ExjpV2LSK/siRzBUs5wVff3RW9w=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.0.9", "", { "os": "linux", "cpu": "arm" }, "sha512-NDDjVweHz2zo4j+oS8y3KwKL5wGCZoXGA9ruJM982uVJLdsF8/1AeKvUwKRlMBpxHt1EdWJSAh8a0Mfhl28GlQ=="],
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.3", "", { "os": "linux", "cpu": "arm" }, "sha512-GvfbJ8wjSSjbLFFE3UYz4Eh8i4L6GiEYqCtA8j2Zd2oXriPuom/Ah/64pg/szWycQpzRnbDiJozoxFU2oJZyfg=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.0.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-jk90UZ0jzJl3Dy1BhuFfRZ2KP9wVKMXPjmCtY4U6fF2LvrjP5gWFJj5VHzfzHonJexjrGe1lMzgtjriuZkxagg=="],
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-35UkuCWQTeG9BHcBQXndDOrpsnt3Pj9NVIB4CgNiKmpG8GnCNXeMczkUpOoqcOhO6Cc/mM2W7kaQ/MTEENDDXg=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.0.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-3eMjyTC6HBxh9nRgOHzrc96PYh1/jWOwHZ3Kk0JN0Kl25BJ80Lj9HEvvwVDNTgPg154LdICwuFLuhfgH9DULmg=="],
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-dm18aQiML5QCj9DQo7wMbt1Z2tl3Giht54uVR87a84X8qRtuXxUqnKQkRDK5B4bCOmcZ580lF9YcoMkbDYTXHQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.0.9", "", { "os": "linux", "cpu": "x64" }, "sha512-v0D8WqI/c3WpWH1kq/HP0J899ATLdGZmENa2/emmNjubT0sWtEke9W9+wXeEoACuGAhF9i3PO5MeyditpDCiWQ=="],
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-LMdTmGe/NPtGOaOfV2HuO7w07jI3cflPrVq5CXl+2O93DCewADK0uW1ORNAcfu2YxDUS035eY2W38TxrsqngxA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.0.9", "", { "os": "linux", "cpu": "x64" }, "sha512-Kvp0TCkfeXyeehqLJr7otsc4hd/BUPfcIGrQiwsTVCfaMfjQZCG7DjI+9/QqPZha8YapLA9UoIcUILRYO7NE1Q=="],
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-aalNWwIi54bbFEizwl1/XpmdDrOaCjRFQRgtbv9slWjmNPuJJTIKPHf5/XXDARc9CneW9FkSTqTbyvNecYAEGw=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.0.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-m3+60T/7YvWekajNq/eexjhV8z10rswcz4BC9bioJ7YaN+7K8W2AmLmG0B79H14m6UHE571qB0XsPus4n0QVgQ=="],
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-PEj7XR4OGTGoboTIAdXicKuWl4EQIjKHKuR+bFy9oYN7CFZo0eu74+70O4XuERX4yjqVZGAkCdglBODlgqcCXg=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.0.9", "", { "os": "win32", "cpu": "x64" }, "sha512-dpc05mSlqkwVNOUjGu/ZXd5U1XNch1kHFJ4/cHkZFvaW1RzbHmRt24gvM8/HC6IirMxNarzVw4IXVtvrOoZtxA=="],
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.3", "", { "os": "win32", "cpu": "x64" }, "sha512-T8gfxECWDBENotpw3HR9SmNiHC9AOJdxs+woasRZ8Q/J4VHN0OMs7F+4yVNZ9EVN26Wv6mZbK0jv7eHYuLJLwA=="],
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.0.9", "", { "dependencies": { "@tailwindcss/node": "4.0.9", "@tailwindcss/oxide": "4.0.9", "lightningcss": "^1.29.1", "tailwindcss": "4.0.9" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-BIKJO+hwdIsN7V6I7SziMZIVHWWMsV/uCQKYEbeiGRDRld+TkqyRRl9+dQ0MCXbhcVr+D9T/qX2E84kT7V281g=="],
|
||||
"@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=="],
|
||||
|
||||
"@tauri-apps/api": ["@tauri-apps/api@2.3.0", "", {}, "sha512-33Z+0lX2wgZbx1SPFfqvzI6su63hCBkbzv+5NexeYjIx7WA9htdOKoRR7Dh3dJyltqS5/J8vQFyybiRoaL0hlA=="],
|
||||
"@tauri-apps/api": ["@tauri-apps/api@2.4.1", "", {}, "sha512-5sYwZCSJb6PBGbBL4kt7CnE5HHbBqwH+ovmOW6ZVju3nX4E3JX6tt2kRklFEH7xMOIwR0btRkZktuLhKvyEQYg=="],
|
||||
|
||||
"@tauri-apps/cli": ["@tauri-apps/cli@2.3.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.3.1", "@tauri-apps/cli-darwin-x64": "2.3.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.3.1", "@tauri-apps/cli-linux-arm64-gnu": "2.3.1", "@tauri-apps/cli-linux-arm64-musl": "2.3.1", "@tauri-apps/cli-linux-x64-gnu": "2.3.1", "@tauri-apps/cli-linux-x64-musl": "2.3.1", "@tauri-apps/cli-win32-arm64-msvc": "2.3.1", "@tauri-apps/cli-win32-ia32-msvc": "2.3.1", "@tauri-apps/cli-win32-x64-msvc": "2.3.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-xewcw/ZsCqgilTy2h7+pp2Baxoy7zLR2wXOV7SZLzkb6SshHVbm1BFAjn8iFATURRW85KLzl6wSGJ2dQHjVHqw=="],
|
||||
"@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=="],
|
||||
|
||||
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.3.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TOhSdsXYt+f+asRU+Dl+Wufglj/7+CX9h8RO4hl5k7D6lR4L8yTtdhpS7btaclOMmjYC4piNfJE70GoxhOoYWw=="],
|
||||
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.4.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QME7s8XQwy3LWClTVlIlwXVSLKkeJ/z88pr917Mtn9spYOjnBfsgHAgGdmpWD3NfJxjg7CtLbhH49DxoFL+hLg=="],
|
||||
|
||||
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.3.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-LDwGg3AuBQ3aCeMAFaFwt0MSGOVFoXuXEe0z4QxQ7jZE5tdAOhKABaq4i569V5lShCgQZ6nLD/tmA5+GipvHnA=="],
|
||||
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.4.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/r89IcW6Ya1sEsFUEH7wLNruDTj7WmDWKGpPy7gATFtQr5JEY4heernqE82isjTUimnHZD8SCr0jA3NceI4ybw=="],
|
||||
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.3.1", "", { "os": "linux", "cpu": "arm" }, "sha512-hu3HpbbtJBvHXw5i54QHwLxOUoXWqhf7CL2YYSPOrWEEQo10NKddulP61L5gfr5z+bSSaitfLwqgTidgnaNJCA=="],
|
||||
"@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-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mEGgwkiGSKYXWHhGodo7zU9PCd2I/d6KkR+Wp1nzK+DxsCrEK6yJ5XxYLSQSDcKkM4dCxpVEPUiVMbDhmn08jg=="],
|
||||
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.4.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-pnFGDEXBAzS4iDYAVxTRhAzNu3K2XPGflYyBc0czfHDBXopqRgMyj5Q9Wj7HAwv6cM8BqzXINxnb2ZJFGmbSgA=="],
|
||||
|
||||
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-tqQkafikGfnc7ISnGjSYkbpnzJKEyO8XSa0YOXTAL3J8R5Pss5ZIZY7G8kq1mwQSR/dPVR1ZLTVXgZGuysjP8w=="],
|
||||
"@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-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-I3puDJ2wGEauXlXbzIHn2etz78TaWs1cpN6zre02maHr6ZR7nf7euTCOGPhhfoMG0opA5mT/eLuYpVw648/VAA=="],
|
||||
"@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-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rbWiCOBuQN7tPySkUyBs914uUikE3mEUOqV/IFospvKESw4UC3G1DL5+ybfXH7Orb8/in3JpJuVzYQjo+OSbBA=="],
|
||||
"@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-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.3.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-PdTmUzSeTHjJuBpCV7L+V29fPhPtToU+NZU46slHKSA1aT38MiFDXBZ/6P5Zudrt9QPMfIubqnJKbK8Ivvv7Ww=="],
|
||||
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.4.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a8exvA5Ub9eg66a6hsMQKJIkf63QAf9OdiuFKOsEnKZkNN2x0NLgfvEcqdw88VY0UMs9dBoZ1AGbWMeYnLrLwQ=="],
|
||||
|
||||
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.3.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-K/Xa97kspWT4UWj3t26lL2D3QsopTAxS7kWi5kObdqtAGn3qD52qBi24FH38TdvHYz4QlnLIb30TukviCgh4gw=="],
|
||||
"@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-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.3.1", "", { "os": "win32", "cpu": "x64" }, "sha512-RgwzXbP8gAno3kQEsybMtgLp6D1Z1Nec2cftryYbPTJmoMJs6e4qgtxuTSbUz5SKnHe8rGgMiFSvEGoHvbG72Q=="],
|
||||
"@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/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.2.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-H6mkxr2KZ3XJcKL44tiq6cOjCw9DL8OgU1xjn3j26Qsn+H/roPFiyhR7CHuB8Ar+sQFj4YVlfmJwtBajK2FETQ=="],
|
||||
"@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/plugin-dialog": ["@tauri-apps/plugin-dialog@2.2.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-6bLkYK68zyK31418AK5fNccCdVuRnNpbxquCl8IqgFByOgWFivbiIlvb79wpSXi0O+8k8RCSsIpOquebusRVSg=="],
|
||||
"@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=="],
|
||||
|
||||
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.2.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-+08mApuONKI8/sCNEZ6AR8vf5vI9DXD4YfrQ9NQmhRxYKMLVhRW164vdW5BSLmMpuevftpQ2FVoL9EFkfG9Z+g=="],
|
||||
"@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.2.1", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-8skZ6qIH/kWaV8d6jj3aPvvkIOuqkVk0APRDey9n9N3Ueu3n4MIbuxpAKR2EdoAyQxnXxPTNVyjw2D35/vfGyg=="],
|
||||
|
||||
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.2.1", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-wZmCouo4PgTosh/UoejPw9DPs6RllS5Pp3fuOV2JobCu36mR5AXU2MzU9NZiVaFi/5Zfc8RN0IhcZHnksJ1o8A=="],
|
||||
|
||||
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.2.1", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-KdGzvvA4Eg0Dhw55MwczFbjxLxsTx0FvwwC/0StXlr6IxwPUxh5ziZQoaugkBFs8t+wfebdQrjBEzd8NmmDXNw=="],
|
||||
|
||||
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.2.6", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-bSdkuP71ZQRepPOn8BOEdBKYJQvl6+jb160QtJX/i2H9BF6ZySY/kYljh76N2Ne5fJMQRge7rlKoStYQY5Jq1w=="],
|
||||
|
||||
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.2.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-iC3Ic1hLmasoboG7BO+7p+AriSoqAwKrIk+Hpk+S/bjTQdXqbl2GbdclghI4gM32X0bls7xHzIFqhRdrlvJeaA=="],
|
||||
"@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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@tsconfig/node22": ["@tsconfig/node22@22.0.0", "", {}, "sha512-twLQ77zevtxobBOD4ToAtVmuYrpeYUh3qh+TEp+08IWhpsrIflVHqQ1F1CiPxQGL7doCdBIOOCF+1Tm833faNg=="],
|
||||
"@tsconfig/node22": ["@tsconfig/node22@22.0.1", "", {}, "sha512-VkgOa3n6jvs1p+r3DiwBqeEwGAwEvnVCg/hIjiANl5IEcqP3G0u5m8cBJspe1t9qjZRlZ7WFgqq5bJrGdgAKMg=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
"@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.24.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.24.0", "@typescript-eslint/type-utils": "8.24.0", "@typescript-eslint/utils": "8.24.0", "@typescript-eslint/visitor-keys": "8.24.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.8.0" } }, "sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ=="],
|
||||
"@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/parser": ["@typescript-eslint/parser@8.24.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.24.0", "@typescript-eslint/types": "8.24.0", "@typescript-eslint/typescript-estree": "8.24.0", "@typescript-eslint/visitor-keys": "8.24.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA=="],
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.29.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.29.0", "@typescript-eslint/types": "8.29.0", "@typescript-eslint/typescript-estree": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.24.0", "", { "dependencies": { "@typescript-eslint/types": "8.24.0", "@typescript-eslint/visitor-keys": "8.24.0" } }, "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw=="],
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.29.0", "", { "dependencies": { "@typescript-eslint/types": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0" } }, "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.24.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.24.0", "@typescript-eslint/utils": "8.24.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA=="],
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.29.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.29.0", "@typescript-eslint/utils": "8.29.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.24.0", "", {}, "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw=="],
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.29.0", "", {}, "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.24.0", "", { "dependencies": { "@typescript-eslint/types": "8.24.0", "@typescript-eslint/visitor-keys": "8.24.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ=="],
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.29.0", "", { "dependencies": { "@typescript-eslint/types": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.24.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.24.0", "@typescript-eslint/types": "8.24.0", "@typescript-eslint/typescript-estree": "8.24.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ=="],
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.29.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.29.0", "@typescript-eslint/types": "8.29.0", "@typescript-eslint/typescript-estree": "8.29.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.24.0", "", { "dependencies": { "@typescript-eslint/types": "8.24.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg=="],
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.29.0", "", { "dependencies": { "@typescript-eslint/types": "8.29.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg=="],
|
||||
|
||||
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@5.2.1", "", { "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ=="],
|
||||
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@5.2.3", "", { "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg=="],
|
||||
|
||||
"@volar/language-core": ["@volar/language-core@2.4.11", "", { "dependencies": { "@volar/source-map": "2.4.11" } }, "sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg=="],
|
||||
|
||||
@ -326,7 +334,7 @@
|
||||
|
||||
"@vue/devtools-shared": ["@vue/devtools-shared@7.7.2", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA=="],
|
||||
|
||||
"@vue/eslint-config-typescript": ["@vue/eslint-config-typescript@14.4.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.23.0", "fast-glob": "^3.3.3", "typescript-eslint": "^8.23.0", "vue-eslint-parser": "^9.4.3" }, "peerDependencies": { "eslint": "^9.10.0", "eslint-plugin-vue": "^9.28.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-daU+eAekEeVz3CReE4PRW25fe+OJDKwE28jHN6LimDEnuFMbJ6H4WGogEpNof276wVP6UvzOeJQfLFjB5mW29A=="],
|
||||
"@vue/eslint-config-typescript": ["@vue/eslint-config-typescript@14.5.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.26.0", "fast-glob": "^3.3.3", "typescript-eslint": "^8.26.0", "vue-eslint-parser": "^10.1.1" }, "peerDependencies": { "eslint": "^9.10.0", "eslint-plugin-vue": "^9.28.0 || ^10.0.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-5oPOyuwkw++AP5gHDh5YFmST50dPfWOcm3/W7Nbh42IK5O3H74ytWAw0TrCRTaBoD/02khnWXuZf1Bz1xflavQ=="],
|
||||
|
||||
"@vue/language-core": ["@vue/language-core@2.2.8", "", { "dependencies": { "@volar/language-core": "~2.4.11", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", "alien-signals": "^1.0.3", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-rrzB0wPGBvcwaSNRriVWdNAbHQWSf0NlGqgKHK5mEkXpefjUlVRP62u03KvwZpvKVjRnBIQ/Lwre+Mx9N6juUQ=="],
|
||||
|
||||
@ -402,7 +410,7 @@
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
|
||||
"detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
|
||||
|
||||
@ -510,27 +518,27 @@
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.29.1", "", { "dependencies": { "detect-libc": "^1.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.1", "lightningcss-darwin-x64": "1.29.1", "lightningcss-freebsd-x64": "1.29.1", "lightningcss-linux-arm-gnueabihf": "1.29.1", "lightningcss-linux-arm64-gnu": "1.29.1", "lightningcss-linux-arm64-musl": "1.29.1", "lightningcss-linux-x64-gnu": "1.29.1", "lightningcss-linux-x64-musl": "1.29.1", "lightningcss-win32-arm64-msvc": "1.29.1", "lightningcss-win32-x64-msvc": "1.29.1" } }, "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q=="],
|
||||
"lightningcss": ["lightningcss@1.29.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.2", "lightningcss-darwin-x64": "1.29.2", "lightningcss-freebsd-x64": "1.29.2", "lightningcss-linux-arm-gnueabihf": "1.29.2", "lightningcss-linux-arm64-gnu": "1.29.2", "lightningcss-linux-arm64-musl": "1.29.2", "lightningcss-linux-x64-gnu": "1.29.2", "lightningcss-linux-x64-musl": "1.29.2", "lightningcss-win32-arm64-msvc": "1.29.2", "lightningcss-win32-x64-msvc": "1.29.2" } }, "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.29.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw=="],
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.29.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.29.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA=="],
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.29.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.29.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ=="],
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.29.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.29.1", "", { "os": "linux", "cpu": "arm" }, "sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg=="],
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.29.2", "", { "os": "linux", "cpu": "arm" }, "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.29.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ=="],
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.29.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw=="],
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.29.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw=="],
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.29.1", "", { "os": "linux", "cpu": "x64" }, "sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw=="],
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.29.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog=="],
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.29.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.1", "", { "os": "win32", "cpu": "x64" }, "sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q=="],
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="],
|
||||
|
||||
"local-pkg": ["local-pkg@0.5.1", "", { "dependencies": { "mlly": "^1.7.3", "pkg-types": "^1.2.1" } }, "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ=="],
|
||||
|
||||
@ -594,7 +602,7 @@
|
||||
|
||||
"pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="],
|
||||
|
||||
"pinia": ["pinia@3.0.1", "", { "dependencies": { "@vue/devtools-api": "^7.7.2" }, "peerDependencies": { "typescript": ">=4.4.4", "vue": "^2.7.0 || ^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-WXglsDzztOTH6IfcJ99ltYZin2mY8XZCXujkYWVIJlBjqsP6ST7zw+Aarh63E1cDVYeyUcPCxPHzJpEOmzB6Wg=="],
|
||||
"pinia": ["pinia@3.0.2", "", { "dependencies": { "@vue/devtools-api": "^7.7.2" }, "peerDependencies": { "typescript": ">=4.4.4", "vue": "^2.7.0 || ^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g=="],
|
||||
|
||||
"pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
||||
|
||||
@ -608,7 +616,7 @@
|
||||
|
||||
"primeicons": ["primeicons@7.0.0", "", {}, "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw=="],
|
||||
|
||||
"primevue": ["primevue@4.3.1", "", { "dependencies": { "@primeuix/styled": "^0.5.0", "@primeuix/styles": "^1.0.0", "@primeuix/utils": "^0.5.1", "@primevue/core": "4.3.1", "@primevue/icons": "4.3.1" } }, "sha512-NSUpcWf2WpXgqOvjgXu5zQM3E5UEXoA2iXLi6xV+h1SBZ1TmgNfrjme96KRzfUY2RBsI0rTSUuPv0I+fXvtcmA=="],
|
||||
"primevue": ["primevue@4.3.3", "", { "dependencies": { "@primeuix/styled": "^0.5.0", "@primeuix/styles": "^1.0.0", "@primeuix/utils": "^0.5.1", "@primevue/core": "4.3.3", "@primevue/icons": "4.3.3" } }, "sha512-nooYVoEz5CdP3EhUkD6c3qTdRmpLHZh75fBynkUkl46K8y5rksHTjdSISiDijwTA5STQIOkyqLb+RM+HQ6nC1Q=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
@ -634,47 +642,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.85.1", "", { "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.85.1", "sass-embedded-android-arm64": "1.85.1", "sass-embedded-android-ia32": "1.85.1", "sass-embedded-android-riscv64": "1.85.1", "sass-embedded-android-x64": "1.85.1", "sass-embedded-darwin-arm64": "1.85.1", "sass-embedded-darwin-x64": "1.85.1", "sass-embedded-linux-arm": "1.85.1", "sass-embedded-linux-arm64": "1.85.1", "sass-embedded-linux-ia32": "1.85.1", "sass-embedded-linux-musl-arm": "1.85.1", "sass-embedded-linux-musl-arm64": "1.85.1", "sass-embedded-linux-musl-ia32": "1.85.1", "sass-embedded-linux-musl-riscv64": "1.85.1", "sass-embedded-linux-musl-x64": "1.85.1", "sass-embedded-linux-riscv64": "1.85.1", "sass-embedded-linux-x64": "1.85.1", "sass-embedded-win32-arm64": "1.85.1", "sass-embedded-win32-ia32": "1.85.1", "sass-embedded-win32-x64": "1.85.1" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-0i+3h2Df/c71afluxC1SXqyyMmJlnKWfu9ZGdzwuKRM1OftEa2XM2myt5tR36CF3PanYrMjFKtRIj8PfSf838w=="],
|
||||
"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-android-arm": ["sass-embedded-android-arm@1.85.1", "", { "os": "android", "cpu": "arm" }, "sha512-GkcgUGMZtEF9gheuE1dxCU0ZSAifuaFXi/aX7ZXvjtdwmTl9Zc/OHR9oiUJkc8IW9UI7H8TuwlTAA8+SwgwIeQ=="],
|
||||
"sass-embedded-android-arm": ["sass-embedded-android-arm@1.86.3", "", { "os": "android", "cpu": "arm" }, "sha512-UyeXrFzZSvrGbvrWUBcspbsbivGgAgebLGJdSqJulgSyGbA6no3DWQ5Qpdd6+OAUC39BlpPu74Wx9s4RrVuaFw=="],
|
||||
|
||||
"sass-embedded-android-arm64": ["sass-embedded-android-arm64@1.85.1", "", { "os": "android", "cpu": "arm64" }, "sha512-27oRheqNA3SJM2hAxpVbs7mCKUwKPWmEEhyiNFpBINb5ELVLg+Ck5RsGg+SJmo130ul5YX0vinmVB5uPWc8X5w=="],
|
||||
"sass-embedded-android-arm64": ["sass-embedded-android-arm64@1.86.3", "", { "os": "android", "cpu": "arm64" }, "sha512-q+XwFp6WgAv+UgnQhsB8KQ95kppvWAB7DSoJp+8Vino8b9ND+1ai3cUUZPE5u4SnLZrgo5NtrbPvN5KLc4Pfyg=="],
|
||||
|
||||
"sass-embedded-android-ia32": ["sass-embedded-android-ia32@1.85.1", "", { "os": "android", "cpu": "ia32" }, "sha512-f3x16NyRgtXFksIaO/xXKrUhttUBv8V0XsAR2Dhdb/yz4yrDrhzw9Wh8fmw7PlQqECcQvFaoDr3XIIM6lKzasw=="],
|
||||
"sass-embedded-android-ia32": ["sass-embedded-android-ia32@1.86.3", "", { "os": "android", "cpu": "ia32" }, "sha512-gTJjVh2cRzvGujXj5ApPk/owUTL5SiO7rDtNLrzYAzi1N5HRuLYXqk3h1IQY3+eCOBjGl7mQ9XyySbJs/3hDvg=="],
|
||||
|
||||
"sass-embedded-android-riscv64": ["sass-embedded-android-riscv64@1.85.1", "", { "os": "android", "cpu": "none" }, "sha512-IP6OijpJ8Mqo7XqCe0LsuZVbAxEFVboa0kXqqR5K55LebEplsTIA2GnmRyMay3Yr/2FVGsZbCb6Wlgkw23eCiA=="],
|
||||
"sass-embedded-android-riscv64": ["sass-embedded-android-riscv64@1.86.3", "", { "os": "android", "cpu": "none" }, "sha512-Po3JnyiCS16kd6REo1IMUbFGYtvL9O0rmKaXx5vOuBaJD1LPy2LiSSp7TU7wkJ9IxsTDGzFaSeP1I9qb6D8VVg=="],
|
||||
|
||||
"sass-embedded-android-x64": ["sass-embedded-android-x64@1.85.1", "", { "os": "android", "cpu": "x64" }, "sha512-Mh7CA53wR3ADvXAYipFc/R3vV4PVOzoKwWzPxmq+7i8UZrtsVjKONxGtqWe9JG1mna0C9CRZAx0sv/BzbOJxWg=="],
|
||||
"sass-embedded-android-x64": ["sass-embedded-android-x64@1.86.3", "", { "os": "android", "cpu": "x64" }, "sha512-+7h3jdDv/0kUFx0BvxYlq2fa7CcHiDPlta6k5OxO5K6jyqJwo9hc0Z052BoYEauWTqZ+vK6bB5rv2BIzq4U9nA=="],
|
||||
|
||||
"sass-embedded-darwin-arm64": ["sass-embedded-darwin-arm64@1.85.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-msWxzhvcP9hqGVegxVePVEfv9mVNTlUgGr6k7O7Ihji702mbtrH/lKwF4aRkkt4g1j7tv10+JtQXmTNi/pi9kA=="],
|
||||
"sass-embedded-darwin-arm64": ["sass-embedded-darwin-arm64@1.86.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EgLwV4ORm5Hr0DmIXo0Xw/vlzwLnfAiqD2jDXIglkBsc5czJmo4/IBdGXOP65TRnsgJEqvbU3aQhuawX5++x9A=="],
|
||||
|
||||
"sass-embedded-darwin-x64": ["sass-embedded-darwin-x64@1.85.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-J4UFHUiyI9Z+mwYMwz11Ky9TYr3hY1fCxeQddjNGL/+ovldtb0yAIHvoVM0BGprQDm5JqhtUk8KyJ3RMJqpaAA=="],
|
||||
"sass-embedded-darwin-x64": ["sass-embedded-darwin-x64@1.86.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-dfKhfrGPRNLWLC82vy/vQGmNKmAiKWpdFuWiePRtg/E95pqw+sCu6080Y6oQLfFu37Iq3MpnXiSpDuSo7UnPWA=="],
|
||||
|
||||
"sass-embedded-linux-arm": ["sass-embedded-linux-arm@1.85.1", "", { "os": "linux", "cpu": "arm" }, "sha512-X0fDh95nNSw1wfRlnkE4oscoEA5Au4nnk785s9jghPFkTBg+A+5uB6trCjf0fM22+Iw6kiP4YYmDdw3BqxAKLQ=="],
|
||||
"sass-embedded-linux-arm": ["sass-embedded-linux-arm@1.86.3", "", { "os": "linux", "cpu": "arm" }, "sha512-+fVCIH+OR0SMHn2NEhb/VfbpHuUxcPtqMS34OCV3Ka99LYZUJZqth4M3lT/ppGl52mwIVLNYzR4iLe6mdZ6mYA=="],
|
||||
|
||||
"sass-embedded-linux-arm64": ["sass-embedded-linux-arm64@1.85.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jGadetB03BMFG2rq3OXub/uvC/lGpbQOiLGEz3NLb2nRZWyauRhzDtvZqkr6BEhxgIWtMtz2020yD8ZJSw/r2w=="],
|
||||
"sass-embedded-linux-arm64": ["sass-embedded-linux-arm64@1.86.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-tYq5rywR53Qtc+0KI6pPipOvW7a47ETY69VxfqI9BR2RKw2hBbaz0bIw6OaOgEBv2/XNwcWb7a4sr7TqgkqKAA=="],
|
||||
|
||||
"sass-embedded-linux-ia32": ["sass-embedded-linux-ia32@1.85.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-7HlYY90d9mitDtNi5s+S+5wYZrTVbkBH2/kf7ixrzh2BFfT0YM81UHLJRnGX93y9aOMBL6DSZAIfkt1RsV9bkQ=="],
|
||||
"sass-embedded-linux-ia32": ["sass-embedded-linux-ia32@1.86.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-CmQ5OkqnaeLdaF+bMqlYGooBuenqm3LvEN9H8BLhjkpWiFW8hnYMetiqMcJjhrXLvDw601KGqA5sr/Rsg5s45g=="],
|
||||
|
||||
"sass-embedded-linux-musl-arm": ["sass-embedded-linux-musl-arm@1.85.1", "", { "os": "linux", "cpu": "arm" }, "sha512-5vcdEqE8QZnu6i6shZo7x2N36V7YUoFotWj2rGekII5ty7Nkaj+VtZhUEOp9tAzEOlaFuDp5CyO1kUCvweT64A=="],
|
||||
"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-arm64": ["sass-embedded-linux-musl-arm64@1.85.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-FLkIT0p18XOkR6wryJ13LqGBDsrYev2dRk9dtiU18NCpNXruKsdBQ1ZnWHVKB3h1dA9lFyEEisC0sooKdNfeOQ=="],
|
||||
"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-ia32": ["sass-embedded-linux-musl-ia32@1.85.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-N1093T84zQJor1yyIAdYScB5eAuQarGK1tKgZ4uTnxVlgA7Xi1lXV8Eh7ox9sDqKCaWkVQ3MjqU26vYRBeRWyw=="],
|
||||
"sass-embedded-linux-musl-ia32": ["sass-embedded-linux-musl-ia32@1.86.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-84Tcld32LB1loiqUvczWyVBQRCChm0wNLlkT59qF29nxh8njFIVf9yaPgXcSyyjpPoD9Tu0wnq3dvVzoMCh9AQ=="],
|
||||
|
||||
"sass-embedded-linux-musl-riscv64": ["sass-embedded-linux-musl-riscv64@1.85.1", "", { "os": "linux", "cpu": "none" }, "sha512-WRsZS/7qlfYXsa93FBpSruieuURIu7ySfFhzYfF1IbKrNAGwmbduutkHZh2ddm5/vQMvQ0Rdosgv+CslaQHMcw=="],
|
||||
"sass-embedded-linux-musl-riscv64": ["sass-embedded-linux-musl-riscv64@1.86.3", "", { "os": "linux", "cpu": "none" }, "sha512-IxEqoiD7vdNpiOwccybbV93NljBy64wSTkUOknGy21SyV43C8uqESOwTwW9ywa3KufImKm8L3uQAW/B0KhJMWg=="],
|
||||
|
||||
"sass-embedded-linux-musl-x64": ["sass-embedded-linux-musl-x64@1.85.1", "", { "os": "linux", "cpu": "x64" }, "sha512-+OlLIilA5TnP0YEqTQ8yZtkW+bJIQYvzoGoNLUEskeyeGuOiIyn2CwL6G4JQB4xZQFaxPHb7JD3EueFkQbH0Pw=="],
|
||||
"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-riscv64": ["sass-embedded-linux-riscv64@1.85.1", "", { "os": "linux", "cpu": "none" }, "sha512-mKKlOwMGLN7yP1p0gB5yG/HX4fYLnpWaqstNuOOXH+fOzTaNg0+1hALg0H0CDIqypPO74M5MS9T6FAJZGdT6dQ=="],
|
||||
"sass-embedded-linux-riscv64": ["sass-embedded-linux-riscv64@1.86.3", "", { "os": "linux", "cpu": "none" }, "sha512-NuXQ72dwfNLe35E+RaXJ4Noq4EkFwM65eWwCwxEWyJO9qxOx1EXiCAJii6x8kkOh5daWuMU0VAI1B9RsJaqqQQ=="],
|
||||
|
||||
"sass-embedded-linux-x64": ["sass-embedded-linux-x64@1.85.1", "", { "os": "linux", "cpu": "x64" }, "sha512-uKRTv0z8NgtHV7xSren78+yoWB79sNi7TMqI7Bxd8fcRNIgHQSA8QBdF8led2ETC004hr8h71BrY60RPO+SSvA=="],
|
||||
"sass-embedded-linux-x64": ["sass-embedded-linux-x64@1.86.3", "", { "os": "linux", "cpu": "x64" }, "sha512-t8be9zJ5B82+og9bQmIQ83yMGYZMTMrlGA+uGWtYacmwg6w3093dk91Fx0YzNSZBp3Tk60qVYjCZnEIwy60x0g=="],
|
||||
|
||||
"sass-embedded-win32-arm64": ["sass-embedded-win32-arm64@1.85.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-/GMiZXBOc6AEMBC3g25Rp+x8fq9Z6Ql7037l5rajBPhZ+DdFwtdHY0Ou3oIU6XuWUwD06U3ii4XufXVFhsP6PA=="],
|
||||
"sass-embedded-win32-arm64": ["sass-embedded-win32-arm64@1.86.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-4ghuAzjX4q8Nksm0aifRz8hgXMMxS0SuymrFfkfJlrSx68pIgvAge6AOw0edoZoe0Tf5ZbsWUWamhkNyNxkTvw=="],
|
||||
|
||||
"sass-embedded-win32-ia32": ["sass-embedded-win32-ia32@1.85.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-L+4BWkKKBGFOKVQ2PQ5HwFfkM5FvTf1Xx2VSRvEWt9HxPXp6SPDho6zC8fqNQ3hSjoaoASEIJcSvgfdQYO0gdg=="],
|
||||
"sass-embedded-win32-ia32": ["sass-embedded-win32-ia32@1.86.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-tCaK4zIRq9mLRPxLzBAdYlfCuS/xLNpmjunYxeWkIwlJo+k53h1udyXH/FInnQ2GgEz0xMXyvH3buuPgzwWYsw=="],
|
||||
|
||||
"sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.85.1", "", { "os": "win32", "cpu": "x64" }, "sha512-/FO0AGKWxVfCk4GKsC0yXWBpUZdySe3YAAbQQL0lL6xUd1OiUY8Kow6g4Kc1TB/+z0iuQKKTqI/acJMEYl4iTQ=="],
|
||||
"sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.86.3", "", { "os": "win32", "cpu": "x64" }, "sha512-zS+YNKfTF4SnOfpC77VTb0qNZyTXrxnAezSoRV0xnw6HlY+1WawMSSB6PbWtmbvyfXNgpmJUttoTtsvJjRCucg=="],
|
||||
|
||||
"semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
|
||||
|
||||
@ -698,7 +706,7 @@
|
||||
|
||||
"sync-message-port": ["sync-message-port@1.1.3", "", {}, "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.0.9", "", {}, "sha512-12laZu+fv1ONDRoNR9ipTOpUD7RN9essRVkX36sjxuRUInpN7hIiHN4lBd/SIFjbISvnXzp8h/hXzmU8SQQYhw=="],
|
||||
"tailwindcss": ["tailwindcss@4.1.3", "", {}, "sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g=="],
|
||||
|
||||
"tailwindcss-primeui": ["tailwindcss-primeui@0.4.0", "", { "peerDependencies": { "tailwindcss": ">=3.1.0" } }, "sha512-YYC7B7Yyzm1/4pEGgpf1ABAhbrKY++LuPoUamnKE7fTPO5Ct/Qr/dT+Uq2yiVhQnaW1zHQpYnThxfksaxhlDfQ=="],
|
||||
|
||||
@ -714,13 +722,13 @@
|
||||
|
||||
"type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
|
||||
|
||||
"typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.24.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.24.0", "@typescript-eslint/parser": "8.24.0", "@typescript-eslint/utils": "8.24.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-/lmv4366en/qbB32Vz5+kCNZEMf6xYHwh1z48suBwZvAtnXKbP+YhGe8OLE2BqC67LMqKkCNLtjejdwsdW6uOQ=="],
|
||||
"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=="],
|
||||
|
||||
"ufo": ["ufo@1.5.4", "", {}, "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="],
|
||||
|
||||
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"unplugin": ["unplugin@2.0.0-beta.1", "", { "dependencies": { "acorn": "^8.14.0", "webpack-virtual-modules": "^0.6.2" } }, "sha512-2qzQo5LN2DmUZXkWDHvGKLF5BP0WN+KthD6aPnPJ8plRBIjv4lh5O07eYcSxgO2znNw9s4MNhEO1sB+JDllDbQ=="],
|
||||
|
||||
@ -736,19 +744,19 @@
|
||||
|
||||
"varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="],
|
||||
|
||||
"vite": ["vite@6.2.0", "", { "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-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ=="],
|
||||
"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-plugin-vuetify": ["vite-plugin-vuetify@2.1.0", "", { "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-4wEAQtZaigPpwbFcZbrKpYwutOsWwWdeXn22B9XHzDPQNxVsKT+K9lKcXZnI5JESO1Iaql48S9rOk8RZZEt+Mw=="],
|
||||
"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=="],
|
||||
|
||||
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
|
||||
|
||||
"vue": ["vue@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/compiler-sfc": "3.5.13", "@vue/runtime-dom": "3.5.13", "@vue/server-renderer": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ=="],
|
||||
|
||||
"vue-eslint-parser": ["vue-eslint-parser@9.4.3", "", { "dependencies": { "debug": "^4.3.4", "eslint-scope": "^7.1.1", "eslint-visitor-keys": "^3.3.0", "espree": "^9.3.1", "esquery": "^1.4.0", "lodash": "^4.17.21", "semver": "^7.3.6" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg=="],
|
||||
"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-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.7.15", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=1.0.0", "vue": "^3.3.0", "webpack-plugin-vuetify": ">=2.0.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-kBZzwXI5EcAMiW5TRMgK1reXQd0K/PpUt+ekX4Alvm7n09uzJ1my1TLNbX1sQ8/0KYgoxOf17C8qOJzBGkT+PA=="],
|
||||
"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=="],
|
||||
|
||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||
|
||||
@ -776,13 +784,7 @@
|
||||
|
||||
"@primeuix/forms/@primeuix/utils": ["@primeuix/utils@0.4.1", "", {}, "sha512-5+1NLfyna+gLRPeFTo+xlR0tfPVLuVdidbeahAMLkQga5Rw0LxyUBCyD2/Zv2JkV69o2T+hpEDyddl3VdnYoBw=="],
|
||||
|
||||
"@tauri-apps/plugin-deep-link/@tauri-apps/api": ["@tauri-apps/api@2.2.0", "", {}, "sha512-R8epOeZl1eJEl603aUMIGb4RXlhPjpgxbGVEaqY+0G5JG9vzV/clNlzTeqc+NLYXVqXcn8mb4c5b9pJIUDEyAg=="],
|
||||
|
||||
"@tauri-apps/plugin-dialog/@tauri-apps/api": ["@tauri-apps/api@2.2.0", "", {}, "sha512-R8epOeZl1eJEl603aUMIGb4RXlhPjpgxbGVEaqY+0G5JG9vzV/clNlzTeqc+NLYXVqXcn8mb4c5b9pJIUDEyAg=="],
|
||||
|
||||
"@tauri-apps/plugin-fs/@tauri-apps/api": ["@tauri-apps/api@2.2.0", "", {}, "sha512-R8epOeZl1eJEl603aUMIGb4RXlhPjpgxbGVEaqY+0G5JG9vzV/clNlzTeqc+NLYXVqXcn8mb4c5b9pJIUDEyAg=="],
|
||||
|
||||
"@tauri-apps/plugin-shell/@tauri-apps/api": ["@tauri-apps/api@2.2.0", "", {}, "sha512-R8epOeZl1eJEl603aUMIGb4RXlhPjpgxbGVEaqY+0G5JG9vzV/clNlzTeqc+NLYXVqXcn8mb4c5b9pJIUDEyAg=="],
|
||||
"@tauri-apps/plugin-opener/@tauri-apps/api": ["@tauri-apps/api@2.3.0", "", {}, "sha512-33Z+0lX2wgZbx1SPFfqvzI6su63hCBkbzv+5NexeYjIx7WA9htdOKoRR7Dh3dJyltqS5/J8vQFyybiRoaL0hlA=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
@ -798,6 +800,8 @@
|
||||
|
||||
"eslint/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"eslint-plugin-vue/vue-eslint-parser": ["vue-eslint-parser@9.4.3", "", { "dependencies": { "debug": "^4.3.4", "eslint-scope": "^7.1.1", "eslint-visitor-keys": "^3.3.0", "espree": "^9.3.1", "esquery": "^1.4.0", "lodash": "^4.17.21", "semver": "^7.3.6" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg=="],
|
||||
|
||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
@ -808,18 +812,18 @@
|
||||
|
||||
"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-eslint-parser/eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
|
||||
|
||||
"vue-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"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/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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"eslint-plugin-vue/vue-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"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=="],
|
||||
}
|
||||
}
|
||||
|
44
package.json
44
package.json
@ -11,42 +11,42 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "7.4.47",
|
||||
"@primevue/forms": "^4.3.1",
|
||||
"@primevue/themes": "^4.3.1",
|
||||
"@tailwindcss/vite": "^4.0.9",
|
||||
"@tauri-apps/api": "^2.3.0",
|
||||
"@primevue/forms": "^4.3.3",
|
||||
"@primevue/themes": "^4.3.3",
|
||||
"@tailwindcss/vite": "^4.1.3",
|
||||
"@tauri-apps/api": "^2.4.1",
|
||||
"@tauri-apps/plugin-cli": "^2.2.0",
|
||||
"@tauri-apps/plugin-deep-link": "~2.2.0",
|
||||
"@tauri-apps/plugin-dialog": "~2.2.0",
|
||||
"@tauri-apps/plugin-fs": "^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.0",
|
||||
"@tauri-apps/plugin-updater": "^2.6.1",
|
||||
"@tauri-apps/plugin-shell": "~2.2.1",
|
||||
"@tauri-apps/plugin-updater": "^2.7.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"pinia": "^3.0.1",
|
||||
"pinia": "^3.0.2",
|
||||
"primeicons": "^7.0.0",
|
||||
"primevue": "^4.3.1",
|
||||
"primevue": "^4.3.3",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"tailwindcss": "^4.0.9",
|
||||
"tailwindcss": "^4.1.3",
|
||||
"tailwindcss-primeui": "^0.4.0",
|
||||
"vue": "^3.5.13",
|
||||
"vuetify": "^3.7.15"
|
||||
"vuetify": "^3.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.3.1",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/node": "^22.13.9",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vue/eslint-config-typescript": "^14.4.0",
|
||||
"@tauri-apps/cli": "^2.4.1",
|
||||
"@tsconfig/node22": "^22.0.1",
|
||||
"@types/node": "^22.14.1",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vue/eslint-config-typescript": "^14.5.0",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"sass": "1.77.8",
|
||||
"sass-embedded": "^1.85.1",
|
||||
"typescript": "^5.8.2",
|
||||
"sass-embedded": "^1.86.3",
|
||||
"typescript": "^5.8.3",
|
||||
"unplugin-fonts": "^1.3.1",
|
||||
"unplugin-vue-components": "^0.27.5",
|
||||
"vite": "^6.2.0",
|
||||
"vite-plugin-vuetify": "^2.1.0",
|
||||
"vite": "^6.2.6",
|
||||
"vite-plugin-vuetify": "^2.1.1",
|
||||
"vue-tsc": "^2.2.8"
|
||||
}
|
||||
}
|
||||
|
1162
rust/Cargo.lock
generated
1162
rust/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -28,7 +28,6 @@ futures = "0.3.31"
|
||||
tauri-plugin-shell = "2"
|
||||
directories = "6.0.0"
|
||||
rust-ini = "0.21.1"
|
||||
simple_logger = "5.0.0"
|
||||
log = "0.4.25"
|
||||
regex = "1.11.1"
|
||||
zip = "2.2.2"
|
||||
@ -42,6 +41,10 @@ junction = "1.2.0"
|
||||
tauri-plugin-fs = "2"
|
||||
yaml-rust2 = "0.10.0"
|
||||
enumflags2 = { version = "0.7.11", features = ["serde"] }
|
||||
sha256 = "1.6.0"
|
||||
serialport = "4.7.1"
|
||||
fern = { version ="0.7.1", features = ["colored"] }
|
||||
humantime = "2.2.0"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-cli = "2"
|
||||
|
@ -12,6 +12,9 @@
|
||||
"core:window:allow-set-focus",
|
||||
"core:window:allow-hide",
|
||||
"core:window:allow-show",
|
||||
"core:window:allow-set-size",
|
||||
"core:window:allow-inner-size",
|
||||
"core:window:allow-set-min-size",
|
||||
"core:app:allow-app-hide",
|
||||
"shell:default",
|
||||
"dialog:default",
|
||||
|
@ -1,7 +1,8 @@
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
use crate::model::config::GlobalConfig;
|
||||
use crate::model::patch::PatchFileVec;
|
||||
use crate::pkg::{Feature, Status};
|
||||
use crate::profiles::Profile;
|
||||
use crate::profiles::types::Profile;
|
||||
use crate::{model::misc::Game, pkg::PkgKey};
|
||||
use crate::pkg_store::PackageStore;
|
||||
use crate::util;
|
||||
@ -17,6 +18,7 @@ pub struct AppData {
|
||||
pub pkgs: PackageStore,
|
||||
pub cfg: GlobalConfig,
|
||||
pub state: GlobalState,
|
||||
pub patch_vec: PatchFileVec,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
@ -37,13 +39,18 @@ impl AppData {
|
||||
None => None
|
||||
};
|
||||
|
||||
log::debug!("Recent profile: {:?}", profile);
|
||||
let patch_vec = PatchFileVec::new(util::config_dir())
|
||||
.map_err(|e| log::error!("unable to load patch set: {e}"))
|
||||
.unwrap_or_default();
|
||||
|
||||
log::info!("recent profile: {:?}", profile);
|
||||
|
||||
AppData {
|
||||
profile: profile,
|
||||
pkgs: PackageStore::new(apph.clone()),
|
||||
cfg,
|
||||
state: GlobalState { remain_open: true }
|
||||
state: GlobalState { remain_open: true },
|
||||
patch_vec
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,8 +85,7 @@ impl AppData {
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Attempted to enable a non-existent package"))?;
|
||||
|
||||
if let Status::OK(feature_set) = loc.status {
|
||||
log::debug!("{:?}", feature_set);
|
||||
if let Status::OK(feature_set, _) = loc.status {
|
||||
if feature_set.contains(Feature::Mod) {
|
||||
profile.mod_pkgs_mut().insert(key);
|
||||
}
|
||||
|
123
rust/src/cmd.rs
123
rust/src/cmd.rs
@ -1,12 +1,17 @@
|
||||
use ini::Ini;
|
||||
use log;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{BTreeMap, 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::misc::Game;
|
||||
use crate::model::patch::Patch;
|
||||
use crate::modules::package::prepare_dlls;
|
||||
use crate::pkg::{Package, PkgKey};
|
||||
use crate::pkg_store::{InstallResult, PackageStore};
|
||||
use crate::profiles::{self, Profile, ProfileData, ProfileMeta, ProfilePaths};
|
||||
use crate::profiles::{self, Profile, ProfileData, ProfileMeta, ProfilePaths, StartPayload};
|
||||
use crate::appdata::{AppData, ToggleAction};
|
||||
use crate::model::misc::StartCheckError;
|
||||
use crate::util;
|
||||
@ -55,18 +60,40 @@ pub async fn startline(app: AppHandle, refresh: bool) -> Result<(), String> {
|
||||
let state = app.state::<Mutex<AppData>>();
|
||||
let mut hash = "".to_owned();
|
||||
|
||||
let mut appd = state.lock().await;
|
||||
let appd = state.lock().await;
|
||||
let mut game_dlls = Vec::new();
|
||||
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())?
|
||||
}
|
||||
if let Some(p) = &mut appd.profile {
|
||||
if let Some(p) = &appd.profile {
|
||||
log::debug!("{}", hash);
|
||||
p.line_up(hash, refresh, app.clone()).await
|
||||
.map_err(|e| format!("Lineup failed:\n{}", e))?;
|
||||
let info = p.prepare_display()
|
||||
.map_err(|e| e.to_string())?;
|
||||
let lineup_res = p.line_up(hash, refresh, &appd.patch_vec).await
|
||||
.map_err(|e| e.to_string());
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Some(info) = info {
|
||||
use crate::model::profile::Display;
|
||||
if lineup_res.is_ok() {
|
||||
Display::wait_for_exit(app.clone(), info);
|
||||
} else {
|
||||
Display::clean_up(&info).map_err(|e| e.to_string())?;
|
||||
}
|
||||
}
|
||||
|
||||
lineup_res?;
|
||||
|
||||
let app_clone = app.clone();
|
||||
let p_clone = p.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
if let Err(e) = p_clone.start(app_clone).await {
|
||||
if let Err(e) = p_clone.start(StartPayload {
|
||||
app: app_clone,
|
||||
game_dlls,
|
||||
amd_dlls
|
||||
}).await {
|
||||
log::error!("Startup failed:\n{}", e);
|
||||
}
|
||||
});
|
||||
@ -148,6 +175,10 @@ pub async fn get_all_packages(state: State<'_, Mutex<AppData>>) -> Result<HashMa
|
||||
|
||||
let appd = state.lock().await;
|
||||
|
||||
let pkgs_all = appd.pkgs.get_all();
|
||||
|
||||
log::debug!("pkgs_all: {:?}", pkgs_all);
|
||||
|
||||
Ok(appd.pkgs.get_all())
|
||||
}
|
||||
|
||||
@ -318,7 +349,7 @@ pub async fn get_current_profile(state: State<'_, Mutex<AppData>>) -> Result<Opt
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn sync_current_profile(state: State<'_, Mutex<AppData>>, data: ProfileData) -> Result<(), String> {
|
||||
log::debug!("invoke: sync_current_profile");
|
||||
log::debug!("invoke: sync_current_profile {:?}", data);
|
||||
|
||||
let mut appd = state.lock().await;
|
||||
if let Some(p) = &mut appd.profile {
|
||||
@ -344,6 +375,27 @@ pub async fn save_current_profile(state: State<'_, Mutex<AppData>>) -> Result<()
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn load_segatools_ini(state: State<'_, Mutex<AppData>>, path: PathBuf) -> Result<(), String> {
|
||||
log::debug!("invoke: load_segatools_ini({:?})", path);
|
||||
|
||||
let mut appd = state.lock().await;
|
||||
if let Some(p) = &mut appd.profile {
|
||||
let str = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
|
||||
// Stupid path escape hack for the ini reader
|
||||
let str = str.replace("\\", "\\\\").replace("\\\\\\\\", "\\\\");
|
||||
let ini = Ini::load_from_str(&str).map_err(|e| e.to_string())?;
|
||||
p.data.sgt.load_from_ini(&ini, p.config_dir()).map_err(|e| e.to_string())?;
|
||||
p.data.network.load_from_ini(&ini).map_err(|e| e.to_string())?;
|
||||
if let Some(kb) = &mut p.data.keyboard {
|
||||
kb.load_from_ini(&ini).map_err(|e| e.to_string())?;
|
||||
}
|
||||
p.save().map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_platform_capabilities() -> Result<Vec<String>, ()> {
|
||||
log::debug!("invoke: list_platform_capabilities");
|
||||
@ -355,6 +407,29 @@ pub async fn list_platform_capabilities() -> Result<Vec<String>, ()> {
|
||||
return Ok(vec!["wine".to_owned()]);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_global_config(state: State<'_, Mutex<AppData>>, field: GlobalConfigField) -> Result<bool, ()> {
|
||||
log::debug!("invoke: get_global_config({field:?})");
|
||||
|
||||
let appd = state.lock().await;
|
||||
match field {
|
||||
GlobalConfigField::OfflineMode => Ok(appd.cfg.offline_mode),
|
||||
GlobalConfigField::EnableAutoupdates => Ok(appd.cfg.enable_autoupdates)
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_global_config(state: State<'_, Mutex<AppData>>, field: GlobalConfigField, value: bool) -> Result<(), String> {
|
||||
log::debug!("invoke: set_global_config({field:?}, {value})");
|
||||
|
||||
let mut appd = state.lock().await;
|
||||
match field {
|
||||
GlobalConfigField::OfflineMode => appd.cfg.offline_mode = value,
|
||||
GlobalConfigField::EnableAutoupdates => appd.cfg.enable_autoupdates = value
|
||||
};
|
||||
appd.write().map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn list_displays() -> Result<Vec<(String, String)>, String> {
|
||||
@ -389,4 +464,36 @@ pub async fn list_directories() -> Result<util::Dirs, ()> {
|
||||
log::debug!("invoke: list_directores");
|
||||
|
||||
Ok(util::all_dirs().clone())
|
||||
}
|
||||
|
||||
// Tauri fs api is useless
|
||||
#[tauri::command]
|
||||
pub async fn file_exists(path: String) -> Result<bool, ()> {
|
||||
Ok(std::fs::exists(path).unwrap_or(false))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_com_ports() -> Result<BTreeMap<String, i32>, String> {
|
||||
let ports = serialport::available_ports().unwrap_or(Vec::new());
|
||||
let mut res = BTreeMap::new();
|
||||
for p in ports {
|
||||
log::debug!("port {}", p.port_name);
|
||||
if p.port_name.starts_with("COM") {
|
||||
if let Ok(parsed) = (p.port_name[3..]).parse() {
|
||||
res.insert(p.port_name, parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_patches(state: State<'_, Mutex<AppData>>, target: String) -> Result<Vec<Patch>, String> {
|
||||
log::debug!("invoke: list_patches({})", target);
|
||||
|
||||
let mut appd = state.lock().await;
|
||||
appd.fix();
|
||||
let list = appd.patch_vec.find_patches(target).map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(list)
|
||||
}
|
@ -43,7 +43,7 @@ impl DownloadHandler {
|
||||
let mut cache_file_w = File::create(&zip_path_part).await?;
|
||||
let mut byte_stream = reqwest::get(&rmt.download_url).await?.bytes_stream();
|
||||
|
||||
log::info!("Downloading: {}", rmt.download_url);
|
||||
log::info!("downloading: {}", rmt.download_url);
|
||||
while let Some(item) = byte_stream.next().await {
|
||||
let i = item?;
|
||||
cache_file_w.write_all(&mut i.as_ref()).await?;
|
||||
@ -51,7 +51,7 @@ impl DownloadHandler {
|
||||
cache_file_w.sync_all().await?;
|
||||
tokio::fs::rename(&zip_path_part, &zip_path).await?;
|
||||
|
||||
log::debug!("Downloaded to {:?}", zip_path);
|
||||
log::debug!("downloaded to {:?}", zip_path);
|
||||
|
||||
app.emit("download-end", pkg_key)?;
|
||||
|
||||
|
135
rust/src/lib.rs
135
rust/src/lib.rs
@ -7,34 +7,25 @@ mod download_handler;
|
||||
mod appdata;
|
||||
mod modules;
|
||||
mod profiles;
|
||||
mod patcher;
|
||||
|
||||
use std::sync::OnceLock;
|
||||
use std::{sync::OnceLock, time::SystemTime};
|
||||
use anyhow::anyhow;
|
||||
use closure::closure;
|
||||
use appdata::{AppData, ToggleAction};
|
||||
use fern::colors::{Color, ColoredLevelConfig};
|
||||
use model::misc::Game;
|
||||
use pkg::PkgKey;
|
||||
use pkg_store::Payload;
|
||||
use tauri::{AppHandle, Listener, Manager, RunEvent};
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
use tauri_plugin_cli::CliExt;
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
use tokio::{fs, sync::Mutex, try_join};
|
||||
|
||||
static EXIT_REQUESTED: OnceLock<()> = OnceLock::new();
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub async fn run(_args: Vec<String>) {
|
||||
simple_logger::init_with_env().expect("Unable to initialize the logger");
|
||||
|
||||
log::info!(
|
||||
"Running from {}",
|
||||
std::env::current_dir()
|
||||
.unwrap_or_default()
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
);
|
||||
|
||||
let tauri = tauri::Builder::default()
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
|
||||
@ -57,6 +48,51 @@ pub async fn run(_args: Vec<String>) {
|
||||
|
||||
util::init_dirs(&apph);
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
#[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!(
|
||||
"running from {}",
|
||||
std::env::current_dir()
|
||||
.unwrap_or_default()
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
);
|
||||
|
||||
let mut app_data = AppData::new(app.handle().clone());
|
||||
let start_immediately;
|
||||
|
||||
@ -71,8 +107,8 @@ pub async fn run(_args: Vec<String>) {
|
||||
} else {
|
||||
tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::App("index.html".into()))
|
||||
.title("STARTLINER")
|
||||
.inner_size(720f64, 480f64)
|
||||
.min_inner_size(720f64, 480f64)
|
||||
.inner_size(900f64, 480f64)
|
||||
.min_inner_size(900f64, 480f64)
|
||||
.build()?;
|
||||
start_immediately = false;
|
||||
}
|
||||
@ -199,10 +235,19 @@ pub async fn run(_args: Vec<String>) {
|
||||
cmd::get_current_profile,
|
||||
cmd::sync_current_profile,
|
||||
cmd::save_current_profile,
|
||||
cmd::load_segatools_ini,
|
||||
|
||||
cmd::get_global_config,
|
||||
cmd::set_global_config,
|
||||
|
||||
cmd::list_displays,
|
||||
cmd::list_platform_capabilities,
|
||||
cmd::list_directories,
|
||||
cmd::file_exists,
|
||||
|
||||
cmd::list_com_ports,
|
||||
|
||||
cmd::list_patches,
|
||||
])
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while building tauri application");
|
||||
@ -237,7 +282,7 @@ fn deep_link(app: AppHandle, args: Vec<String>) {
|
||||
let url = &args[1];
|
||||
let proto = "rainycolor://";
|
||||
if &url[..proto.len()] == proto {
|
||||
log::info!("Deep link: {}", url);
|
||||
log::info!("deep link: {}", url);
|
||||
|
||||
let regex = regex::Regex::new(
|
||||
r"rainycolor://v1/install/rainy\.patafour\.zip/([^/]+)/([^/]+)/[0-9]+\.[0-9]+\.[0-9]+/"
|
||||
@ -262,23 +307,53 @@ fn deep_link(app: AppHandle, args: Vec<String>) {
|
||||
}
|
||||
|
||||
async fn update(app: tauri::AppHandle) -> tauri_plugin_updater::Result<()> {
|
||||
if let Some(update) = app.updater()?.check().await? {
|
||||
let mut downloaded = 0;
|
||||
update
|
||||
.download_and_install(
|
||||
|chunk_length, content_length| {
|
||||
downloaded += chunk_length;
|
||||
log::debug!("downloaded {downloaded} from {content_length:?}");
|
||||
},
|
||||
|| {
|
||||
log::info!("download finished");
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let mutex = app.state::<Mutex<AppData>>();
|
||||
{
|
||||
let appd = mutex.lock().await;
|
||||
if !appd.cfg.enable_autoupdates {
|
||||
log::info!("skipping auto-update");
|
||||
|
||||
log::info!("update installed");
|
||||
app.restart();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
use tauri::Emitter;
|
||||
if let Some(update) = app.updater()?.check().await? {
|
||||
let mut downloaded = 0;
|
||||
update.download_and_install(
|
||||
|chunk_length, content_length| {
|
||||
downloaded += chunk_length;
|
||||
_ = app.emit("update-progress", (chunk_length as f64) / (content_length.unwrap_or(u64::MAX) as f64));
|
||||
},
|
||||
|| {
|
||||
log::info!("download finished");
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
log::info!("update installed");
|
||||
app.restart();
|
||||
}
|
||||
}
|
||||
|
||||
// One day I will write proper tests
|
||||
// #[cfg(debug_assertions)]
|
||||
// {
|
||||
// use tauri::Emitter;
|
||||
// std::thread::sleep(std::time::Duration::from_millis(5000));
|
||||
// let mut downloaded = 0;
|
||||
// while downloaded < 500 {
|
||||
// std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
// downloaded += 1;
|
||||
// _ = app.emit("update-progress", (downloaded as f32) / 500f32);
|
||||
// }
|
||||
// app.restart();
|
||||
// }
|
||||
|
||||
log::info!("ending auto-update check");
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,10 +1,25 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use super::misc::Game;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct GlobalConfig {
|
||||
pub recent_profile: Option<(Game, String)>,
|
||||
|
||||
#[serde(default)]
|
||||
pub offline_mode: bool,
|
||||
pub enable_autoupdates: bool,
|
||||
}
|
||||
|
||||
impl Default for GlobalConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
recent_profile: Default::default(),
|
||||
offline_mode: false,
|
||||
enable_autoupdates: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum GlobalConfigField {
|
||||
OfflineMode,
|
||||
EnableAutoupdates
|
||||
}
|
@ -59,14 +59,14 @@ impl Game {
|
||||
pub fn amd_args(&self) -> Vec<&'static str> {
|
||||
match self {
|
||||
Game::Ongeki => vec!["-f", "-c", "config_common.json", "config_server.json", "config_client.json"],
|
||||
Game::Chunithm => vec!["-c", "config_common.json", "config_server.json", "config_client.json", "config_cvt.json", "config_sp.json", "config_hook.json"]
|
||||
Game::Chunithm => vec!["-c", "config_common.json", "config_server.json", "config_client.json", "config_cvt.json", "config_sp.json"]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_module(&self, module: ProfileModule) -> bool {
|
||||
match self {
|
||||
Game::Ongeki => make_bitflags!(ProfileModule::{Segatools | Display | Network | BepInEx | Mu3Ini}),
|
||||
Game::Chunithm => make_bitflags!(ProfileModule::{Segatools | Network}),
|
||||
Game::Ongeki => make_bitflags!(ProfileModule::{Segatools | Display | Network | BepInEx | Mu3Ini | Keyboard}),
|
||||
Game::Chunithm => make_bitflags!(ProfileModule::{Segatools | Display | Network | Keyboard | Mempatcher}),
|
||||
}.contains(module)
|
||||
}
|
||||
}
|
||||
@ -86,4 +86,28 @@ pub enum StartCheckError {
|
||||
MissingLocalPackage(PkgKey),
|
||||
MissingDependency(PkgKey, PkgKey),
|
||||
MissingTool(PkgKey),
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone)]
|
||||
pub struct ConfigHook {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub allnet_auth: Option<ConfigHookAuth>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub aime: Option<ConfigHookAime>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone)]
|
||||
pub struct ConfigHookAuth {
|
||||
pub r#type: String
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone)]
|
||||
pub struct ConfigHookAime {
|
||||
pub unit: Vec<ConfigHookAimeUnit>
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone)]
|
||||
pub struct ConfigHookAimeUnit {
|
||||
pub port: i32,
|
||||
pub id: i32
|
||||
}
|
@ -3,4 +3,4 @@ pub mod misc;
|
||||
pub mod rainy;
|
||||
pub mod profile;
|
||||
pub mod config;
|
||||
pub mod segatools_base;
|
||||
pub mod patch;
|
162
rust/src/model/patch.rs
Normal file
162
rust/src/model/patch.rs
Normal file
@ -0,0 +1,162 @@
|
||||
use std::collections::BTreeMap;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
use serde::ser::{Serializer, SerializeStruct};
|
||||
use serde_json::Value;
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct PatchSelection(pub BTreeMap<String, PatchSelectionData>);
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PatchSelectionData {
|
||||
Enabled,
|
||||
Number(i8),
|
||||
Hex(Vec<u8>)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PatchFileVec(pub Vec<PatchFile>);
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct PatchFile(pub Vec<PatchList>);
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct PatchList {
|
||||
pub filename: String,
|
||||
pub version: String,
|
||||
pub sha256: String,
|
||||
pub patches: Vec<Patch>
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Patch {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub tooltip: Option<String>,
|
||||
pub data: PatchData
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum PatchData {
|
||||
Normal(NormalPatch),
|
||||
Number(NumberPatch),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct NormalPatch {
|
||||
pub patches: Vec<NormalPatchField>
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct NormalPatchField {
|
||||
pub offset: u64,
|
||||
pub off: Vec<u8>,
|
||||
pub on: Vec<u8>
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct NumberPatch {
|
||||
pub offset: u64,
|
||||
pub default: i32,
|
||||
pub size: i64,
|
||||
pub min: i32,
|
||||
pub max: i32
|
||||
}
|
||||
|
||||
impl Serialize for Patch {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer {
|
||||
let mut state = serializer.serialize_struct("Patch", 7)?;
|
||||
state.serialize_field("id", &self.id)?;
|
||||
state.serialize_field("name", &self.name)?;
|
||||
state.serialize_field("tooltip", &self.tooltip)?;
|
||||
match &self.data {
|
||||
PatchData::Normal(patch) => {
|
||||
state.serialize_field("patches", &patch.patches)?;
|
||||
}
|
||||
PatchData::Number(patch) => {
|
||||
state.serialize_field("type", "number")?;
|
||||
state.serialize_field("offset", &patch.offset)?;
|
||||
state.serialize_field("default", &patch.default)?;
|
||||
state.serialize_field("size", &patch.size)?;
|
||||
state.serialize_field("min", &patch.min)?;
|
||||
state.serialize_field("max", &patch.max)?;
|
||||
}
|
||||
}
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for Patch {
|
||||
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
||||
let value = Value::deserialize(d)?;
|
||||
|
||||
let data = Ok(match value.get("type").and_then(Value::as_str) {
|
||||
Some("number") => PatchData::Number(NumberPatch {
|
||||
offset: value.get("offset")
|
||||
.and_then(Value::as_u64)
|
||||
.ok_or_else(|| de::Error::missing_field("offset"))?,
|
||||
default: i32::try_from(value.get("default")
|
||||
.and_then(Value::as_i64)
|
||||
.ok_or_else(|| de::Error::missing_field("default"))?
|
||||
).map_err(|_| de::Error::missing_field("default"))?,
|
||||
size: value.get("size")
|
||||
.and_then(Value::as_i64)
|
||||
.ok_or_else(|| de::Error::missing_field("size"))?,
|
||||
min: i32::try_from(value.get("min")
|
||||
.and_then(Value::as_i64)
|
||||
.ok_or_else(|| de::Error::missing_field("min"))?
|
||||
).map_err(|_| de::Error::missing_field("min"))?,
|
||||
max: i32::try_from(value.get("max")
|
||||
.and_then(Value::as_i64)
|
||||
.ok_or_else(|| de::Error::missing_field("max"))?
|
||||
).map_err(|_| de::Error::missing_field("max"))?
|
||||
}),
|
||||
None => {
|
||||
let mut patches = vec![];
|
||||
for patch in value.get("patches").and_then(Value::as_array).unwrap() {
|
||||
let mut off_list: Vec<u8> = Vec::new();
|
||||
let mut on_list: Vec<u8> = Vec::new();
|
||||
for off in patch.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 on in patch.get("on").and_then(Value::as_array).unwrap() {
|
||||
on_list.push(u8::try_from(
|
||||
on.as_u64().ok_or_else(|| de::Error::missing_field("on"))?
|
||||
).map_err(|_| de::Error::missing_field("on"))?);
|
||||
}
|
||||
patches.push(NormalPatchField {
|
||||
offset: patch.get("offset")
|
||||
.and_then(Value::as_u64)
|
||||
.ok_or_else(|| de::Error::missing_field("offset"))?,
|
||||
off: off_list,
|
||||
on: on_list
|
||||
})
|
||||
}
|
||||
PatchData::Normal(NormalPatch {
|
||||
patches
|
||||
})
|
||||
},
|
||||
Some(&_) => return Err(de::Error::custom("unsupported type"))
|
||||
});
|
||||
|
||||
Ok(Patch {
|
||||
id: value.get("id")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| de::Error::missing_field("id"))?
|
||||
.to_owned(),
|
||||
name: value.get("name")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| de::Error::missing_field("name"))?
|
||||
.to_owned(),
|
||||
tooltip: value.get("tooltip")
|
||||
.and_then(Value::as_str)
|
||||
.and_then(|s| Some(s.to_owned())),
|
||||
data: data?
|
||||
})
|
||||
}
|
||||
}
|
@ -39,6 +39,7 @@ pub struct Segatools {
|
||||
pub intel: bool,
|
||||
#[serde(default)]
|
||||
pub amnet: AMNet,
|
||||
pub aime_port: Option<i32>,
|
||||
}
|
||||
|
||||
impl Segatools {
|
||||
@ -56,6 +57,7 @@ impl Segatools {
|
||||
aime: Aime::default(),
|
||||
intel: false,
|
||||
amnet: AMNet::default(),
|
||||
aime_port: None
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,9 +74,15 @@ pub struct Display {
|
||||
pub target: String,
|
||||
pub rez: (i32, i32),
|
||||
pub mode: DisplayMode,
|
||||
pub rotation: i32,
|
||||
pub rotation: Option<i32>,
|
||||
pub frequency: i32,
|
||||
pub borderless_fullscreen: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub dont_switch_primary: bool,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub monitor_index_override: Option<i32>,
|
||||
}
|
||||
|
||||
impl Display {
|
||||
@ -86,12 +94,14 @@ impl Display {
|
||||
Game::Ongeki => (1080, 1920),
|
||||
},
|
||||
mode: DisplayMode::Borderless,
|
||||
rotation: 0,
|
||||
rotation: None,
|
||||
frequency: match game {
|
||||
Game::Chunithm => 120,
|
||||
Game::Ongeki => 60,
|
||||
},
|
||||
borderless_fullscreen: true,
|
||||
dont_switch_primary: false,
|
||||
monitor_index_override: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -154,13 +164,88 @@ pub struct Mu3Ini {
|
||||
pub blacklist: Option<(i32, i32)>,
|
||||
}
|
||||
|
||||
fn default_true() -> bool { true }
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct OngekiKeyboard {
|
||||
#[serde(default = "default_true")] pub enabled: bool,
|
||||
pub use_mouse: bool,
|
||||
pub coin: i32,
|
||||
pub svc: i32,
|
||||
pub test: i32,
|
||||
pub lmenu: i32,
|
||||
pub rmenu: i32,
|
||||
pub l1: i32,
|
||||
pub l2: i32,
|
||||
pub l3: i32,
|
||||
pub r1: i32,
|
||||
pub r2: i32,
|
||||
pub r3: i32,
|
||||
pub lwad: i32,
|
||||
pub rwad: i32,
|
||||
}
|
||||
|
||||
impl Default for OngekiKeyboard {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
use_mouse: true,
|
||||
test: 0x70,
|
||||
svc: 0x71,
|
||||
coin: 0x72,
|
||||
lmenu: 0x55,
|
||||
rmenu: 0x4F,
|
||||
lwad: 0x01,
|
||||
rwad: 0x02,
|
||||
l1: 0x41,
|
||||
l2: 0x53,
|
||||
l3: 0x44,
|
||||
r1: 0x4A,
|
||||
r2: 0x4B,
|
||||
r3: 0x4C
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct ChunithmKeyboard {
|
||||
#[serde(default = "default_true")] pub enabled: bool,
|
||||
pub coin: i32,
|
||||
pub svc: i32,
|
||||
pub test: i32,
|
||||
pub cell: [i32; 32],
|
||||
pub ir: [i32; 6],
|
||||
}
|
||||
|
||||
impl Default for ChunithmKeyboard {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
test: 0x70,
|
||||
svc: 0x71,
|
||||
coin: 0x72,
|
||||
cell: Default::default(),
|
||||
ir: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(tag = "game", content = "data")]
|
||||
pub enum Keyboard {
|
||||
Ongeki(OngekiKeyboard),
|
||||
Chunithm(ChunithmKeyboard),
|
||||
}
|
||||
|
||||
#[bitflags]
|
||||
#[repr(u8)]
|
||||
#[repr(u16)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ProfileModule {
|
||||
Segatools,
|
||||
Network,
|
||||
Display,
|
||||
BepInEx,
|
||||
Mu3Ini
|
||||
Mu3Ini,
|
||||
Keyboard,
|
||||
Mempatcher
|
||||
}
|
@ -1,298 +0,0 @@
|
||||
use super::misc::Game;
|
||||
|
||||
pub fn segatools_base(game: Game) -> String {
|
||||
match game {
|
||||
Game::Ongeki =>
|
||||
"[vfd]
|
||||
; Enable VFD emulation. Disable to use a real VFD
|
||||
; GP1232A02A FUTABA assembly.
|
||||
enable=1
|
||||
|
||||
[system]
|
||||
; Enable ALLS system settings.
|
||||
enable=1
|
||||
|
||||
; Enable freeplay mode. This will disable the coin slot and set the game to
|
||||
; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not
|
||||
; allow you to start a game in freeplay mode.
|
||||
freeplay=0
|
||||
|
||||
; LAN Install: Set this to 1 on all machines.
|
||||
dipsw1=1
|
||||
|
||||
[gfx]
|
||||
; Enables the graphics hook.
|
||||
enable=1
|
||||
|
||||
[led15093]
|
||||
; Enable emulation of the 15093-06 controlled lights, which handle the air tower
|
||||
; RGBs and the rear LED panel (billboard) on the cabinet.
|
||||
enable=1
|
||||
|
||||
[led]
|
||||
; Output billboard LED strip data to a named pipe called \"\\\\.\\pipe\\ongeki_led\"
|
||||
cabLedOutputPipe=1
|
||||
; Output billboard LED strip data to serial
|
||||
cabLedOutputSerial=0
|
||||
|
||||
; Output slider LED data to the named pipe
|
||||
controllerLedOutputPipe=1
|
||||
; Output slider LED data to the serial port
|
||||
controllerLedOutputSerial=0
|
||||
|
||||
[io4]
|
||||
; Test button virtual-key code. Default is the F1 key.
|
||||
test=0x70
|
||||
; Service button virtual-key code. Default is the F2 key.
|
||||
service=0x71
|
||||
; Keyboard button to increment coin counter. Default is the F3 key.
|
||||
coin=0x72
|
||||
|
||||
; Set \"1\" to enable mouse lever emulation, \"0\" to use XInput
|
||||
mouse=1
|
||||
|
||||
; XInput input bindings
|
||||
;
|
||||
; Left Stick Lever
|
||||
; Left Trigger Lever (move to the left)
|
||||
; Right Trigger Lever (move to the right)
|
||||
; Left Left red button
|
||||
; Up Left green button
|
||||
; Right Left blue button
|
||||
; Left Shoulder Left side button
|
||||
; Right Shoulder Right side button
|
||||
; X Right red button
|
||||
; Y Right green button
|
||||
; A Right blue button
|
||||
; Back Left menu button
|
||||
; Start Right menu button
|
||||
|
||||
; Keyboard input bindings
|
||||
left1=0x41 ; A
|
||||
left2=0x53 ; S
|
||||
left3=0x44 ; D
|
||||
|
||||
leftSide=0x01 ; Mouse Left
|
||||
rightSide=0x02 ; Mouse Right
|
||||
|
||||
right1=0x4A ; J
|
||||
right2=0x4B ; K
|
||||
right3=0x4C ; L
|
||||
|
||||
leftMenu=0x55 ; U
|
||||
rightMenu=0x4F ; O".to_owned(),
|
||||
Game::Chunithm => "
|
||||
[vfd]
|
||||
; Enable VFD emulation. Disable to use a real VFD
|
||||
; GP1232A02A FUTABA assembly.
|
||||
enable=1
|
||||
|
||||
[system]
|
||||
; Enable ALLS system settings.
|
||||
enable=1
|
||||
|
||||
; Enable freeplay mode. This will disable the coin slot and set the game to
|
||||
; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not
|
||||
; allow you to start a game in freeplay mode.
|
||||
freeplay=0
|
||||
|
||||
; LAN Install: If multiple machines are present on the same LAN then set
|
||||
; this to 1 on exactly one machine and set this to 0 on all others.
|
||||
dipsw1=1
|
||||
; Monitor type: 0 = 120FPS, 1 = 60FPS
|
||||
dipsw2=1
|
||||
; Cab type: 0 = SP, 1 = CVT. SP will enable VFD and eMoney. This setting will switch
|
||||
; the LED 837-15093-06 COM port and the AiMe reder hardware generation as well.
|
||||
dipsw3=1
|
||||
|
||||
; -----------------------------------------------------------------------------
|
||||
; Misc. hooks settings
|
||||
; -----------------------------------------------------------------------------
|
||||
|
||||
[gfx]
|
||||
; Enables the graphics hook.
|
||||
enable=1
|
||||
; Force the game to run windowed.
|
||||
windowed=1
|
||||
; Add a frame to the game window if running windowed.
|
||||
framed=0
|
||||
; Select the monitor to run the game on. (Fullscreen only, 0 =primary screen)
|
||||
monitor=0
|
||||
|
||||
; -----------------------------------------------------------------------------
|
||||
; LED settings
|
||||
; -----------------------------------------------------------------------------
|
||||
|
||||
[led15093]
|
||||
; Enable emulation of the 15093-06 controlled lights, which handle the air tower
|
||||
; RGBs and the rear LED panel (billboard) on the cabinet.
|
||||
enable=1
|
||||
|
||||
[led]
|
||||
; Output billboard LED strip data to a named pipe called \"\\\\.\\pipe\\chuni_led\"
|
||||
cabLedOutputPipe=1
|
||||
; Output billboard LED strip data to serial
|
||||
cabLedOutputSerial=0
|
||||
|
||||
; Output slider LED data to the named pipe
|
||||
controllerLedOutputPipe=1
|
||||
; Output slider LED data to the serial port
|
||||
controllerLedOutputSerial=0
|
||||
; Use the OpeNITHM protocol for serial LED output
|
||||
controllerLedOutputOpeNITHM=0
|
||||
|
||||
; Serial port to send data to if using serial output. Default is COM5.
|
||||
;serialPort=COM5
|
||||
; Baud rate for serial data (set to 115200 if using OpeNITHM)
|
||||
;serialBaud=921600
|
||||
|
||||
; Data output a sequence of bytes, with JVS-like framing.
|
||||
; Each \"packet\" starts with 0xE0 as a sync. To avoid E0 appearing elsewhere,
|
||||
; 0xD0 is used as an escape character -- if you receive D0 in the output, ignore
|
||||
; it and use the next sent byte plus one instead.
|
||||
;
|
||||
; After the sync is one byte for the board number that was updated, followed by
|
||||
; the red, green and blue values for each LED.
|
||||
;
|
||||
; Board 0 has 53 LEDs:
|
||||
; [0]-[49]: snakes through left half of billboard (first column starts at top)
|
||||
; [50]-[52]: left side partition LEDs
|
||||
;
|
||||
; Board 1 has 63 LEDs:
|
||||
; [0]-[59]: right half of billboard (first column starts at bottom)
|
||||
; [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=
|
||||
|
||||
; -----------------------------------------------------------------------------
|
||||
; Input settings
|
||||
; -----------------------------------------------------------------------------
|
||||
|
||||
; Keyboard bindings are specified as hexadecimal (prefixed with 0x) or decimal
|
||||
; (not prefixed with 0x) virtual-key codes, a list of which can be found here:
|
||||
;
|
||||
; https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||
;
|
||||
; This is, admittedly, not the most user-friendly configuration method in the
|
||||
; world. An improved solution will be provided later.
|
||||
|
||||
[io3]
|
||||
|
||||
test=0x31
|
||||
|
||||
service=0x32
|
||||
|
||||
coin=0x33
|
||||
|
||||
ir=0x00
|
||||
|
||||
ir6=0x39
|
||||
|
||||
ir5=0x38
|
||||
|
||||
ir4=0x37
|
||||
|
||||
ir3=0x36
|
||||
|
||||
ir2=0x35
|
||||
|
||||
ir1=0x34
|
||||
|
||||
[ir]
|
||||
|
||||
ir6=0x39
|
||||
|
||||
ir5=0x38
|
||||
|
||||
ir4=0x37
|
||||
|
||||
ir3=0x36
|
||||
|
||||
ir2=0x35
|
||||
|
||||
ir1=0x34
|
||||
|
||||
[slider]
|
||||
|
||||
cell32=0x51
|
||||
|
||||
cell30=0x5A
|
||||
|
||||
cell28=0x53
|
||||
|
||||
cell26=0x45
|
||||
|
||||
cell24=0x43
|
||||
|
||||
cell22=0x46
|
||||
|
||||
cell20=0x54
|
||||
|
||||
cell18=0x42
|
||||
|
||||
cell16=0x48
|
||||
|
||||
cell14=0x55
|
||||
|
||||
cell12=0x4D
|
||||
|
||||
cell10=0x4B
|
||||
|
||||
cell8=0x4F
|
||||
|
||||
cell6=190
|
||||
|
||||
cell4=186
|
||||
|
||||
cell2=219
|
||||
|
||||
cell31=0x41
|
||||
|
||||
cell29=0x57
|
||||
|
||||
cell27=0x58
|
||||
|
||||
cell25=0x44
|
||||
|
||||
cell23=0x52
|
||||
|
||||
cell21=0x56
|
||||
|
||||
cell19=0x47
|
||||
|
||||
cell17=0x59
|
||||
|
||||
cell15=0x4E
|
||||
|
||||
cell13=0x4A
|
||||
|
||||
cell11=0x49
|
||||
|
||||
cell9=188
|
||||
|
||||
cell7=0x4C
|
||||
|
||||
cell5=0x50
|
||||
|
||||
cell3=191
|
||||
|
||||
cell1=222
|
||||
".to_owned()
|
||||
}
|
||||
}
|
@ -1,20 +1,20 @@
|
||||
|
||||
use crate::model::profile::{Display, DisplayMode};
|
||||
use crate::{model::{misc::Game, profile::{Display, DisplayMode}}, util::bool_to_01};
|
||||
use anyhow::Result;
|
||||
use displayz::{query_displays, DisplaySet};
|
||||
use ini::Ini;
|
||||
use tauri::{AppHandle, Listener};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DisplayInfo {
|
||||
pub primary: String,
|
||||
pub set: Option<DisplaySet>
|
||||
pub set: Option<DisplaySet>,
|
||||
}
|
||||
|
||||
impl Default for DisplayInfo {
|
||||
fn default() -> Self {
|
||||
DisplayInfo {
|
||||
primary: "default".to_owned(),
|
||||
set: query_displays().ok()
|
||||
set: query_displays().ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -31,7 +31,7 @@ impl Display {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn line_up(&self) -> Result<Option<DisplayInfo>> {
|
||||
pub fn prepare(&self) -> Result<Option<DisplayInfo>> {
|
||||
use anyhow::anyhow;
|
||||
use displayz::{query_displays, Orientation, Resolution, Frequency};
|
||||
|
||||
@ -52,21 +52,35 @@ impl Display {
|
||||
.find(|display| display.name() == self.target)
|
||||
.ok_or_else(|| anyhow!("Display {} not found", self.target))?;
|
||||
|
||||
target.set_primary()?;
|
||||
if !self.dont_switch_primary {
|
||||
target.set_primary()?;
|
||||
}
|
||||
let settings = target.settings()
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("Unable to query display settings"))?;
|
||||
|
||||
let res = DisplayInfo {
|
||||
primary: primary.name().to_owned(),
|
||||
set: Some(display_set.clone())
|
||||
set: Some(display_set.clone()),
|
||||
};
|
||||
|
||||
if self.rotation == 90 || self.rotation == 270 {
|
||||
if let Some(rotation) = self.rotation {
|
||||
let rez = settings.borrow_mut().resolution;
|
||||
settings.borrow_mut().orientation = if self.rotation == 90 { Orientation::PortraitFlipped } else { Orientation::Portrait };
|
||||
settings.borrow_mut().orientation = match rotation {
|
||||
0 => Orientation::Landscape,
|
||||
90 => Orientation::PortraitFlipped,
|
||||
180 => Orientation::LandscapeFlipped,
|
||||
270 => Orientation::Portrait,
|
||||
_ => panic!("Invalid display rotation")
|
||||
};
|
||||
if rez.height < rez.width {
|
||||
settings.borrow_mut().resolution = Resolution::new(rez.height, rez.width);
|
||||
if rotation == 90 || rotation == 270 {
|
||||
settings.borrow_mut().resolution = Resolution::new(rez.height, rez.width);
|
||||
}
|
||||
} else {
|
||||
if rotation == 0 || rotation == 180 {
|
||||
settings.borrow_mut().resolution = Resolution::new(rez.height, rez.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +102,9 @@ impl Display {
|
||||
settings.borrow_mut().resolution = Resolution::new(width, height);
|
||||
}
|
||||
|
||||
display_set.apply()?;
|
||||
display_set.apply().map_err(
|
||||
|_| anyhow!("The selected monitor has been disconnected or doesn't support the chosen display mode")
|
||||
)?;
|
||||
displayz::refresh()?;
|
||||
|
||||
log::debug!("prepare display: done");
|
||||
@ -96,6 +112,20 @@ impl Display {
|
||||
Ok(Some(res))
|
||||
}
|
||||
|
||||
pub fn line_up(&self, game: Game, ini: &mut Ini) {
|
||||
if game == Game::Chunithm {
|
||||
let autism = self.monitor_index_override.unwrap_or(0).to_string();
|
||||
ini.with_section(Some("gfx"))
|
||||
.set("enable", "1")
|
||||
.set("windowed", bool_to_01(self.mode != DisplayMode::Fullscreen))
|
||||
.set("framed", bool_to_01(self.mode == DisplayMode::Window))
|
||||
.set("monitor", if self.dont_switch_primary { &autism } else { "0" });
|
||||
ini.with_section(Some("system"))
|
||||
.set("dipsw2", bool_to_01(self.frequency == 60))
|
||||
.set("dipsw3", bool_to_01(self.frequency == 60));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clean_up(info: &DisplayInfo) -> Result<()> {
|
||||
use anyhow::anyhow;
|
||||
|
||||
|
125
rust/src/modules/keyboard.rs
Normal file
125
rust/src/modules/keyboard.rs
Normal file
@ -0,0 +1,125 @@
|
||||
use ini::Ini;
|
||||
use anyhow::Result;
|
||||
use crate::model::profile::Keyboard;
|
||||
|
||||
macro_rules! parse_int_field {
|
||||
($section:expr,$sgt:expr,$sl:expr) => {
|
||||
if let Some(field) = $section.get($sgt) {
|
||||
let field = &field[0..field.chars().position(|c| c == ';').unwrap_or(field.len())].trim();
|
||||
log::debug!("loading {}={}", $sgt, field);
|
||||
|
||||
let res = if field.starts_with("0x") {
|
||||
i32::from_str_radix(&field.trim()[2..], 16)
|
||||
} else {
|
||||
field.trim().parse::<i32>()
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok(v) => $sl = v,
|
||||
Err(e) => log::warn!("unable to read a segatools.ini field key={} value={}: {:?}", $sgt, field.trim(), e)
|
||||
};
|
||||
} else {
|
||||
log::debug!("unable to load {}", $sgt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Keyboard {
|
||||
pub fn load_from_ini(&mut self, ini: &Ini) -> Result<()> {
|
||||
log::debug!("loading kb");
|
||||
match self {
|
||||
Keyboard::Ongeki(kb) => {
|
||||
if let Some(s) = ini.section(Some("io4")) {
|
||||
parse_int_field!(s, "test", kb.test);
|
||||
parse_int_field!(s, "service", kb.svc);
|
||||
parse_int_field!(s, "coin", kb.coin);
|
||||
parse_int_field!(s, "left1", kb.l1);
|
||||
parse_int_field!(s, "left2", kb.l2);
|
||||
parse_int_field!(s, "left3", kb.l3);
|
||||
parse_int_field!(s, "right1", kb.r1);
|
||||
parse_int_field!(s, "right2", kb.r2);
|
||||
parse_int_field!(s, "right3", kb.r3);
|
||||
parse_int_field!(s, "leftMenu", kb.lmenu);
|
||||
parse_int_field!(s, "rightMenu", kb.rmenu);
|
||||
parse_int_field!(s, "leftSide", kb.lwad);
|
||||
parse_int_field!(s, "rightSide", kb.rwad);
|
||||
|
||||
let mut mouse: i32 = 1;
|
||||
parse_int_field!(s, "mouse", mouse);
|
||||
kb.use_mouse = if mouse == 1 { true } else { false };
|
||||
}
|
||||
}
|
||||
Keyboard::Chunithm(kb) => {
|
||||
if let Some(s) = ini.section(Some("io3")) {
|
||||
parse_int_field!(s, "test", kb.test);
|
||||
parse_int_field!(s, "service", kb.svc);
|
||||
parse_int_field!(s, "coin", kb.coin);
|
||||
}
|
||||
|
||||
if let Some(s) = ini.section(Some("slider")) {
|
||||
for i in 0..kb.cell.len() {
|
||||
parse_int_field!(s, format!("cell{}", i + 1), kb.cell[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(s) = ini.section(Some("ir")) {
|
||||
for i in 0..kb.ir.len() {
|
||||
parse_int_field!(s, format!("ir{}", i + 1), kb.ir[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This is assumed to run in sync after the segatools module
|
||||
pub fn line_up(&self, ini: &mut Ini) -> Result<()> {
|
||||
match self {
|
||||
Keyboard::Ongeki(kb) => {
|
||||
if kb.enabled {
|
||||
ini.with_section(Some("io4"))
|
||||
.set("test", kb.test.to_string())
|
||||
.set("service", kb.svc.to_string())
|
||||
.set("coin", kb.coin.to_string())
|
||||
.set("left1", kb.l1.to_string())
|
||||
.set("left2", kb.l2.to_string())
|
||||
.set("left3", kb.l3.to_string())
|
||||
.set("right1", kb.r1.to_string())
|
||||
.set("right2", kb.r2.to_string())
|
||||
.set("right3", kb.r3.to_string())
|
||||
.set("leftSide", kb.lwad.to_string())
|
||||
.set("rightSide", kb.rwad.to_string())
|
||||
.set("leftMenu", kb.lmenu.to_string())
|
||||
.set("rightMenu", kb.rmenu.to_string())
|
||||
.set("mouse", if kb.use_mouse { "1" } else { "0" });
|
||||
} else {
|
||||
ini.with_section(Some("io4"))
|
||||
.set("enable", "0");
|
||||
}
|
||||
}
|
||||
Keyboard::Chunithm(kb) => {
|
||||
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("io3"))
|
||||
.set("test", kb.test.to_string())
|
||||
.set("service", kb.svc.to_string())
|
||||
.set("coin", kb.coin.to_string())
|
||||
.set("ir", "0");
|
||||
} else {
|
||||
ini.with_section(Some("io4"))
|
||||
.set("enable", "0");
|
||||
ini.with_section(Some("slider"))
|
||||
.set("enable", "0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
59
rust/src/modules/mempatcher.rs
Normal file
59
rust/src/modules/mempatcher.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use std::path::Path;
|
||||
use anyhow::Result;
|
||||
use crate::model::patch::{Patch, PatchData, PatchFileVec, PatchSelection, PatchSelectionData};
|
||||
|
||||
impl PatchSelection {
|
||||
pub async fn render_to_file(
|
||||
&self,
|
||||
filename: &str,
|
||||
patches: &PatchFileVec,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokio::fs::write(path, res).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(filename: &str, patch: &Patch, sel: &PatchSelectionData) -> String {
|
||||
let mut res = "".to_owned();
|
||||
match &patch.data {
|
||||
PatchData::Normal(data) => {
|
||||
for p in &data.patches {
|
||||
res += &format!("{} F+{:X} ", filename, p.offset);
|
||||
for on in &p.on {
|
||||
res += &format!("{:02X}", on);
|
||||
}
|
||||
res += " ";
|
||||
for off in &p.off {
|
||||
res += &format!("{:02X}", off);
|
||||
}
|
||||
res += "\n";
|
||||
}
|
||||
},
|
||||
PatchData::Number(data) => {
|
||||
if let PatchSelectionData::Number(val) = sel {
|
||||
let width = (data.size as usize) * 2usize;
|
||||
res += &format!("{} F+{:X} {:0width$X} {:0width$X}", filename, data.offset, val, data.default, width = width);
|
||||
} else {
|
||||
log::error!("invalid number patch {:?}", patch);
|
||||
}
|
||||
}
|
||||
}
|
||||
format!("{}\n", res)
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@ pub mod segatools;
|
||||
pub mod network;
|
||||
pub mod bepinex;
|
||||
pub mod mu3ini;
|
||||
pub mod keyboard;
|
||||
pub mod mempatcher;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod display_windows;
|
@ -5,6 +5,32 @@ use ini::Ini;
|
||||
use crate::model::profile::{Network, NetworkType};
|
||||
|
||||
impl Network {
|
||||
pub fn load_from_ini(&mut self, ini: &Ini) -> Result<()> {
|
||||
log::debug!("loading network");
|
||||
if let Some(s) = ini.section(Some("dns")) {
|
||||
if let Some(default) = s.get("default") {
|
||||
if default.starts_with("192.") || default.starts_with("127.") {
|
||||
self.network_type = NetworkType::Artemis;
|
||||
} else {
|
||||
self.network_type = NetworkType::Remote;
|
||||
self.remote_address = default.to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(s) = ini.section(Some("netenv")) {
|
||||
s.get("addrSuffix").map(|v|
|
||||
self.suffix = v.parse::<i32>().ok()
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(s) = ini.section(Some("keychip")) {
|
||||
s.get("subnet").map(|v| self.subnet = v.to_owned());
|
||||
s.get("id").map(|v| self.keychip = v.to_owned());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn line_up(&self, ini: &mut Ini) -> Result<()> {
|
||||
log::debug!("begin line-up: network");
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use std::collections::BTreeSet;
|
||||
use crate::pkg::PkgKey;
|
||||
use std::path::PathBuf;
|
||||
use crate::pkg::{PkgKey, Status};
|
||||
use crate::pkg_store::PackageStore;
|
||||
use crate::util;
|
||||
use crate::profiles::ProfilePaths;
|
||||
use crate::profiles::types::ProfilePaths;
|
||||
|
||||
pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgKey>, redo_bepinex: bool) -> Result<()> {
|
||||
log::debug!("begin prepare packages");
|
||||
@ -22,10 +24,10 @@ pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgK
|
||||
|
||||
for m in pkgs {
|
||||
log::debug!("preparing {}", m);
|
||||
let (namespace, name) = m.0.split_at(m.0.find("-").expect("Invalid mod definition"));
|
||||
let (namespace, name) = m.split()?;
|
||||
|
||||
if redo_bepinex {
|
||||
let bpx_dir = util::pkg_dir_of(namespace, &name[1..]) // cut the hyphen
|
||||
let bpx_dir = util::pkg_dir_of(&namespace, &name)
|
||||
.join("app")
|
||||
.join("BepInEx");
|
||||
if bpx_dir.exists() {
|
||||
@ -33,7 +35,7 @@ pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgK
|
||||
}
|
||||
}
|
||||
|
||||
let opt_dir = util::pkg_dir_of(namespace, &name[1..]).join("option");
|
||||
let opt_dir = util::pkg_dir_of(&namespace, &name).join("option");
|
||||
if opt_dir.exists() {
|
||||
let x = opt_dir.read_dir().unwrap().next().unwrap()?;
|
||||
if x.metadata()?.is_dir() {
|
||||
@ -45,4 +47,27 @@ pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgK
|
||||
log::debug!("end prepare packages");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn prepare_dlls(
|
||||
enabled_pkgs: &BTreeSet<PkgKey>,
|
||||
store: &PackageStore,
|
||||
) -> Result<(Vec<PathBuf>, Vec<PathBuf>)> {
|
||||
let mut res_game = Vec::new();
|
||||
let mut res_amd = Vec::new();
|
||||
for pkg in enabled_pkgs {
|
||||
if let Ok(pkg) = store.get(&pkg) {
|
||||
if let Some(loc) = &pkg.loc {
|
||||
if let Status::OK(_, dlls) = &loc.status {
|
||||
if let Some(game_dll) = &dlls.game {
|
||||
res_game.push(pkg.path().join(game_dll.clone()));
|
||||
}
|
||||
if let Some(amd_dll) = &dlls.amd {
|
||||
res_amd.push(pkg.path().join(amd_dll.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((res_game, res_amd))
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use std::path::{PathBuf, Path};
|
||||
use anyhow::{anyhow, Result};
|
||||
use ini::Ini;
|
||||
use crate::{model::{misc::Game, profile::{Aime, Segatools}, segatools_base::segatools_base}, profiles::ProfilePaths, util::{self, PathStr}};
|
||||
use crate::{model::{misc::{ConfigHook, ConfigHookAime, ConfigHookAimeUnit, ConfigHookAuth, Game}, profile::{Aime, Segatools}}, profiles::ProfilePaths, util::{self, PathStr}};
|
||||
use crate::pkg_store::PackageStore;
|
||||
|
||||
impl Segatools {
|
||||
@ -31,6 +30,31 @@ impl Segatools {
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
pub fn load_from_ini(&mut self, ini: &Ini, config_dir: impl AsRef<Path>) -> Result<()> {
|
||||
log::debug!("loading sgt");
|
||||
if let Some(s) = ini.section(Some("vfs")) {
|
||||
s.get("amfs").map(|v| self.amfs = PathBuf::from(v));
|
||||
s.get("appdata").map(|v| self.appdata = PathBuf::from(v));
|
||||
s.get("option").map(|v| self.option = PathBuf::from(v));
|
||||
}
|
||||
|
||||
if let Some(s) = ini.section(Some("aime")) {
|
||||
if s.get("enable").unwrap_or("0") == "1" {
|
||||
if let Some(aime_path) = s.get("aimePath") {
|
||||
if let Some(game_dir) = self.target.parent() {
|
||||
let target = game_dir.join(aime_path);
|
||||
std::fs::copy(target, config_dir.as_ref().join("aime.txt"))?;
|
||||
} else {
|
||||
log::error!("profile doesn't have a game directory");
|
||||
}
|
||||
} else {
|
||||
log::warn!("aime emulation is enabled, but no aimePath specified");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub async fn line_up(&self, p: &impl ProfilePaths, game: Game) -> Result<Ini> {
|
||||
log::debug!("begin line-up: segatools");
|
||||
|
||||
@ -42,8 +66,12 @@ impl Segatools {
|
||||
let ini_path = p.config_dir().join("segatools-base.ini");
|
||||
|
||||
if !ini_path.exists() {
|
||||
tokio::fs::write(&ini_path, segatools_base(game)).await
|
||||
.map_err(|e| anyhow!("Error creating {:?}: {}", ini_path, e))?;
|
||||
match game {
|
||||
Game::Ongeki => tokio::fs::write(&ini_path, include_bytes!("../../static/segatools-ongeki.ini"))
|
||||
.await.map_err(|e| anyhow!("Error creating {:?}: {}", ini_path, e))?,
|
||||
Game::Chunithm => tokio::fs::write(&ini_path, include_bytes!("../../static/segatools-chunithm.ini"))
|
||||
.await.map_err(|e| anyhow!("Error creating {:?}: {}", ini_path, e))?
|
||||
}
|
||||
}
|
||||
if !pfx_dir.exists() {
|
||||
tokio::fs::create_dir(&pfx_dir).await
|
||||
@ -143,6 +171,31 @@ impl Segatools {
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut cfg_hook = ConfigHook::default();
|
||||
|
||||
if game == Game::Chunithm {
|
||||
cfg_hook.allnet_auth = Some({
|
||||
ConfigHookAuth {
|
||||
r#type: "1.0".to_owned()
|
||||
}
|
||||
})
|
||||
}
|
||||
if let Some(port) = self.aime_port {
|
||||
if self.aime == Aime::Disabled {
|
||||
cfg_hook.aime = Some({
|
||||
ConfigHookAime {
|
||||
unit: vec![
|
||||
ConfigHookAimeUnit {
|
||||
port,
|
||||
id: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
std::fs::write(pfx_dir.join("config_hook.json"), serde_json::to_string(&cfg_hook)?)?;
|
||||
|
||||
log::debug!("end line-up: segatools");
|
||||
|
||||
Ok(ini_out)
|
||||
|
45
rust/src/patcher.rs
Normal file
45
rust/src/patcher.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use sha256::try_digest;
|
||||
|
||||
use crate::model::patch::{Patch, PatchFile, PatchFileVec};
|
||||
|
||||
impl PatchFileVec {
|
||||
pub fn new(config_path: impl AsRef<Path>) -> Result<PatchFileVec> {
|
||||
let path = config_path.as_ref().join("patches");
|
||||
if !path.exists() {
|
||||
std::fs::create_dir(&path)?;
|
||||
}
|
||||
std::fs::write(path.join("builtin-chunithm.json5"), include_bytes!("../static/standard-chunithm.json5"))?;
|
||||
let mut res = Vec::new();
|
||||
for f in std::fs::read_dir(path)? {
|
||||
let f = f?;
|
||||
let f = f.path();
|
||||
res.push(
|
||||
serde_json5::from_str::<PatchFile>(&std::fs::read_to_string(f)?)?
|
||||
);
|
||||
}
|
||||
Ok(PatchFileVec(res))
|
||||
}
|
||||
|
||||
pub fn find_patches(&self, target: impl AsRef<Path>) -> Result<Vec<Patch>> {
|
||||
let checksum = try_digest(target.as_ref())?;
|
||||
|
||||
let mut res = Vec::new();
|
||||
for pfile in &self.0 {
|
||||
for plist in &pfile.0 {
|
||||
log::debug!("checking {}", plist.sha256);
|
||||
if plist.sha256 == checksum {
|
||||
let mut cloned = plist.clone().patches;
|
||||
res.append(&mut cloned);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if res.len() == 0 {
|
||||
log::warn!("no matching patchset for {:?} ({})", target.as_ref(), checksum);
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
125
rust/src/pkg.rs
125
rust/src/pkg.rs
@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::{collections::BTreeSet, path::{Path, PathBuf}};
|
||||
use tokio::fs;
|
||||
use enumflags2::{bitflags, make_bitflags, BitFlags};
|
||||
use crate::{model::{local::{self, PackageManifest}, rainy}, util};
|
||||
use crate::{model::{local::{self, PackageManifest}, misc::Game, rainy}, util};
|
||||
|
||||
// {namespace}-{name}
|
||||
#[derive(Eq, Hash, PartialEq, PartialOrd, Ord, Clone, Serialize, Deserialize, Display, Debug)]
|
||||
@ -14,25 +14,38 @@ pub struct PkgKey(pub String);
|
||||
#[derive(Eq, Hash, PartialEq, PartialOrd, Ord, Clone, Serialize, Deserialize, Display, Debug)]
|
||||
pub struct PkgKeyVersion(String);
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, Display, Debug, Serialize, Deserialize, Default)]
|
||||
pub enum PackageSource {
|
||||
#[default] Rainy,
|
||||
Local(Game)
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Package {
|
||||
pub namespace: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub loc: Option<Local>,
|
||||
pub rmt: Option<Remote>
|
||||
pub rmt: Option<Remote>,
|
||||
pub source: PackageSource,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
|
||||
pub enum Status {
|
||||
Unchecked,
|
||||
Unsupported,
|
||||
OK(BitFlags<Feature>)
|
||||
OK(BitFlags<Feature>, DLLs),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
|
||||
pub struct DLLs {
|
||||
pub game: Option<String>,
|
||||
pub amd: Option<String>
|
||||
}
|
||||
|
||||
#[bitflags]
|
||||
#[repr(u8)]
|
||||
#[repr(u16)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Feature {
|
||||
Mod,
|
||||
@ -41,9 +54,13 @@ pub enum Feature {
|
||||
Mu3Hook,
|
||||
Mu3IO,
|
||||
ChusanHook,
|
||||
ChuniIO,
|
||||
Mempatcher,
|
||||
GameDLL,
|
||||
AmdDLL
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Local {
|
||||
pub version: String,
|
||||
@ -53,7 +70,7 @@ pub struct Local {
|
||||
pub icon: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Remote {
|
||||
pub version: String,
|
||||
@ -66,6 +83,14 @@ pub struct Remote {
|
||||
pub dependencies: BTreeSet<PkgKey>,
|
||||
}
|
||||
|
||||
impl PkgKey {
|
||||
pub fn split(&self) -> Result<(String, String)> {
|
||||
let (namespace, name) = self.0
|
||||
.split_at(self.0.find("-").ok_or_else(|| anyhow!("Invalid package key"))?);
|
||||
Ok((namespace.to_owned(), name[1..].to_owned())) // cut the hyphen
|
||||
}
|
||||
}
|
||||
|
||||
impl Package {
|
||||
pub fn from_rainy(mut p: rainy::V1Package) -> Option<Package> {
|
||||
if p.versions.len() == 0 {
|
||||
@ -88,11 +113,12 @@ impl Package {
|
||||
version: v.version_number,
|
||||
categories: p.categories,
|
||||
dependencies: Self::sanitize_deps(v.dependencies)
|
||||
})
|
||||
}),
|
||||
source: PackageSource::Rainy,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn from_dir(dir: PathBuf) -> Result<Package> {
|
||||
pub async fn from_dir(dir: PathBuf, source: PackageSource) -> Result<Package> {
|
||||
let str = fs::read_to_string(dir.join("manifest.json")).await?;
|
||||
let mft: local::PackageManifest = serde_json::from_str(&str)?;
|
||||
|
||||
@ -116,7 +142,8 @@ impl Package {
|
||||
status,
|
||||
dependencies
|
||||
}),
|
||||
rmt: None
|
||||
rmt: None,
|
||||
source
|
||||
})
|
||||
}
|
||||
|
||||
@ -125,7 +152,15 @@ impl Package {
|
||||
}
|
||||
|
||||
pub fn path(&self) -> PathBuf {
|
||||
util::pkg_dir().join(self.key().0)
|
||||
match self.source {
|
||||
PackageSource::Rainy => util::pkg_dir().join(self.key().0),
|
||||
PackageSource::Local(game) =>
|
||||
util::pkg_dir()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(format!("pkg-{game}"))
|
||||
.join(&self.name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn _dir_to_key(dir: &Path) -> Result<String> {
|
||||
@ -188,31 +223,55 @@ impl Package {
|
||||
|
||||
fn parse_status(mft: &PackageManifest) -> Status {
|
||||
if mft.installers.len() == 0 {
|
||||
return Status::OK(make_bitflags!(Feature::Mod));//Unchecked
|
||||
} else if mft.installers.len() == 1 {
|
||||
if let Some(serde_json::Value::String(id)) = &mft.installers[0].get("identifier") {
|
||||
if id == "rainycolor" {
|
||||
return Status::OK(make_bitflags!(Feature::Mod));
|
||||
} else if id == "segatools" {
|
||||
// Multiple features in the same dll (yubideck etc.) should be supported at some point
|
||||
let mut flags = BitFlags::default();
|
||||
if let Some(serde_json::Value::String(module)) = mft.installers[0].get("module") {
|
||||
if module == "mu3hook" {
|
||||
flags |= Feature::Mu3Hook;
|
||||
} else if module == "chusanhook" {
|
||||
flags |= Feature::ChusanHook;
|
||||
} else if module == "amnet" {
|
||||
flags |= Feature::AMNet | Feature::Aime;
|
||||
} else if module == "aimeio" {
|
||||
flags |= Feature::Aime;
|
||||
} else if module == "mu3io" {
|
||||
flags |= Feature::Mu3IO;
|
||||
return Status::OK(make_bitflags!(Feature::Mod), DLLs { game: None, amd: None }); //Unchecked
|
||||
} else {
|
||||
let mut flags = BitFlags::default();
|
||||
let mut game_dll = None;
|
||||
let mut amd_dll = None;
|
||||
for installer in &mft.installers {
|
||||
if let Some(serde_json::Value::String(id)) = installer.get("identifier") {
|
||||
if id == "rainycolor" {
|
||||
flags |= Feature::Mod;
|
||||
} else if id == "segatools" {
|
||||
// Multiple features in the same dll (yubideck etc.) should be supported at some point
|
||||
if let Some(serde_json::Value::String(module)) = installer.get("module") {
|
||||
if module == "mu3hook" {
|
||||
flags |= Feature::Mu3Hook;
|
||||
} else if module == "chusanhook" {
|
||||
flags |= Feature::ChusanHook;
|
||||
} else if module == "amnet" {
|
||||
flags |= Feature::AMNet | Feature::Aime;
|
||||
} else if module == "aimeio" {
|
||||
flags |= Feature::Aime;
|
||||
} else if module == "mu3io" {
|
||||
flags |= Feature::Mu3IO;
|
||||
} else if module == "chuniio" {
|
||||
flags |= Feature::ChuniIO;
|
||||
}
|
||||
}
|
||||
} else if id == "native_mod" {
|
||||
if let Some(serde_json::Value::String(path)) = installer.get("dll-game") {
|
||||
flags |= Feature::GameDLL;
|
||||
flags |= Feature::Mod;
|
||||
game_dll = Some(path.to_owned());
|
||||
}
|
||||
if let Some(serde_json::Value::String(path)) = installer.get("dll_game") {
|
||||
flags |= Feature::GameDLL;
|
||||
flags |= Feature::Mod;
|
||||
game_dll = Some(path.to_owned());
|
||||
}
|
||||
if let Some(serde_json::Value::String(path)) = installer.get("dll-amdaemon") {
|
||||
flags |= Feature::AmdDLL;
|
||||
flags |= Feature::Mod;
|
||||
amd_dll = Some(path.to_owned());
|
||||
}
|
||||
} else {
|
||||
return Status::Unsupported;
|
||||
}
|
||||
return Status::OK(flags);
|
||||
}
|
||||
}
|
||||
log::debug!("{} parse result: {:?} {:?} {:?}", mft.name, flags, game_dll, amd_dll);
|
||||
Status::OK(flags, DLLs { game: game_dll, amd: amd_dll })
|
||||
}
|
||||
Status::Unsupported
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ use tokio::task::JoinSet;
|
||||
use crate::model::local::{PackageList, PackageListEntry};
|
||||
use crate::model::misc::Game;
|
||||
use crate::model::rainy;
|
||||
use crate::pkg::{Package, PkgKey, Remote, Status};
|
||||
use crate::pkg::{Feature, Package, PackageSource, PkgKey, Remote, Status};
|
||||
use crate::util;
|
||||
use crate::download_handler::DownloadHandler;
|
||||
|
||||
@ -67,9 +67,24 @@ impl PackageStore {
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_by_feature(&self, feature: Feature) -> Vec<PkgKey> {
|
||||
self.store.iter()
|
||||
.filter(|(_, v)| {
|
||||
if let Some(loc) = &v.loc {
|
||||
if let Status::OK(flags, _) = loc.status {
|
||||
return flags.contains(feature);
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
.map(|(k, _)| k.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub async fn reload_package(&mut self, key: PkgKey) {
|
||||
let dir = util::pkg_dir().join(&key.0);
|
||||
if let Ok(pkg) = Package::from_dir(dir).await {
|
||||
if let Ok(pkg) = Package::from_dir(dir, PackageSource::Rainy).await {
|
||||
self.update_nonremote(key, pkg);
|
||||
} else {
|
||||
log::error!("couldn't reload {}", key);
|
||||
@ -83,7 +98,7 @@ impl PackageStore {
|
||||
for dir in dirents {
|
||||
if let Ok(dir) = dir {
|
||||
let path = dir.path();
|
||||
futures.spawn(Package::from_dir(path));
|
||||
futures.spawn(Package::from_dir(path, PackageSource::Rainy));
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,6 +299,10 @@ impl PackageStore {
|
||||
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
|
||||
|
@ -1,61 +1,16 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::AppHandle;
|
||||
use std::{collections::BTreeSet, path::{Path, PathBuf}};
|
||||
use crate::{model::{misc::Game, profile::{Aime, Mu3Ini, ProfileModule}}, modules::package::prepare_packages, pkg::PkgKey, pkg_store::PackageStore, util};
|
||||
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, Keyboard, Mu3Ini, OngekiKeyboard, ProfileModule}}, modules::{display_windows::DisplayInfo, package::prepare_packages}, pkg::PkgKey, pkg_store::PackageStore, util};
|
||||
use tauri::Emitter;
|
||||
use std::process::Stdio;
|
||||
use crate::model::profile::BepInEx;
|
||||
use crate::model::{profile::{Display, DisplayMode, Network, Segatools}, segatools_base::segatools_base};
|
||||
use crate::model::profile::{Display, DisplayMode, Network, Segatools};
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::fs::File;
|
||||
use tokio::process::Command;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
pub trait ProfilePaths {
|
||||
fn config_dir(&self) -> PathBuf;
|
||||
fn data_dir(&self) -> PathBuf;
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
|
||||
pub struct ProfileMeta {
|
||||
pub game: Game,
|
||||
pub name: String
|
||||
}
|
||||
|
||||
impl ProfilePaths for ProfileMeta {
|
||||
fn config_dir(&self) -> PathBuf {
|
||||
util::profile_config_dir(self.game, &self.name)
|
||||
}
|
||||
|
||||
fn data_dir(&self) -> PathBuf {
|
||||
util::data_dir().join(format!("profile-{}-{}", &self.game, &self.name))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct Profile {
|
||||
pub meta: ProfileMeta,
|
||||
pub data: ProfileData,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct ProfileData {
|
||||
pub mods: BTreeSet<PkgKey>,
|
||||
pub sgt: Segatools,
|
||||
pub network: Network,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub display: Option<Display>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bepinex: Option<BepInEx>,
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub wine: crate::model::profile::Wine,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mu3_ini: Option<Mu3Ini>
|
||||
}
|
||||
pub mod types;
|
||||
|
||||
impl Profile {
|
||||
pub fn new(mut meta: ProfileMeta) -> Result<Self> {
|
||||
@ -66,7 +21,7 @@ impl Profile {
|
||||
mods: BTreeSet::new(),
|
||||
sgt: Segatools::default_for(meta.game),
|
||||
#[cfg(target_os = "windows")]
|
||||
display: if meta.game == Game::Ongeki { Some(Display::default_for(meta.game)) } else { None },
|
||||
display: Some(Display::default_for(meta.game)),
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
display: None,
|
||||
network: Network::default(),
|
||||
@ -74,24 +29,51 @@ impl Profile {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
wine: crate::model::profile::Wine::default(),
|
||||
mu3_ini: if meta.game == Game::Ongeki { Some(Mu3Ini { audio: None, blacklist: None }) } else { None },
|
||||
keyboard:
|
||||
if meta.game == Game::Ongeki {
|
||||
Some(Keyboard::Ongeki(OngekiKeyboard::default()))
|
||||
} else {
|
||||
Some(Keyboard::Chunithm(ChunithmKeyboard::default()))
|
||||
},
|
||||
patches: if meta.game == Game::Chunithm { Some(PatchSelection(BTreeMap::new())) } else { None }
|
||||
},
|
||||
meta: meta.clone()
|
||||
};
|
||||
p.save()?;
|
||||
std::fs::create_dir_all(p.config_dir())?;
|
||||
std::fs::create_dir_all(p.data_dir())?;
|
||||
std::fs::write(p.config_dir().join("segatools-base.ini"), segatools_base(meta.game))?;
|
||||
|
||||
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"))?,
|
||||
};
|
||||
|
||||
Ok(p)
|
||||
}
|
||||
pub fn load(game: Game, name: String) -> Result<Self> {
|
||||
let path = util::profile_config_dir(game, &name).join("profile.json");
|
||||
if let Ok(s) = std::fs::read_to_string(&path) {
|
||||
let data = serde_json::from_str::<ProfileData>(&s)
|
||||
let mut data = serde_json::from_str::<ProfileData>(&s)
|
||||
.map_err(|e| anyhow!("Unable to parse {:?}: {:?}", path, e))?;
|
||||
|
||||
log::debug!("{:?}", data);
|
||||
|
||||
// Backwards compat
|
||||
if game == Game::Ongeki && data.keyboard.is_none() {
|
||||
data.keyboard = Some(Keyboard::Ongeki(OngekiKeyboard::default()));
|
||||
}
|
||||
if game == Game::Chunithm {
|
||||
if data.keyboard.is_none() {
|
||||
data.keyboard = Some(Keyboard::Chunithm(ChunithmKeyboard::default()));
|
||||
}
|
||||
if data.patches.is_none() {
|
||||
data.patches = Some(PatchSelection(BTreeMap::new()));
|
||||
}
|
||||
if data.display.is_none() {
|
||||
data.display = Some(Display::default_for(Game::Chunithm));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Profile {
|
||||
meta: ProfileMeta {
|
||||
game, name
|
||||
@ -112,7 +94,7 @@ impl Profile {
|
||||
}
|
||||
std::fs::write(&path, s)
|
||||
.map_err(|e| anyhow!("error when writing to {:?}: {}", path, e))?;
|
||||
log::info!("Written to {:?}", path);
|
||||
log::info!("profile written to {:?}", path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -163,28 +145,24 @@ impl Profile {
|
||||
if self.meta.game.has_module(ProfileModule::Mu3Ini) && source.mu3_ini.is_some() {
|
||||
self.data.mu3_ini = source.mu3_ini;
|
||||
}
|
||||
}
|
||||
pub async fn line_up(&self, pkg_hash: String, refresh: bool, _app: AppHandle) -> Result<()> {
|
||||
let info = match &self.data.display {
|
||||
None => None,
|
||||
Some(display) => display.line_up()?
|
||||
};
|
||||
|
||||
let res = self.line_up_the_rest(pkg_hash, refresh).await;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Some(info) = info {
|
||||
use crate::model::profile::Display;
|
||||
if res.is_ok() {
|
||||
Display::wait_for_exit(_app, info);
|
||||
} else {
|
||||
Display::clean_up(&info)?;
|
||||
}
|
||||
if self.meta.game.has_module(ProfileModule::Keyboard) && source.keyboard.is_some() {
|
||||
self.data.keyboard = source.keyboard;
|
||||
}
|
||||
|
||||
res
|
||||
if self.data.patches.is_some() && source.patches.is_some() {
|
||||
self.data.patches = source.patches;
|
||||
}
|
||||
}
|
||||
async fn line_up_the_rest(&self, pkg_hash: String, refresh: bool) -> Result<()> {
|
||||
pub fn prepare_display(&self) -> Result<Option<DisplayInfo>> {
|
||||
let info = match &self.data.display {
|
||||
None => None,
|
||||
Some(display) => display.prepare()?
|
||||
};
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
pub async fn line_up(&self, pkg_hash: String, refresh: bool, patch_files: &PatchFileVec) -> Result<()> {
|
||||
if !self.data_dir().exists() {
|
||||
tokio::fs::create_dir(self.data_dir()).await?;
|
||||
}
|
||||
@ -194,12 +172,23 @@ impl Profile {
|
||||
util::clean_up_opts(self.data_dir().join("option"))?;
|
||||
|
||||
let hash_check = Self::hash_check(&hash_path, &pkg_hash).await? || refresh;
|
||||
|
||||
prepare_packages(&self.meta, &self.data.mods, hash_check).await
|
||||
.map_err(|e| anyhow!("package configuration failed:\n{:?}", e))?;
|
||||
|
||||
let mut ini = self.data.sgt.line_up(&self.meta, self.meta.game).await
|
||||
.map_err(|e| anyhow!("segatools configuration failed:\n{:?}", e))?;
|
||||
|
||||
self.data.network.line_up(&mut ini)?;
|
||||
|
||||
if let Some(display) = &self.data.display {
|
||||
display.line_up(self.meta.game, &mut ini);
|
||||
}
|
||||
|
||||
if let Some(keyboard) = &self.data.keyboard {
|
||||
keyboard.line_up(&mut ini)?;
|
||||
}
|
||||
|
||||
ini.write_to_file(self.data_dir().join("segatools.ini"))
|
||||
.map_err(|e| anyhow!("Error writing segatools.ini: {}", e))?;
|
||||
|
||||
@ -211,10 +200,18 @@ impl Profile {
|
||||
mu3ini.line_up(&self.data.sgt.target.parent().unwrap())?;
|
||||
}
|
||||
|
||||
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"))
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start(&self, app: AppHandle) -> Result<()> {
|
||||
pub async fn start(&self, payload: StartPayload) -> Result<()> {
|
||||
let ini_path = self.data_dir().join("segatools.ini");
|
||||
|
||||
log::debug!("With path {:?}", ini_path);
|
||||
@ -245,12 +242,24 @@ impl Profile {
|
||||
&ini_path,
|
||||
)
|
||||
.current_dir(&exe_dir)
|
||||
.arg("/C")
|
||||
.raw_arg("/C")
|
||||
.arg(&sgt_dir.join(self.meta.game.inject_amd()))
|
||||
.args(["-d", "-k"])
|
||||
.raw_arg("-d");
|
||||
|
||||
|
||||
for dll in payload.amd_dlls {
|
||||
amd_builder.raw_arg("-k");
|
||||
amd_builder.arg(dll);
|
||||
}
|
||||
|
||||
amd_builder
|
||||
.raw_arg("-k")
|
||||
.arg(sgt_dir.join(self.meta.game.hook_amd()))
|
||||
.arg("amdaemon.exe")
|
||||
.args(self.meta.game.amd_args());
|
||||
|
||||
amd_builder.arg(self.data_dir().join("config_hook.json"));
|
||||
|
||||
game_builder
|
||||
.env(
|
||||
"SEGATOOLS_CONFIG_PATH",
|
||||
@ -260,23 +269,50 @@ impl Profile {
|
||||
"INOHARA_CONFIG_PATH",
|
||||
self.config_dir().join("inohara.cfg"),
|
||||
)
|
||||
.env(
|
||||
"SAEKAWA_CONFIG_PATH",
|
||||
self.config_dir().join("saekawa.toml"),
|
||||
)
|
||||
.current_dir(&exe_dir)
|
||||
.args(["-d", "-k"])
|
||||
.arg(sgt_dir.join(self.meta.game.hook_exe()))
|
||||
.arg(self.meta.game.exe());
|
||||
.raw_arg("-d")
|
||||
.raw_arg("-k")
|
||||
.arg(sgt_dir.join(self.meta.game.hook_exe()));
|
||||
|
||||
if let Some(display) = &self.data.display {
|
||||
game_builder.args([
|
||||
"-monitor 1",
|
||||
"-screen-width", &display.rez.0.to_string(),
|
||||
"-screen-height", &display.rez.1.to_string(),
|
||||
"-screen-fullscreen", if display.mode == DisplayMode::Fullscreen { "1" } else { "0" }
|
||||
]);
|
||||
if display.mode == DisplayMode::Borderless {
|
||||
game_builder.arg("-popupwindow");
|
||||
for dll in payload.game_dlls {
|
||||
game_builder.raw_arg("-k");
|
||||
game_builder.arg(dll);
|
||||
}
|
||||
|
||||
game_builder.arg(self.meta.game.exe());
|
||||
|
||||
if self.meta.game.has_module(ProfileModule::BepInEx) {
|
||||
if let Some(display) = &self.data.display {
|
||||
if display.dont_switch_primary && display.target != "default" {
|
||||
game_builder.args(["-monitor", &display.monitor_index_override.unwrap_or_else(|| 1).to_string()]);
|
||||
} else {
|
||||
game_builder.args(["-monitor", "1"]);
|
||||
}
|
||||
game_builder.args([
|
||||
"-screen-width", &display.rez.0.to_string(),
|
||||
"-screen-height", &display.rez.1.to_string(),
|
||||
"-screen-fullscreen", if display.mode == DisplayMode::Fullscreen { "1" } else { "0" }
|
||||
]);
|
||||
if display.mode == DisplayMode::Borderless {
|
||||
game_builder.arg("-popupwindow");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.meta.game.has_module(ProfileModule::Mempatcher) {
|
||||
amd_builder
|
||||
.env("MEMPATCHER_PATCH_PATH", self.data_dir().join("patch-amd.mph"))
|
||||
.env("MEMPATCHER_LOG_PATH", self.data_dir().join("mempatcher-amdaemon.log"));
|
||||
game_builder
|
||||
.raw_arg("--mempatch")
|
||||
.arg(self.data_dir().join("patch-game.mph"))
|
||||
.env("MEMPATCHER_LOG_PATH", self.data_dir().join("mempatcher-game.log"));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
amd_builder.env("WINEPREFIX", &self.wine.prefix);
|
||||
@ -305,8 +341,8 @@ impl Profile {
|
||||
|
||||
util::pkill("amdaemon.exe").await;
|
||||
|
||||
log::info!("Launching amdaemon: {:?}", amd_builder);
|
||||
log::info!("Launching {}: {:?}", self.meta.game, game_builder);
|
||||
log::info!("launching amdaemon: {:?}", amd_builder);
|
||||
log::info!("launching {}: {:?}", self.meta.game, game_builder);
|
||||
|
||||
let mut amd = amd_builder.spawn()?;
|
||||
let mut game = game_builder.spawn()?;
|
||||
@ -321,7 +357,7 @@ impl Profile {
|
||||
(game.wait().await.expect("game failed to run"), "game")
|
||||
});
|
||||
|
||||
if let Err(e) = app.emit("launch-start", "") {
|
||||
if let Err(e) = payload.app.emit("launch-start", "") {
|
||||
log::warn!("Unable to emit launch-start: {}", e);
|
||||
}
|
||||
|
||||
@ -339,7 +375,7 @@ impl Profile {
|
||||
|
||||
log::debug!("Fin");
|
||||
|
||||
if let Err(e) = app.emit("launch-end", "") {
|
||||
if let Err(e) = payload.app.emit("launch-end", "") {
|
||||
log::warn!("Unable to emit launch-end: {}", e);
|
||||
}
|
||||
|
||||
|
64
rust/src/profiles/types.rs
Normal file
64
rust/src/profiles/types.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::AppHandle;
|
||||
use std::{collections::BTreeSet, path::PathBuf};
|
||||
use crate::{model::{misc::Game, patch::PatchSelection, profile::{Keyboard, Mu3Ini}}, pkg::PkgKey, util};
|
||||
use crate::model::profile::BepInEx;
|
||||
use crate::model::profile::{Display, Network, Segatools};
|
||||
|
||||
pub trait ProfilePaths {
|
||||
fn config_dir(&self) -> PathBuf;
|
||||
fn data_dir(&self) -> PathBuf;
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
|
||||
pub struct ProfileMeta {
|
||||
pub game: Game,
|
||||
pub name: String
|
||||
}
|
||||
|
||||
impl ProfilePaths for ProfileMeta {
|
||||
fn config_dir(&self) -> PathBuf {
|
||||
util::profile_config_dir(self.game, &self.name)
|
||||
}
|
||||
|
||||
fn data_dir(&self) -> PathBuf {
|
||||
util::data_dir().join(format!("profile-{}-{}", &self.game, &self.name))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct Profile {
|
||||
pub meta: ProfileMeta,
|
||||
pub data: ProfileData,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct ProfileData {
|
||||
pub mods: BTreeSet<PkgKey>,
|
||||
pub sgt: Segatools,
|
||||
pub network: Network,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub display: Option<Display>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bepinex: Option<BepInEx>,
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub wine: crate::model::profile::Wine,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mu3_ini: Option<Mu3Ini>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub keyboard: Option<Keyboard>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub patches: Option<PatchSelection>,
|
||||
}
|
||||
|
||||
pub struct StartPayload {
|
||||
pub app: AppHandle,
|
||||
pub game_dlls: Vec<PathBuf>,
|
||||
pub amd_dlls: Vec<PathBuf>,
|
||||
}
|
@ -150,4 +150,8 @@ impl PathStr for PathBuf {
|
||||
fn stringify(&self) -> Result<String> {
|
||||
path_to_str(&self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bool_to_01(val: bool) -> &'static str {
|
||||
return if val { "1" } else { "0" }
|
||||
}
|
82
rust/static/segatools-chunithm.ini
Normal file
82
rust/static/segatools-chunithm.ini
Normal file
@ -0,0 +1,82 @@
|
||||
[vfd]
|
||||
; Enable VFD emulation. Disable to use a real VFD
|
||||
; GP1232A02A FUTABA assembly.
|
||||
enable=1
|
||||
|
||||
[system]
|
||||
; Enable ALLS system settings.
|
||||
enable=1
|
||||
|
||||
; Enable freeplay mode. This will disable the coin slot and set the game to
|
||||
; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not
|
||||
; allow you to start a game in freeplay mode.
|
||||
freeplay=0
|
||||
|
||||
; LAN Install: If multiple machines are present on the same LAN then set
|
||||
; this to 1 on exactly one machine and set this to 0 on all others.
|
||||
dipsw1=1
|
||||
|
||||
; -----------------------------------------------------------------------------
|
||||
; Misc. hooks settings
|
||||
; -----------------------------------------------------------------------------
|
||||
|
||||
; -----------------------------------------------------------------------------
|
||||
; LED settings
|
||||
; -----------------------------------------------------------------------------
|
||||
|
||||
[led15093]
|
||||
; Enable emulation of the 15093-06 controlled lights, which handle the air tower
|
||||
; RGBs and the rear LED panel (billboard) on the cabinet.
|
||||
enable=1
|
||||
|
||||
[led]
|
||||
; Output billboard LED strip data to a named pipe called "\\.\pipe\chuni_led"
|
||||
cabLedOutputPipe=1
|
||||
; Output billboard LED strip data to serial
|
||||
cabLedOutputSerial=0
|
||||
|
||||
; Output slider LED data to the named pipe
|
||||
controllerLedOutputPipe=1
|
||||
; Output slider LED data to the serial port
|
||||
controllerLedOutputSerial=0
|
||||
; Use the OpeNITHM protocol for serial LED output
|
||||
controllerLedOutputOpeNITHM=0
|
||||
|
||||
; Serial port to send data to if using serial output. Default is COM5.
|
||||
;serialPort=COM5
|
||||
; Baud rate for serial data (set to 115200 if using OpeNITHM)
|
||||
;serialBaud=921600
|
||||
|
||||
; Data output a sequence of bytes, with JVS-like framing.
|
||||
; Each "packet" starts with 0xE0 as a sync. To avoid E0 appearing elsewhere,
|
||||
; 0xD0 is used as an escape character -- if you receive D0 in the output, ignore
|
||||
; it and use the next sent byte plus one instead.
|
||||
;
|
||||
; After the sync is one byte for the board number that was updated, followed by
|
||||
; the red, green and blue values for each LED.
|
||||
;
|
||||
; Board 0 has 53 LEDs:
|
||||
; [0]-[49]: snakes through left half of billboard (first column starts at top)
|
||||
; [50]-[52]: left side partition LEDs
|
||||
;
|
||||
; Board 1 has 63 LEDs:
|
||||
; [0]-[59]: right half of billboard (first column starts at bottom)
|
||||
; [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=
|
36
rust/static/segatools-ongeki.ini
Normal file
36
rust/static/segatools-ongeki.ini
Normal file
@ -0,0 +1,36 @@
|
||||
[vfd]
|
||||
; Enable VFD emulation. Disable to use a real VFD
|
||||
; GP1232A02A FUTABA assembly.
|
||||
enable=1
|
||||
|
||||
[system]
|
||||
; Enable ALLS system settings.
|
||||
enable=1
|
||||
|
||||
; Enable freeplay mode. This will disable the coin slot and set the game to
|
||||
; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not
|
||||
; allow you to start a game in freeplay mode.
|
||||
freeplay=0
|
||||
|
||||
; LAN Install: Set this to 1 on all machines.
|
||||
dipsw1=1
|
||||
|
||||
[gfx]
|
||||
; Enables the graphics hook.
|
||||
enable=1
|
||||
|
||||
[led15093]
|
||||
; Enable emulation of the 15093-06 controlled lights, which handle the air tower
|
||||
; RGBs and the rear LED panel (billboard) on the cabinet.
|
||||
enable=1
|
||||
|
||||
[led]
|
||||
; Output billboard LED strip data to a named pipe called \"\\\\.\\pipe\\ongeki_led\"
|
||||
cabLedOutputPipe=1
|
||||
; Output billboard LED strip data to serial
|
||||
cabLedOutputSerial=0
|
||||
|
||||
; Output slider LED data to the named pipe
|
||||
controllerLedOutputPipe=1
|
||||
; Output slider LED data to the serial port
|
||||
controllerLedOutputSerial=0
|
219
rust/static/standard-chunithm.json5
Normal file
219
rust/static/standard-chunithm.json5
Normal file
@ -0,0 +1,219 @@
|
||||
[
|
||||
{
|
||||
filename: 'chusanApp.exe',
|
||||
version: '2.30.00',
|
||||
sha256: 'd624da8a397c2885b3937e7b8bd0de6fc4e8da4beaf5c229569b29bb2847d694',
|
||||
patches: [
|
||||
{
|
||||
id: 'standard-shared-audio',
|
||||
name: 'Force shared audio mode, system audio sample rate must be 48000Hz',
|
||||
tooltip: 'Improves compatibility, but may increase latency',
|
||||
patches: [
|
||||
{
|
||||
offset: 16181386,
|
||||
off: [1],
|
||||
on: [0],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'standard-2ch',
|
||||
name: 'Force 2 channel audio output',
|
||||
tooltip: 'May cause bass overload',
|
||||
patches: [
|
||||
{
|
||||
offset: 16181601,
|
||||
off: [117, 63],
|
||||
on: [144, 144],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'standard-song-timer',
|
||||
name: 'Disable song select timer',
|
||||
patches: [
|
||||
{
|
||||
offset: 10766682,
|
||||
off: [116],
|
||||
on: [235],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'standard-map-timer',
|
||||
name: 'Map selection timer',
|
||||
tooltip: 'If set to negative, the timer becomes 968 + value (e.g. 968 + -1 = 967)',
|
||||
type: 'number',
|
||||
default: 30,
|
||||
offset: 10111639,
|
||||
size: 1,
|
||||
min: -128,
|
||||
max: 127,
|
||||
},
|
||||
{
|
||||
id: 'standard-ticket-timer',
|
||||
name: 'Ticket selection timer',
|
||||
tooltip: 'If set to negative, the timer becomes 968 + value (e.g. 968 + -1 = 967)',
|
||||
type: 'number',
|
||||
default: 60,
|
||||
offset: 10060322,
|
||||
size: 1,
|
||||
min: -128,
|
||||
max: 127,
|
||||
},
|
||||
{
|
||||
id: 'standard-course-timer',
|
||||
name: 'Course selection timer',
|
||||
tooltip: 'If set to negative, the timer becomes 968 + value (e.g. 968 + -1 = 967)',
|
||||
type: 'number',
|
||||
default: 30,
|
||||
offset: 10812315,
|
||||
size: 1,
|
||||
min: -128,
|
||||
max: 127,
|
||||
},
|
||||
{
|
||||
id: 'standard-unlimited-tracks',
|
||||
name: 'Unlimited maximum tracks',
|
||||
tooltip: 'Must check to play more than 7 tracks per credit',
|
||||
patches: [
|
||||
{
|
||||
offset: 7635328,
|
||||
off: [240],
|
||||
on: [192],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'standard-maximum-tracks',
|
||||
name: 'Maximum tracks',
|
||||
type: 'number',
|
||||
default: 3,
|
||||
offset: 3768513,
|
||||
size: 1,
|
||||
min: 1,
|
||||
max: 12,
|
||||
},
|
||||
{
|
||||
id: 'standard-no-encryption',
|
||||
name: 'No encryption',
|
||||
tooltip: 'Will also disable TLS',
|
||||
patches: [
|
||||
{
|
||||
offset: 31812584,
|
||||
off: [230],
|
||||
on: [0],
|
||||
},
|
||||
{
|
||||
offset: 31812588,
|
||||
off: [230],
|
||||
on: [0],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'standard-no-tls',
|
||||
name: 'No TLS',
|
||||
tooltip: 'Title server workaround',
|
||||
patches: [
|
||||
{
|
||||
offset: 16062679,
|
||||
off: [128],
|
||||
on: [0],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'standard-head-to-head',
|
||||
name: 'Patch for head-to-head play',
|
||||
tooltip: 'Fix infinite sync while trying to connect to head to head play',
|
||||
patches: [
|
||||
{
|
||||
offset: 6795139,
|
||||
off: [1],
|
||||
on: [0],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'standard-bypass-1080p',
|
||||
name: 'Bypass 1080p monitor check',
|
||||
patches: [
|
||||
{
|
||||
offset: 117951,
|
||||
off: [
|
||||
129, 188, 36, 184, 2, 0, 0, 128, 7, 0, 0, 117, 31,
|
||||
129, 188, 36, 188, 2, 0, 0, 56, 4, 0, 0, 117, 18,
|
||||
],
|
||||
on: [
|
||||
144, 144, 144, 144, 144, 144, 144, 144, 144, 144,
|
||||
144, 144, 144, 144, 144, 144, 144, 144, 144, 144,
|
||||
144, 144, 144, 144, 144, 144,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'standard-bypass-120hz',
|
||||
name: 'Bypass 120Hz monitor check',
|
||||
patches: [
|
||||
{
|
||||
offset: 117937,
|
||||
off: [133, 192, 116, 63],
|
||||
on: [235, 48, 235, 46],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'standard-force-free-play-text',
|
||||
name: 'Force FREE PLAY credit text',
|
||||
tooltip: 'Replaces the credit count with FREE PLAY',
|
||||
patches: [
|
||||
{
|
||||
offset: 3700132,
|
||||
off: [60, 1],
|
||||
on: [56, 192],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "STARTLINER",
|
||||
"version": "0.2.0",
|
||||
"version": "0.6.0",
|
||||
"identifier": "zip.patafour.startliner",
|
||||
"build": {
|
||||
"beforeDevCommand": "bun run dev",
|
||||
|
@ -1,25 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { Ref, computed, onMounted, ref } from 'vue';
|
||||
import Button from 'primevue/button';
|
||||
import ConfirmDialog from 'primevue/confirmdialog';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import InputIcon from 'primevue/inputicon';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import ProgressBar from 'primevue/progressbar';
|
||||
import ScrollPanel from 'primevue/scrollpanel';
|
||||
import Tab from 'primevue/tab';
|
||||
import TabList from 'primevue/tablist';
|
||||
import TabPanel from 'primevue/tabpanel';
|
||||
import TabPanels from 'primevue/tabpanels';
|
||||
import Tabs from 'primevue/tabs';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import ModList from './ModList.vue';
|
||||
import ModStore from './ModStore.vue';
|
||||
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 { useGeneralStore, usePkgStore, usePrfStore } from '../stores';
|
||||
import {
|
||||
useClientStore,
|
||||
useGeneralStore,
|
||||
usePkgStore,
|
||||
usePrfStore,
|
||||
} from '../stores';
|
||||
import { Dirs } from '../types';
|
||||
import { messageSplit } from '../util';
|
||||
|
||||
const pkg = usePkgStore();
|
||||
const prf = usePrfStore();
|
||||
const general = useGeneralStore();
|
||||
const client = useClientStore();
|
||||
|
||||
pkg.setupListeners();
|
||||
|
||||
@ -28,9 +41,20 @@ const pkgSearchTerm = ref('');
|
||||
|
||||
const isProfileDisabled = computed(() => prf.current === null);
|
||||
|
||||
const updateProgress: Ref<number | null> = ref(null);
|
||||
|
||||
listen<number>('update-progress', (ev) => {
|
||||
updateProgress.value = Math.floor(ev.payload * 100);
|
||||
});
|
||||
|
||||
listen<undefined>('update-end', (_) => {
|
||||
updateProgress.value = null;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
invoke('list_directories').then((d) => {
|
||||
general.dirs = d as Dirs;
|
||||
client.load();
|
||||
});
|
||||
|
||||
const fetch_promise = pkg.fetch(true);
|
||||
@ -54,21 +78,85 @@ onMounted(async () => {
|
||||
});
|
||||
});
|
||||
|
||||
const errorVisible = ref(false);
|
||||
const errorMessage = ref('No error');
|
||||
const errorHeader = ref('No header');
|
||||
|
||||
listen<{ message: string; header: string }>('invoke-error', (event) => {
|
||||
errorVisible.value = true;
|
||||
errorMessage.value = event.payload.message;
|
||||
errorHeader.value = event.payload.header;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<main
|
||||
:class="
|
||||
client.scaleFactor === 's'
|
||||
? 'main-scale-s'
|
||||
: client.scaleFactor === 'm'
|
||||
? 'main-scale-m'
|
||||
: client.scaleFactor === 'l'
|
||||
? 'main-scale-l'
|
||||
: 'main-scale-xl'
|
||||
"
|
||||
>
|
||||
<ConfirmDialog>
|
||||
<template #message="{ message }">
|
||||
<ScrollPanel
|
||||
v-if="messageSplit(message).length > 5"
|
||||
style="width: 100%; height: 40vh"
|
||||
>
|
||||
<p v-for="m in messageSplit(message)">
|
||||
{{ m }}
|
||||
</p></ScrollPanel
|
||||
>
|
||||
<div v-else>
|
||||
<p v-for="m in messageSplit(message)">
|
||||
{{ m }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</ConfirmDialog>
|
||||
<Dialog
|
||||
modal
|
||||
:visible="errorVisible"
|
||||
:closable="false /*this shit doesn't work */"
|
||||
:header="errorHeader"
|
||||
:style="{ width: '50vw' }"
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
{{ errorMessage }}
|
||||
<Button
|
||||
class="m-auto"
|
||||
label="A sad state of affairs"
|
||||
@click="errorVisible = false"
|
||||
/>
|
||||
</div>
|
||||
</Dialog>
|
||||
<Dialog
|
||||
modal
|
||||
:visible="updateProgress !== null"
|
||||
:closable="false"
|
||||
header="Updating"
|
||||
:style="{ width: '200px' }"
|
||||
>
|
||||
<ProgressBar :value="updateProgress ?? undefined" />
|
||||
</Dialog>
|
||||
|
||||
<Tabs
|
||||
lazy
|
||||
:value="currentTab"
|
||||
v-on:update:value="(value) => (currentTab = value)"
|
||||
v-on:update:value="
|
||||
(value) => {
|
||||
currentTab = value;
|
||||
}
|
||||
"
|
||||
class="h-screen"
|
||||
>
|
||||
<div class="fixed w-full flex z-100">
|
||||
<TabList class="grow">
|
||||
<Tab :value="3"
|
||||
><div class="pi pi-question-circle"></div
|
||||
></Tab>
|
||||
<TabList class="grow" :show-navigators="false">
|
||||
<Tab :value="3"><div class="pi pi-users"></div></Tab>
|
||||
<Tab :disabled="isProfileDisabled" :value="0"
|
||||
><div class="pi pi-box"></div
|
||||
></Tab>
|
||||
@ -86,13 +174,18 @@ onMounted(async () => {
|
||||
></Tab>
|
||||
|
||||
<div class="grow"></div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<div class="flex" v-if="currentTab !== 3">
|
||||
<div
|
||||
class="flex"
|
||||
v-if="[0, 1, 2].includes(currentTab as number)"
|
||||
>
|
||||
<InputIcon class="self-center mr-2">
|
||||
<i class="pi pi-search" />
|
||||
</InputIcon>
|
||||
<InputText
|
||||
v-if="currentTab === 2"
|
||||
style="min-width: 0; width: 25dvw"
|
||||
class="self-center"
|
||||
size="small"
|
||||
placeholder="Search"
|
||||
@ -100,6 +193,7 @@ onMounted(async () => {
|
||||
/>
|
||||
<InputText
|
||||
v-else
|
||||
style="min-width: 0; width: 25dvw"
|
||||
class="self-center"
|
||||
size="small"
|
||||
placeholder="Search"
|
||||
@ -114,12 +208,26 @@ onMounted(async () => {
|
||||
:disabled="true"
|
||||
/>
|
||||
<Button
|
||||
v-if="pkg.networkStatus === 'offline'"
|
||||
v-if="
|
||||
pkg.networkStatus === 'offline' &&
|
||||
!client.offlineMode
|
||||
"
|
||||
class="shrink self-center"
|
||||
icon="pi pi-sync"
|
||||
size="small"
|
||||
@click="pkg.fetch(false)"
|
||||
/>
|
||||
<Button
|
||||
v-if="
|
||||
pkg.networkStatus === 'online' &&
|
||||
pkg.hasAvailableUpdates
|
||||
"
|
||||
icon="pi pi-download"
|
||||
label="UPDATE ALL"
|
||||
size="small"
|
||||
class="mr-4 m-2.5"
|
||||
@click="pkg.updateAll()"
|
||||
></Button>
|
||||
</div>
|
||||
<div class="grow"></div>
|
||||
<StartButton />
|
||||
@ -136,9 +244,6 @@ onMounted(async () => {
|
||||
<OptionList />
|
||||
</TabPanel>
|
||||
<TabPanel :value="3">
|
||||
<strong>UNDER CONSTRUCTION</strong><br />Some features are
|
||||
missing.<br />Existing features are expected to break
|
||||
sometimes.
|
||||
<ProfileList />
|
||||
<br /><br /><br />
|
||||
<footer>
|
||||
@ -151,8 +256,17 @@ onMounted(async () => {
|
||||
</footer>
|
||||
</TabPanel>
|
||||
<TabPanel :value="4">
|
||||
CHUNITHM patches are not yet implemented.<br />Use
|
||||
<a href=https://patcher.two-torial.xyz/ target="_blank" style="text-decoration: underline;">patcher.two-torial.xyz</a>
|
||||
<PatchList
|
||||
v-if="
|
||||
pkg.hasLocal('mempatcher-mempatcher') &&
|
||||
prf.isPkgKeyEnabled('mempatcher-mempatcher')
|
||||
.value === true
|
||||
"
|
||||
/>
|
||||
<div v-else>
|
||||
Patches require <code>mempatcher</code> to be installed
|
||||
and enabled.
|
||||
</div>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
<div v-if="currentTab === 5 || currentTab === 3">
|
||||
@ -184,4 +298,42 @@ body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.main-scale-s {
|
||||
zoom: 1;
|
||||
}
|
||||
|
||||
.main-scale-m {
|
||||
zoom: 1.25;
|
||||
}
|
||||
|
||||
.main-scale-l {
|
||||
zoom: 1.4;
|
||||
}
|
||||
|
||||
.main-scale-xl {
|
||||
zoom: 1.7;
|
||||
}
|
||||
|
||||
.p-tablist {
|
||||
background-color: #ffffff !important;
|
||||
border-bottom: white 10px !important;
|
||||
}
|
||||
|
||||
.p-tab {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
.p-tablist-active-bar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.p-tooltip {
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.p-progressbar,
|
||||
.p-progressbar-value,
|
||||
.p-progressbar-label {
|
||||
transition-duration: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,7 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import * as path from '@tauri-apps/api/path';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { usePrfStore } from '../stores';
|
||||
|
||||
const props = defineProps({
|
||||
placeholder: String,
|
||||
@ -12,10 +14,25 @@ const props = defineProps({
|
||||
callback: Function,
|
||||
});
|
||||
|
||||
const prf = usePrfStore();
|
||||
|
||||
const filePick = async () => {
|
||||
const exePath = prf.current?.data.sgt.target;
|
||||
let defaultPath: string | undefined;
|
||||
if (
|
||||
exePath !== undefined &&
|
||||
exePath.length > 0 &&
|
||||
props.value !== undefined &&
|
||||
!(await path.isAbsolute(props.value))
|
||||
) {
|
||||
defaultPath = await path.join(exePath, '..');
|
||||
defaultPath = await path.join(defaultPath, props.value);
|
||||
defaultPath = await path.join(defaultPath, '..');
|
||||
}
|
||||
const res = await open({
|
||||
multiple: false,
|
||||
directory: props.directory,
|
||||
defaultPath,
|
||||
filters:
|
||||
props.promptname && props.extension
|
||||
? [
|
||||
@ -28,7 +45,7 @@ const filePick = async () => {
|
||||
});
|
||||
if (res != null && props.callback !== undefined) {
|
||||
props.callback(res);
|
||||
/*path.relative(cfgs.current?.data.exe_dir ?? '', res) */
|
||||
/*path.relative(prf.current?.data.sgt.target ?? '', res) */
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -1,33 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button';
|
||||
import { invoke } from '../invoke';
|
||||
import { usePkgStore } from '../stores';
|
||||
import { Package } from '../types';
|
||||
import { pkgKey } from '../util';
|
||||
|
||||
const pkgs = usePkgStore();
|
||||
|
||||
const props = defineProps({
|
||||
pkg: Object as () => Package,
|
||||
});
|
||||
|
||||
const install = async () => {
|
||||
if (props.pkg === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await invoke('install_package', {
|
||||
key: pkgKey(props.pkg),
|
||||
force: true,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (props.pkg !== undefined) {
|
||||
props.pkg.js.busy = false;
|
||||
}
|
||||
}
|
||||
|
||||
//if (rv === 'Deferred') { /* download progress */ }
|
||||
};
|
||||
|
||||
const remove = async () => {
|
||||
if (props.pkg === undefined) {
|
||||
return;
|
||||
@ -41,7 +24,7 @@ const remove = async () => {
|
||||
|
||||
<template>
|
||||
<Button
|
||||
v-if="pkg?.loc"
|
||||
v-if="pkg?.loc && !pkg?.js.busy"
|
||||
rounded
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
@ -63,6 +46,6 @@ const remove = async () => {
|
||||
class="self-center ml-4"
|
||||
style="width: 2rem; height: 2rem"
|
||||
:loading="pkg?.js.busy"
|
||||
v-on:click="install()"
|
||||
v-on:click="async () => await pkgs.install(pkg)"
|
||||
/>
|
||||
</template>
|
||||
|
245
src/components/KeyboardKey.vue
Normal file
245
src/components/KeyboardKey.vue
Normal file
@ -0,0 +1,245 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import { usePrfStore } from '../stores';
|
||||
import { OngekiButtons } from '../types';
|
||||
|
||||
const prf = usePrfStore();
|
||||
|
||||
const hasClickedM1Once = ref(false);
|
||||
|
||||
const handleKey = (
|
||||
button: string | undefined,
|
||||
event: KeyboardEvent,
|
||||
index?: number
|
||||
) => {
|
||||
event.preventDefault();
|
||||
|
||||
const keycode = toKeycode(event.code);
|
||||
if (keycode !== null && button !== undefined) {
|
||||
const data = prf.current!.data.keyboard!.data as any;
|
||||
if (index !== undefined) {
|
||||
data[button][index] = keycode;
|
||||
} else {
|
||||
data[button] = keycode;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouse = (
|
||||
button: string | undefined,
|
||||
event: MouseEvent,
|
||||
index?: number
|
||||
) => {
|
||||
if (button === undefined || button == 'use_mouse') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.button === 0) {
|
||||
if (hasClickedM1Once.value === false) {
|
||||
hasClickedM1Once.value = true;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
let keycode;
|
||||
switch (event.button) {
|
||||
case 0:
|
||||
keycode = 1;
|
||||
break;
|
||||
case 1:
|
||||
keycode = 4;
|
||||
break;
|
||||
case 2:
|
||||
keycode = 2;
|
||||
break;
|
||||
case 3:
|
||||
keycode = 5;
|
||||
break;
|
||||
case 4:
|
||||
keycode = 6;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (keycode !== undefined) {
|
||||
const data = prf.current!.data.keyboard!.data as any;
|
||||
|
||||
if (index !== undefined) {
|
||||
data[button][index] = keycode;
|
||||
} else {
|
||||
data[button] = keycode;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getKey = (key: keyof OngekiButtons, index?: number) =>
|
||||
computed(() => {
|
||||
const data = prf.current!.data.keyboard?.data as any;
|
||||
const keycode =
|
||||
index === undefined
|
||||
? (data[key] as number | undefined)
|
||||
: (data[key]?.[index] as number | undefined);
|
||||
return keycode && fromKeycode(keycode) ? fromKeycode(keycode) : '–';
|
||||
});
|
||||
|
||||
const KEY_MAP: { [key: number]: string } = {
|
||||
1: 'M1',
|
||||
2: 'M2',
|
||||
4: 'M3',
|
||||
5: 'M4',
|
||||
6: 'M5',
|
||||
8: 'Backspace',
|
||||
9: 'Tab',
|
||||
13: 'Enter',
|
||||
19: 'Pause',
|
||||
20: 'CapsLock',
|
||||
27: 'Escape',
|
||||
32: 'Space',
|
||||
33: 'PageUp',
|
||||
34: 'PageDown',
|
||||
35: 'End',
|
||||
36: 'Home',
|
||||
37: 'ArrowLeft',
|
||||
38: 'ArrowUp',
|
||||
39: 'ArrowRight',
|
||||
40: 'ArrowDown',
|
||||
45: 'Insert',
|
||||
46: 'Delete',
|
||||
48: 'Digit0',
|
||||
49: 'Digit1',
|
||||
50: 'Digit2',
|
||||
51: 'Digit3',
|
||||
52: 'Digit4',
|
||||
53: 'Digit5',
|
||||
54: 'Digit6',
|
||||
55: 'Digit7',
|
||||
56: 'Digit8',
|
||||
57: 'Digit9',
|
||||
65: 'KeyA',
|
||||
66: 'KeyB',
|
||||
67: 'KeyC',
|
||||
68: 'KeyD',
|
||||
69: 'KeyE',
|
||||
70: 'KeyF',
|
||||
71: 'KeyG',
|
||||
72: 'KeyH',
|
||||
73: 'KeyI',
|
||||
74: 'KeyJ',
|
||||
75: 'KeyK',
|
||||
76: 'KeyL',
|
||||
77: 'KeyM',
|
||||
78: 'KeyN',
|
||||
79: 'KeyO',
|
||||
80: 'KeyP',
|
||||
81: 'KeyQ',
|
||||
82: 'KeyR',
|
||||
83: 'KeyS',
|
||||
84: 'KeyT',
|
||||
85: 'KeyU',
|
||||
86: 'KeyV',
|
||||
87: 'KeyW',
|
||||
88: 'KeyX',
|
||||
89: 'KeyY',
|
||||
90: 'KeyZ',
|
||||
91: 'MetaLeft',
|
||||
92: 'MetaRight',
|
||||
93: 'ContextMenu',
|
||||
96: 'Numpad0',
|
||||
97: 'Numpad1',
|
||||
98: 'Numpad2',
|
||||
99: 'Numpad3',
|
||||
100: 'Numpad4',
|
||||
101: 'Numpad5',
|
||||
102: 'Numpad6',
|
||||
103: 'Numpad7',
|
||||
104: 'Numpad8',
|
||||
105: 'Numpad9',
|
||||
106: 'NumpadMultiply',
|
||||
107: 'NumpadAdd',
|
||||
109: 'NumpadSubtract',
|
||||
110: 'NumpadDecimal',
|
||||
111: 'NumpadDivide',
|
||||
112: 'F1',
|
||||
113: 'F2',
|
||||
114: 'F3',
|
||||
115: 'F4',
|
||||
116: 'F5',
|
||||
117: 'F6',
|
||||
118: 'F7',
|
||||
119: 'F8',
|
||||
120: 'F9',
|
||||
121: 'F10',
|
||||
122: 'F11',
|
||||
123: 'F12',
|
||||
144: 'NumLock',
|
||||
145: 'ScrollLock',
|
||||
160: 'ShiftLeft',
|
||||
161: 'ShiftRight',
|
||||
162: 'ControlLeft',
|
||||
163: 'ControlRight',
|
||||
164: 'AltLeft',
|
||||
165: 'AltRight',
|
||||
186: 'Semicolon',
|
||||
187: 'Equal',
|
||||
188: 'Comma',
|
||||
189: 'Minus',
|
||||
190: 'Period',
|
||||
191: 'Slash',
|
||||
192: 'Backquote',
|
||||
219: 'BracketLeft',
|
||||
220: 'Backslash',
|
||||
221: 'BracketRight',
|
||||
222: 'Quote',
|
||||
};
|
||||
|
||||
const fromKeycode = (keyCode: number): string | null => {
|
||||
return KEY_MAP[keyCode] ?? null;
|
||||
};
|
||||
|
||||
const toKeycode = (key: string): number | null => {
|
||||
const res = Object.entries(KEY_MAP).find(([_, v]) => v === key)?.[0];
|
||||
return res ? parseInt(res) : null;
|
||||
};
|
||||
|
||||
defineProps({
|
||||
small: Boolean,
|
||||
verySmall: Boolean,
|
||||
tall: Boolean,
|
||||
tooltip: String,
|
||||
button: String,
|
||||
color: String,
|
||||
index: Number,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InputText
|
||||
:style="{
|
||||
width: small ? '3em' : '5em',
|
||||
height: small ? '3em' : tall ? '10em' : '5em',
|
||||
fontSize: small ? '0.9em' : '1em',
|
||||
backgroundColor: color,
|
||||
}"
|
||||
unstyled
|
||||
class="text-center buttoninputtext"
|
||||
v-tooltip="tooltip"
|
||||
@contextmenu.prevent="() => {}"
|
||||
@keydown="(ev: KeyboardEvent) => handleKey(button, ev, index)"
|
||||
@mousedown="
|
||||
(ev: MouseEvent) =>
|
||||
handleMouse(button as keyof OngekiButtons, ev, index)
|
||||
"
|
||||
@focusout="() => (hasClickedM1Once = false)"
|
||||
:model-value="getKey(button as keyof OngekiButtons, index) as any"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="css">
|
||||
.buttoninputtext {
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(200, 200, 200, 0.3);
|
||||
}
|
||||
</style>
|
@ -62,7 +62,10 @@ const iconSrc = computed(() => {
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
v-if="hasFeature(pkg, Feature.Mu3IO)"
|
||||
v-if="
|
||||
hasFeature(pkg, Feature.Mu3IO) ||
|
||||
hasFeature(pkg, Feature.ChuniIO)
|
||||
"
|
||||
v-tooltip="'IO'"
|
||||
class="pi pi-wrench ml-1 text-green-400"
|
||||
>
|
||||
@ -73,6 +76,15 @@ const iconSrc = computed(() => {
|
||||
class="pi pi-credit-card ml-1 text-purple-400"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
hasFeature(pkg, Feature.GameDLL) ||
|
||||
hasFeature(pkg, Feature.AmdDLL)
|
||||
"
|
||||
v-tooltip="'DLL'"
|
||||
class="pi pi-cog ml-1 text-red-400"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
v-if="showNamespace && pkg?.namespace"
|
||||
class="text-sm opacity-75"
|
||||
|
@ -6,6 +6,8 @@ const general = useGeneralStore();
|
||||
|
||||
defineProps({
|
||||
title: String,
|
||||
collapsed: Boolean,
|
||||
alwaysFound: Boolean,
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -13,7 +15,8 @@ defineProps({
|
||||
<Fieldset
|
||||
:legend="title"
|
||||
:toggleable="true"
|
||||
v-show="general.cfgCategories.has(title ?? '')"
|
||||
v-show="general.cfgCategories.has(title ?? '') || alwaysFound"
|
||||
:collapsed="collapsed"
|
||||
>
|
||||
<div class="flex w-full flex-col gap-1">
|
||||
<slot />
|
||||
|
@ -7,9 +7,11 @@ import OptionCategory from './OptionCategory.vue';
|
||||
import OptionRow from './OptionRow.vue';
|
||||
import AimeOptions from './options/Aime.vue';
|
||||
import DisplayOptions from './options/Display.vue';
|
||||
import KeyboardOptions from './options/Keyboard.vue';
|
||||
import MiscOptions from './options/Misc.vue';
|
||||
import NetworkOptions from './options/Network.vue';
|
||||
import SegatoolsOptions from './options/Segatools.vue';
|
||||
import StartlinerOptions from './options/Startliner.vue';
|
||||
import { usePrfStore } from '../stores';
|
||||
|
||||
const prf = usePrfStore();
|
||||
@ -67,10 +69,21 @@ prf.reload();
|
||||
|
||||
<template>
|
||||
<SegatoolsOptions />
|
||||
<DisplayOptions v-if="prf.current!.meta.game === 'ongeki'" />
|
||||
<DisplayOptions />
|
||||
<NetworkOptions />
|
||||
<AimeOptions />
|
||||
<MiscOptions />
|
||||
<OptionCategory
|
||||
title="Extensions"
|
||||
v-if="prf.current!.meta.game === 'chunithm'"
|
||||
>
|
||||
<OptionRow title="Saekawa config">
|
||||
<FileEditor
|
||||
filename="saekawa.toml"
|
||||
promptname="saekawa config file"
|
||||
extension="toml"
|
||||
/> </OptionRow
|
||||
></OptionCategory>
|
||||
<OptionCategory
|
||||
title="Extensions"
|
||||
v-if="prf.current!.meta.game === 'ongeki'"
|
||||
@ -128,6 +141,8 @@ prf.reload();
|
||||
v-model="blacklistMaxModel"
|
||||
/></OptionRow> -->
|
||||
</OptionCategory>
|
||||
<KeyboardOptions />
|
||||
<StartlinerOptions />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
@ -8,6 +8,8 @@ const category = getCurrentInstance()?.parent?.parent?.parent?.parent; // yes in
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
tooltip: String,
|
||||
dangerousTooltip: String,
|
||||
greytext: String,
|
||||
});
|
||||
|
||||
const searched = computed(() => {
|
||||
@ -32,6 +34,17 @@ const searched = computed(() => {
|
||||
class="pi pi-question-circle ml-2"
|
||||
v-tooltip="tooltip"
|
||||
></span>
|
||||
<span
|
||||
v-if="dangerousTooltip"
|
||||
class="pi pi-exclamation-circle ml-2 text-red-500"
|
||||
v-tooltip="dangerousTooltip"
|
||||
></span>
|
||||
<span
|
||||
v-if="greytext"
|
||||
style="font-size: 0.65rem"
|
||||
class="ml-2 text-gray-400"
|
||||
>{{ greytext }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
|
54
src/components/PatchEntry.vue
Normal file
54
src/components/PatchEntry.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import InputNumber from 'primevue/inputnumber';
|
||||
import ToggleSwitch from 'primevue/toggleswitch';
|
||||
import OptionRow from './OptionRow.vue';
|
||||
import { usePrfStore } from '../stores';
|
||||
import { Patch } from '@/types';
|
||||
|
||||
const prf = usePrfStore();
|
||||
|
||||
const toggleUnary = (key: string, val: boolean) => {
|
||||
if (val) {
|
||||
prf.current!.data.patches[key] = 'enabled';
|
||||
} else {
|
||||
delete prf.current!.data.patches[key];
|
||||
}
|
||||
};
|
||||
|
||||
const setNumber = (key: string, val: number) => {
|
||||
if (val) {
|
||||
prf.current!.data.patches[key] = { number: val };
|
||||
} else {
|
||||
delete prf.current!.data.patches[key];
|
||||
}
|
||||
};
|
||||
|
||||
defineProps({
|
||||
patch: Object as () => Patch,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OptionRow
|
||||
:title="patch?.name"
|
||||
:tooltip="patch?.tooltip"
|
||||
:greytext="patch?.id"
|
||||
>
|
||||
<ToggleSwitch
|
||||
v-if="patch?.type === undefined"
|
||||
:model-value="prf.current!.data.patches[patch!.id!] !== undefined"
|
||||
@update:model-value="(v: boolean) => toggleUnary(patch!.id!, v)"
|
||||
/>
|
||||
<InputNumber
|
||||
v-else-if="patch?.type === 'number'"
|
||||
class="number-input"
|
||||
:model-value="
|
||||
(prf.current!.data.patches[patch!.id!] as any)?.number
|
||||
"
|
||||
@update:model-value="(v: number) => setNumber(patch!.id!, v)"
|
||||
:min="patch?.min"
|
||||
:max="patch?.max"
|
||||
:placeholder="(patch?.default ?? 0).toString()"
|
||||
/>
|
||||
</OptionRow>
|
||||
</template>
|
60
src/components/PatchList.vue
Normal file
60
src/components/PatchList.vue
Normal file
@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import { Ref, ref } from 'vue';
|
||||
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';
|
||||
|
||||
const prf = usePrfStore();
|
||||
|
||||
prf.reload();
|
||||
|
||||
const gamePatches: Ref<Patch[] | null> = ref(null);
|
||||
const amdPatches: Ref<Patch[] | null> = ref(null);
|
||||
|
||||
invoke('list_patches', { target: prf.current!.data.sgt.target }).then(
|
||||
(patches) => {
|
||||
gamePatches.value = patches as Patch[];
|
||||
}
|
||||
);
|
||||
|
||||
(async () => {
|
||||
const amd = await path.join(
|
||||
prf.current!.data.sgt.target,
|
||||
'../amdaemon.exe'
|
||||
);
|
||||
amdPatches.value = (await invoke('list_patches', {
|
||||
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>
|
||||
<PatchEntry
|
||||
v-if="gamePatches !== null"
|
||||
v-for="p in gamePatches"
|
||||
:patch="p"
|
||||
/>
|
||||
<div v-if="gamePatches === null">Loading...</div>
|
||||
<div v-if="gamePatches !== null && gamePatches.length === 0">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</OptionCategory>
|
||||
<OptionCategory title="amdaemon.exe" always-found>
|
||||
<PatchEntry
|
||||
v-if="amdPatches !== null"
|
||||
v-for="p in amdPatches"
|
||||
:patch="p"
|
||||
/>
|
||||
<div v-if="gamePatches === null">Loading...</div>
|
||||
<div v-if="amdPatches !== null && amdPatches.length === 0">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</OptionCategory>
|
||||
</template>
|
@ -7,6 +7,9 @@ const prf = usePrfStore();
|
||||
</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"
|
||||
|
@ -74,6 +74,8 @@ const deleteProfile = async () => {
|
||||
<div v-if="!isEditing">{{ p!.name }}</div>
|
||||
<div v-else>
|
||||
<InputText
|
||||
unstyled
|
||||
class="text-center"
|
||||
:model-value="p!.name"
|
||||
@vue:mounted="$event?.el?.focus()"
|
||||
@keyup="renameProfile"
|
||||
@ -127,3 +129,11 @@ const deleteProfile = async () => {
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
.p-tablist-tab-list {
|
||||
border: none !important;
|
||||
border-color: transparent !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,9 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Ref, computed, ref } from 'vue';
|
||||
import Button from 'primevue/button';
|
||||
import ConfirmDialog from 'primevue/confirmdialog';
|
||||
import ContextMenu from 'primevue/contextmenu';
|
||||
import ScrollPanel from 'primevue/scrollpanel';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
@ -38,6 +36,8 @@ const startline = async (force: boolean, refresh: boolean) => {
|
||||
confirmDialog.require({
|
||||
message: message.join('\n'),
|
||||
header: 'Start check failed',
|
||||
acceptLabel: 'Run anyway',
|
||||
rejectLabel: 'Cancel',
|
||||
accept: () => {
|
||||
startline(true, refresh);
|
||||
},
|
||||
@ -85,10 +85,6 @@ listen('launch-end', () => {
|
||||
getCurrentWindow().setFocus();
|
||||
});
|
||||
|
||||
const messageSplit = (message: any) => {
|
||||
return message.message?.split('\n');
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: 'Refresh and start',
|
||||
@ -111,45 +107,6 @@ const showContextMenu = (event: Event) => {
|
||||
|
||||
<template>
|
||||
<ContextMenu ref="menu" :model="menuItems" />
|
||||
<ConfirmDialog>
|
||||
<template #container="{ message, acceptCallback, rejectCallback }">
|
||||
<div
|
||||
class="flex flex-col p-8 bg-surface-0 dark:bg-surface-900 rounded"
|
||||
>
|
||||
<span class="font-bold self-center text-2xl block mb-2 mt-2">{{
|
||||
message.header
|
||||
}}</span>
|
||||
<ScrollPanel
|
||||
v-if="messageSplit(message).length > 5"
|
||||
style="width: 100%; height: 40vh"
|
||||
>
|
||||
<p v-for="m in messageSplit(message)">
|
||||
{{ m }}
|
||||
</p></ScrollPanel
|
||||
>
|
||||
<div v-else>
|
||||
<p v-for="m in messageSplit(message)">
|
||||
{{ m }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex self-center items-center gap-2 mt-6">
|
||||
<Button
|
||||
label="Run anyway"
|
||||
@click="acceptCallback"
|
||||
size="small"
|
||||
class="w-32"
|
||||
></Button>
|
||||
<Button
|
||||
label="Cancel"
|
||||
outlined
|
||||
size="small"
|
||||
@click="rejectCallback"
|
||||
class="w-32"
|
||||
></Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ConfirmDialog>
|
||||
<Button
|
||||
v-if="startStatus === 'ready'"
|
||||
v-tooltip="disabledTooltip"
|
||||
|
@ -32,7 +32,7 @@ const install = async () => {
|
||||
<Button
|
||||
v-if="needsUpdate(pkg) && !pkg?.js.busy"
|
||||
rounded
|
||||
icon="pi pi-sync"
|
||||
icon="pi pi-download"
|
||||
severity="success"
|
||||
aria-label="remove"
|
||||
size="small"
|
||||
|
@ -1,12 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { Ref, computed, ref } from 'vue';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import Select from 'primevue/select';
|
||||
import ToggleSwitch from 'primevue/toggleswitch';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import * as path from '@tauri-apps/api/path';
|
||||
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
|
||||
import OptionCategory from '../OptionCategory.vue';
|
||||
import OptionRow from '../OptionRow.vue';
|
||||
import { invoke } from '../../invoke';
|
||||
import { usePkgStore, usePrfStore } from '../../stores';
|
||||
import { Feature } from '../../types';
|
||||
import { hasFeature, pkgKey } from '../../util';
|
||||
@ -15,6 +17,7 @@ const pkgs = usePkgStore();
|
||||
const prf = usePrfStore();
|
||||
|
||||
const aimeCode = ref('');
|
||||
const coms: Ref<{ [key: string]: number }> = ref({});
|
||||
|
||||
prf.reload();
|
||||
|
||||
@ -40,20 +43,31 @@ const aimeCodePaste = (ev: ClipboardEvent) => {
|
||||
.join('') ?? '';
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const load = async () => {
|
||||
const aime_path = await path.join(await prf.configDir, 'aime.txt');
|
||||
aimeCode.value = await readTextFile(aime_path).catch(() => '');
|
||||
})();
|
||||
};
|
||||
|
||||
invoke('list_com_ports').then((newComs) => {
|
||||
coms.value = newComs as typeof coms.value;
|
||||
});
|
||||
|
||||
listen('reload-aime-code', load);
|
||||
|
||||
load();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OptionCategory title="Aime">
|
||||
<OptionRow title="Aime emulation">
|
||||
<OptionRow
|
||||
title="Aime type"
|
||||
tooltip="Additional Aime plugins can be downloaded from the package store."
|
||||
>
|
||||
<Select
|
||||
v-model="prf.current!.data.sgt.aime"
|
||||
:options="[
|
||||
{ title: 'none', value: 'Disabled' },
|
||||
{ title: 'segatools built-in', value: 'BuiltIn' },
|
||||
{ title: 'hardware', value: 'Disabled' },
|
||||
{ title: 'segatools built-in emulation', value: 'BuiltIn' },
|
||||
...pkgs.byFeature(Feature.Aime).map((p) => {
|
||||
return {
|
||||
title: pkgKey(p),
|
||||
@ -68,11 +82,13 @@ const aimeCodePaste = (ev: ClipboardEvent) => {
|
||||
option-value="value"
|
||||
></Select>
|
||||
</OptionRow>
|
||||
<OptionRow title="Aime code">
|
||||
<OptionRow
|
||||
title="Aime code"
|
||||
v-if="prf.current!.data.sgt.aime !== 'Disabled'"
|
||||
>
|
||||
<InputText
|
||||
class="shrink"
|
||||
size="small"
|
||||
:disabled="prf.current!.data.sgt.aime === 'Disabled'"
|
||||
:maxlength="20"
|
||||
placeholder="00000000000000000000"
|
||||
v-model="aimeCodeModel"
|
||||
@ -105,5 +121,27 @@ const aimeCodePaste = (ev: ClipboardEvent) => {
|
||||
<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."
|
||||
v-if="prf.current!.data.sgt.aime === 'Disabled'"
|
||||
>
|
||||
<Select
|
||||
v-model="prf.current!.data.sgt.aime_port"
|
||||
:options="[
|
||||
{ title: 'default', value: null },
|
||||
...Object.entries(coms ?? {}).map(([title, value]) => {
|
||||
return {
|
||||
title,
|
||||
value,
|
||||
};
|
||||
}),
|
||||
]"
|
||||
placeholder="default"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
></Select>
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
</template>
|
||||
|
@ -63,6 +63,11 @@ const loadDisplays = () => {
|
||||
};
|
||||
|
||||
loadDisplays();
|
||||
|
||||
const game = prf.current!.meta.game;
|
||||
const isVertical = game === 'ongeki';
|
||||
const adjustableRez = game === 'ongeki';
|
||||
const canSkipPrimarySwitch = game === 'ongeki';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -80,7 +85,11 @@ loadDisplays();
|
||||
@show="loadDisplays"
|
||||
></Select>
|
||||
</OptionRow>
|
||||
<OptionRow class="number-input" title="Game resolution">
|
||||
<OptionRow
|
||||
class="number-input"
|
||||
title="Game resolution"
|
||||
v-if="adjustableRez"
|
||||
>
|
||||
<InputNumber
|
||||
class="shrink"
|
||||
size="small"
|
||||
@ -118,12 +127,18 @@ loadDisplays();
|
||||
>
|
||||
<SelectButton
|
||||
v-model="prf.current!.data.display.rotation"
|
||||
:options="[
|
||||
{ title: 'Unchanged', value: 0 },
|
||||
{ title: 'Portrait', value: 90 },
|
||||
{ title: 'Portrait (flipped)', value: 270 },
|
||||
]"
|
||||
:allow-empty="false"
|
||||
:options="
|
||||
isVertical
|
||||
? [
|
||||
{ title: 'Portrait', value: 90 },
|
||||
{ title: 'Portrait (flipped)', value: 270 },
|
||||
]
|
||||
: [
|
||||
{ title: 'Landscape', value: 0 },
|
||||
{ title: 'Landscape (flipped)', value: 180 },
|
||||
]
|
||||
"
|
||||
:allow-empty="true"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
:disabled="extraDisplayOptionsDisabled"
|
||||
@ -135,6 +150,7 @@ loadDisplays();
|
||||
title="Refresh Rate"
|
||||
>
|
||||
<InputNumber
|
||||
v-if="game === 'ongeki'"
|
||||
class="shrink"
|
||||
size="small"
|
||||
:min="60"
|
||||
@ -143,6 +159,18 @@ loadDisplays();
|
||||
v-model="prf.current!.data.display.frequency"
|
||||
:disabled="extraDisplayOptionsDisabled"
|
||||
/>
|
||||
<SelectButton
|
||||
v-if="game === 'chunithm'"
|
||||
v-model="prf.current!.data.display.frequency"
|
||||
:options="[
|
||||
{ title: '60Hz (CVT)', value: 60 },
|
||||
{ title: '120Hz (SP)', value: 120 },
|
||||
]"
|
||||
:allow-empty="false"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
:disabled="extraDisplayOptionsDisabled"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
title="Borderless fullscreen"
|
||||
@ -157,5 +185,41 @@ loadDisplays();
|
||||
v-model="prf.current!.data.display.borderless_fullscreen"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
title="Skip switching primary display"
|
||||
v-if="
|
||||
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."
|
||||
>
|
||||
<ToggleSwitch
|
||||
:disabled="extraDisplayOptionsDisabled"
|
||||
v-model="prf.current!.data.display.dont_switch_primary"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
title="Display index"
|
||||
class="number-input"
|
||||
v-if="
|
||||
capabilities.includes('display') &&
|
||||
prf.current?.data.display.target !== 'default' &&
|
||||
prf.current!.data.display.dont_switch_primary
|
||||
"
|
||||
>
|
||||
<InputNumber
|
||||
class="shrink"
|
||||
size="small"
|
||||
:min="game === 'chunithm' ? 0 : 1"
|
||||
:max="32"
|
||||
:use-grouping="false"
|
||||
v-model="prf.current!.data.display.monitor_index_override"
|
||||
:disabled="extraDisplayOptionsDisabled"
|
||||
:allow-empty="true"
|
||||
/>
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
</template>
|
||||
|
156
src/components/options/Keyboard.vue
Normal file
156
src/components/options/Keyboard.vue
Normal file
@ -0,0 +1,156 @@
|
||||
<script setup lang="ts">
|
||||
import SelectButton from 'primevue/selectbutton';
|
||||
import ToggleSwitch from 'primevue/toggleswitch';
|
||||
import KeyboardKey from '../KeyboardKey.vue';
|
||||
import OptionCategory from '../OptionCategory.vue';
|
||||
import OptionRow from '../OptionRow.vue';
|
||||
import { usePrfStore } from '../../stores';
|
||||
|
||||
const prf = usePrfStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OptionCategory title="Keyboard">
|
||||
<OptionRow title="Enable">
|
||||
<ToggleSwitch v-model="prf.current!.data.keyboard!.data.enabled" />
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
title="Lever mode"
|
||||
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 },
|
||||
]"
|
||||
:allow-empty="false"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
/>
|
||||
</OptionRow>
|
||||
<div
|
||||
:style="`position: relative; height: ${prf.current!.data.keyboard!.game === 'Ongeki' ? 400 : 250}px`"
|
||||
>
|
||||
<div
|
||||
class="absolute left-1/6 top-1/10"
|
||||
style="transform: translateX(-30%) translateY(-50%)"
|
||||
>
|
||||
<div class="flex flex-row gap-2 self-center w-full">
|
||||
<KeyboardKey button="test" small tooltip="Test" />
|
||||
<KeyboardKey button="svc" small tooltip="Service" />
|
||||
<KeyboardKey button="coin" small tooltip="Coin" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="prf.current?.meta.game === 'ongeki'">
|
||||
<div
|
||||
class="absolute left-1/2 top-1/2"
|
||||
style="transform: translateX(-540%) translateY(-200%)"
|
||||
>
|
||||
<KeyboardKey
|
||||
button="lmenu"
|
||||
small
|
||||
color="rgba(255, 0, 0, 0.2)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="absolute right-1/2 top-1/2"
|
||||
style="transform: translateX(540%) translateY(-200%)"
|
||||
>
|
||||
<KeyboardKey
|
||||
button="rmenu"
|
||||
small
|
||||
color="rgba(255, 255, 0, 0.2)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="absolute left-1/2 top-1/2"
|
||||
style="transform: translateX(-50%) translateY(-20%)"
|
||||
>
|
||||
<div class="flex flex-row gap-2 self-center w-full">
|
||||
<KeyboardKey
|
||||
button="lwad"
|
||||
tall
|
||||
color="rgba(180, 0, 255, 0.2)"
|
||||
/>
|
||||
<div style="width: 0.7em"></div>
|
||||
<KeyboardKey button="l1" color="rgba(255, 0, 0, 0.2)" />
|
||||
<KeyboardKey button="l2" color="rgba(0, 255, 0, 0.2)" />
|
||||
<KeyboardKey button="l3" color="rgba(0, 0, 255, 0.2)" />
|
||||
<div style="width: 0.7em"></div>
|
||||
<KeyboardKey button="r1" color="rgba(255, 0, 0, 0.2)" />
|
||||
<KeyboardKey button="r2" color="rgba(0, 255, 0, 0.2)" />
|
||||
<KeyboardKey button="r3" color="rgba(0, 0, 255, 0.2)" />
|
||||
<div style="width: 0.7em"></div>
|
||||
<KeyboardKey
|
||||
button="rwad"
|
||||
tall
|
||||
color="rgba(255, 0, 180, 0.2)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="prf.current?.meta.game === 'chunithm'">
|
||||
<div class="absolute left-1/2 top-1/5">
|
||||
<div
|
||||
class="flex flex-row flex-nowrap gap-2 self-center w-full"
|
||||
>
|
||||
<div
|
||||
v-for="idx in Array(6)
|
||||
.fill(0)
|
||||
.map((_, i) => i + 1)"
|
||||
>
|
||||
<KeyboardKey
|
||||
button="ir"
|
||||
:index="idx - 1"
|
||||
:tooltip="`ir${idx}`"
|
||||
small
|
||||
color="rgba(0, 255, 0, 0.2)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="absolute left-1/2 top-1/2"
|
||||
style="transform: translateX(-50%) translateY(-5%)"
|
||||
>
|
||||
<div
|
||||
class="flex flex-row flex-nowrap gap-2 self-center w-full"
|
||||
>
|
||||
<div
|
||||
v-for="idx in Array(16)
|
||||
.fill(0)
|
||||
.map((_, i) => 16 - i)"
|
||||
>
|
||||
<KeyboardKey
|
||||
button="cell"
|
||||
:index="idx - 1"
|
||||
:tooltip="`cell${idx}`"
|
||||
small
|
||||
color="rgba(255, 255, 0, 0.2)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style="height: 0.6em"></div>
|
||||
<div
|
||||
class="flex flex-row flex-nowrap gap-2 self-center w-full"
|
||||
>
|
||||
<div
|
||||
v-for="idx in Array(16)
|
||||
.fill(0)
|
||||
.map((_, i) => 32 - i)"
|
||||
>
|
||||
<KeyboardKey
|
||||
button="cell"
|
||||
:index="idx - 1"
|
||||
:tooltip="`cell${idx}`"
|
||||
small
|
||||
color="rgba(255, 255, 0, 0.2)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</OptionCategory>
|
||||
</template>
|
@ -13,7 +13,11 @@ const prf = usePrfStore();
|
||||
<OptionRow title="OpenSSL bug workaround for Intel ≥10th gen">
|
||||
<ToggleSwitch v-model="prf.current!.data.sgt.intel" />
|
||||
</OptionRow>
|
||||
<OptionRow title="More segatools options">
|
||||
<OptionRow
|
||||
title="More segatools options"
|
||||
tooltip="Advanced options not covered by STARTLINER"
|
||||
>
|
||||
<!-- <Button icon="pi pi-refresh" size="small" /> -->
|
||||
<FileEditor filename="segatools-base.ini" />
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
|
@ -1,15 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import Select from 'primevue/select';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import * as path from '@tauri-apps/api/path';
|
||||
import FilePicker from '../FilePicker.vue';
|
||||
import OptionCategory from '../OptionCategory.vue';
|
||||
import OptionRow from '../OptionRow.vue';
|
||||
import { invoke } from '../../invoke';
|
||||
import { usePkgStore, usePrfStore } from '../../stores';
|
||||
import { Feature } from '../../types';
|
||||
import { pkgKey } from '../../util';
|
||||
|
||||
const prf = usePrfStore();
|
||||
const pkgs = usePkgStore();
|
||||
const confirmDialog = useConfirm();
|
||||
|
||||
const names = computed(() => {
|
||||
switch (prf.current?.meta.game) {
|
||||
@ -31,18 +36,39 @@ const names = computed(() => {
|
||||
throw new Error('Option tab without a profile');
|
||||
}
|
||||
});
|
||||
|
||||
const checkSegatoolsIni = async (target: string) => {
|
||||
const iniPath = await path.join(target, '../segatools.ini');
|
||||
if (await invoke('file_exists', { path: iniPath })) {
|
||||
confirmDialog.require({
|
||||
message: 'Would you like to load the existing configuration data?',
|
||||
header: 'segatools.ini found',
|
||||
accept: async () => {
|
||||
await invoke('load_segatools_ini', { path: iniPath });
|
||||
await prf.reload();
|
||||
await emit('reload-aime-code');
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OptionCategory title="General">
|
||||
<OptionRow :title="names.exe">
|
||||
<OptionRow
|
||||
:title="names.exe"
|
||||
tooltip="STARTLINER expects unpacked executables put into otherwise clean data."
|
||||
>
|
||||
<FilePicker
|
||||
:directory="false"
|
||||
:promptname="names.exe"
|
||||
extension="exe"
|
||||
:value="prf.current!.data.sgt.target"
|
||||
:callback="
|
||||
(value: string) => (prf.current!.data.sgt.target = value)
|
||||
(value: string) => (
|
||||
(prf.current!.data.sgt.target = value),
|
||||
checkSegatoolsIni(value)
|
||||
)
|
||||
"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
@ -76,7 +102,10 @@ const names = computed(() => {
|
||||
"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
<OptionRow :title="names.hook">
|
||||
<OptionRow
|
||||
:title="names.hook"
|
||||
tooltip="Hooks can be downloaded from the package store."
|
||||
>
|
||||
<Select
|
||||
v-model="prf.current!.data.sgt.hook"
|
||||
:options="
|
||||
@ -94,7 +123,11 @@ const names = computed(() => {
|
||||
option-value="value"
|
||||
></Select>
|
||||
</OptionRow>
|
||||
<OptionRow :title="names.io" v-if="prf.current?.meta.game === 'ongeki'">
|
||||
<OptionRow
|
||||
:title="names.io"
|
||||
v-if="prf.current?.meta.game === 'ongeki'"
|
||||
tooltip="IO plugins can be downloaded from the package store."
|
||||
>
|
||||
<Select
|
||||
v-model="prf.current!.data.sgt.io"
|
||||
placeholder="segatools built-in"
|
||||
|
56
src/components/options/Startliner.vue
Normal file
56
src/components/options/Startliner.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<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';
|
||||
|
||||
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);
|
||||
},
|
||||
});
|
||||
</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>
|
||||
<OptionRow
|
||||
title="Offline mode"
|
||||
tooltip="Disables the package store. Requires a restart."
|
||||
>
|
||||
<ToggleSwitch v-model="offlineModel" />
|
||||
</OptionRow>
|
||||
<OptionRow title="Enable automatic updates">
|
||||
<ToggleSwitch v-model="updatesModel" />
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
</template>
|
@ -3,7 +3,7 @@ import {
|
||||
InvokeOptions,
|
||||
invoke as real_invoke,
|
||||
} from '@tauri-apps/api/core';
|
||||
import { message } from '@tauri-apps/plugin-dialog';
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
|
||||
export const invoke = async <T>(
|
||||
cmd: string,
|
||||
@ -14,9 +14,9 @@ export const invoke = async <T>(
|
||||
return await real_invoke(cmd, args, options);
|
||||
} catch (e: unknown) {
|
||||
if (typeof e === 'string') {
|
||||
await message(`${cmd}: ${e}`, {
|
||||
title: `Error`,
|
||||
kind: 'error',
|
||||
emit('invoke-error', {
|
||||
message: e,
|
||||
header: `${cmd} failed`,
|
||||
});
|
||||
} else {
|
||||
console.error(`Unresolved error: ${e}`);
|
||||
|
174
src/stores.ts
174
src/stores.ts
@ -2,6 +2,8 @@ import { Ref, computed, ref, watchEffect } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
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 { invoke, invoke_nopopup } from './invoke';
|
||||
import { Dirs, Feature, Game, Package, Profile, ProfileMeta } from './types';
|
||||
import { changePrimaryColor, hasFeature, pkgKey } from './util';
|
||||
@ -102,6 +104,10 @@ export const usePkgStore = defineStore('pkg', {
|
||||
),
|
||||
byFeature: (state) => (feature: Feature) =>
|
||||
Object.values(state.pkg).filter((p) => hasFeature(p, feature)),
|
||||
hasAvailableUpdates: (state) =>
|
||||
Object.values(state.pkg).some(
|
||||
(p) => p.loc && (p.rmt?.version ?? 0) > p.loc.version
|
||||
),
|
||||
},
|
||||
actions: {
|
||||
setupListeners() {
|
||||
@ -170,6 +176,36 @@ export const usePkgStore = defineStore('pkg', {
|
||||
}
|
||||
await this.reloadAll();
|
||||
},
|
||||
|
||||
async install(pkg: Package | undefined) {
|
||||
if (pkg === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await invoke('install_package', {
|
||||
key: pkgKey(pkg),
|
||||
force: true,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (pkg !== undefined) {
|
||||
pkg.js.busy = false;
|
||||
}
|
||||
}
|
||||
|
||||
//if (rv === 'Deferred') { /* download progress */ }
|
||||
},
|
||||
|
||||
async updateAll() {
|
||||
const list = [];
|
||||
for (const pkg of this.allLocal) {
|
||||
if (pkg.rmt && pkg.rmt.version > pkg.loc!.version) {
|
||||
list.push(this.install(pkg));
|
||||
}
|
||||
}
|
||||
await Promise.all(list);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -186,6 +222,12 @@ export const usePrfStore = defineStore('prf', () => {
|
||||
current.value?.data.mods.includes(pkgKey(pkg))
|
||||
);
|
||||
|
||||
const isPkgKeyEnabled = (pkg: string) =>
|
||||
computed(
|
||||
() =>
|
||||
current.value !== null && current.value?.data.mods.includes(pkg)
|
||||
);
|
||||
|
||||
const reload = async () => {
|
||||
const p = (await invoke('get_current_profile')) as Profile;
|
||||
current.value = p;
|
||||
@ -286,6 +328,7 @@ export const usePrfStore = defineStore('prf', () => {
|
||||
current,
|
||||
list,
|
||||
isPkgEnabled,
|
||||
isPkgKeyEnabled,
|
||||
reload,
|
||||
create,
|
||||
rename,
|
||||
@ -295,3 +338,134 @@ export const usePrfStore = defineStore('prf', () => {
|
||||
configDir,
|
||||
};
|
||||
});
|
||||
|
||||
export const useClientStore = defineStore('client', () => {
|
||||
type ScaleType = 's' | 'm' | 'l' | 'xl';
|
||||
const scaleFactor: Ref<ScaleType> = ref('s');
|
||||
const timeout: Ref<NodeJS.Timeout | null> = ref(null);
|
||||
|
||||
const offlineMode = ref(false);
|
||||
const enableAutoupdates = ref(true);
|
||||
|
||||
const scaleValue = (value: ScaleType) =>
|
||||
value === 's' ? 1 : value === 'm' ? 1.25 : value === 'l' ? 1.5 : 2;
|
||||
|
||||
const setScaleFactor = async (value: ScaleType) => {
|
||||
scaleFactor.value = value;
|
||||
|
||||
const window = getCurrentWindow();
|
||||
const w = Math.floor(scaleValue(value) * 900);
|
||||
const h = Math.floor(scaleValue(value) * 480);
|
||||
|
||||
let size = await window.innerSize();
|
||||
|
||||
window.setMinSize(new PhysicalSize(w, h));
|
||||
window.setSize(
|
||||
new PhysicalSize(Math.max(w, size.width), Math.max(h, size.height))
|
||||
);
|
||||
};
|
||||
|
||||
const scaleModel = computed({
|
||||
get() {
|
||||
return scaleFactor;
|
||||
},
|
||||
async set(value: ScaleType) {
|
||||
await setScaleFactor(value);
|
||||
await save();
|
||||
},
|
||||
});
|
||||
|
||||
const load = async () => {
|
||||
const generalStore = useGeneralStore();
|
||||
try {
|
||||
const input = JSON.parse(
|
||||
await readTextFile(
|
||||
await path.join(
|
||||
generalStore.configDir,
|
||||
'client-options.json'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if (input.windowSize) {
|
||||
getCurrentWindow().setSize(
|
||||
new PhysicalSize(input.windowSize.w, input.windowSize.h)
|
||||
);
|
||||
}
|
||||
|
||||
if (input.scaleFactor) {
|
||||
await setScaleFactor(input.scaleFactor);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error reading client options: ${e}`);
|
||||
}
|
||||
|
||||
offlineMode.value = await invoke('get_global_config', {
|
||||
field: 'OfflineMode',
|
||||
});
|
||||
|
||||
enableAutoupdates.value = await invoke('get_global_config', {
|
||||
field: 'EnableAutoupdates',
|
||||
});
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
const generalStore = useGeneralStore();
|
||||
const w = getCurrentWindow();
|
||||
const size = await w.innerSize();
|
||||
|
||||
await writeTextFile(
|
||||
await path.join(generalStore.configDir, 'client-options.json'),
|
||||
JSON.stringify({
|
||||
scaleFactor: scaleFactor.value,
|
||||
windowSize: {
|
||||
w: Math.floor(size.width),
|
||||
h: Math.floor(size.height),
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const queueSave = async () => {
|
||||
if (timeout.value !== null) {
|
||||
clearTimeout(timeout.value);
|
||||
}
|
||||
timeout.value = setTimeout(async () => {
|
||||
timeout.value = null;
|
||||
await save();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const setOfflineMode = async (value: boolean) => {
|
||||
offlineMode.value = value;
|
||||
await invoke('set_global_config', { field: 'OfflineMode', value });
|
||||
};
|
||||
|
||||
const setAutoupdates = async (value: boolean) => {
|
||||
enableAutoupdates.value = value;
|
||||
await invoke('set_global_config', {
|
||||
field: 'EnableAutoupdates',
|
||||
value,
|
||||
});
|
||||
};
|
||||
|
||||
getCurrentWindow().onResized(async ({ payload }) => {
|
||||
// For whatever reason this is 0 when minimized
|
||||
if (payload.width > 0) {
|
||||
await queueSave();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
scaleFactor,
|
||||
offlineMode,
|
||||
enableAutoupdates,
|
||||
timeout,
|
||||
scaleModel,
|
||||
load,
|
||||
save,
|
||||
queueSave,
|
||||
setOfflineMode,
|
||||
setAutoupdates,
|
||||
};
|
||||
});
|
||||
|
62
src/types.ts
62
src/types.ts
@ -30,13 +30,17 @@ export enum Feature {
|
||||
Mu3Hook = 1 << 3,
|
||||
Mu3IO = 1 << 4,
|
||||
ChusanHook = 1 << 5,
|
||||
ChuniIO = 1 << 6,
|
||||
Mempatcher = 1 << 7,
|
||||
GameDLL = 1 << 8,
|
||||
AmdDLL = 1 << 9,
|
||||
}
|
||||
|
||||
export type Status =
|
||||
| 'Unchecked'
|
||||
| 'Unsupported'
|
||||
| {
|
||||
OK: Feature;
|
||||
OK: [Feature, String, String];
|
||||
};
|
||||
|
||||
export type Game = 'ongeki' | 'chunithm';
|
||||
@ -53,6 +57,10 @@ export interface ProfileData {
|
||||
network: NetworkConfig;
|
||||
bepinex: BepInExConfig;
|
||||
mu3_ini: Mu3IniConfig | undefined;
|
||||
keyboard: KeyboardConfig | undefined;
|
||||
patches: {
|
||||
[key: string]: 'enabled' | { number: number } | { hex: Int8Array };
|
||||
};
|
||||
}
|
||||
|
||||
export interface SegatoolsConfig {
|
||||
@ -69,15 +77,18 @@ export interface SegatoolsConfig {
|
||||
addr: string;
|
||||
physical: boolean;
|
||||
};
|
||||
aime_port: number;
|
||||
}
|
||||
|
||||
export interface DisplayConfig {
|
||||
target: String;
|
||||
rez: [number, number];
|
||||
mode: 'Window' | 'Borderless' | 'Fullscreen';
|
||||
rotation: number;
|
||||
rotation: number | null;
|
||||
frequency: number;
|
||||
borderless_fullscreen: boolean;
|
||||
dont_switch_primary: boolean;
|
||||
monitor_index_override: number | null;
|
||||
}
|
||||
|
||||
export interface NetworkConfig {
|
||||
@ -99,6 +110,43 @@ export interface Mu3IniConfig {
|
||||
// blacklist?: [number, number];
|
||||
}
|
||||
|
||||
export interface OngekiButtons {
|
||||
use_mouse: boolean;
|
||||
enabled: boolean;
|
||||
coin: number;
|
||||
svc: number;
|
||||
test: number;
|
||||
lmenu: number;
|
||||
rmenu: number;
|
||||
l1: number;
|
||||
l2: number;
|
||||
l3: number;
|
||||
r1: number;
|
||||
r2: number;
|
||||
r3: number;
|
||||
lwad: number;
|
||||
rwad: number;
|
||||
}
|
||||
|
||||
export interface ChunithmButtons {
|
||||
enabled: boolean;
|
||||
coin: number;
|
||||
svc: number;
|
||||
test: number;
|
||||
cell: number[];
|
||||
ir: number[];
|
||||
}
|
||||
|
||||
export type KeyboardConfig =
|
||||
| {
|
||||
game: 'Ongeki';
|
||||
data: OngekiButtons;
|
||||
}
|
||||
| {
|
||||
game: 'Chunithm';
|
||||
data: ChunithmButtons;
|
||||
};
|
||||
|
||||
export interface Profile {
|
||||
meta: ProfileMeta;
|
||||
data: ProfileData;
|
||||
@ -111,3 +159,13 @@ export interface Dirs {
|
||||
data_dir: string;
|
||||
cache_dir: string;
|
||||
}
|
||||
|
||||
export interface Patch {
|
||||
id: string;
|
||||
name: string;
|
||||
tooltip: string;
|
||||
type: undefined | 'number';
|
||||
default: number;
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
@ -52,6 +52,10 @@ export const hasFeature = (pkg: Package | undefined, feature: Feature) => {
|
||||
pkg.loc !== null &&
|
||||
pkg.loc !== undefined &&
|
||||
typeof pkg.loc?.status !== 'string' &&
|
||||
pkg.loc.status.OK & feature
|
||||
pkg.loc.status.OK[0] & feature
|
||||
);
|
||||
};
|
||||
|
||||
export const messageSplit = (message: any) => {
|
||||
return message.message?.split('\n');
|
||||
};
|
||||
|
Reference in New Issue
Block a user