Compare commits
259 Commits
patching
...
v1.0.22-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01d12178ba | ||
|
|
b67e0992f1 | ||
|
|
a949754dce | ||
|
|
cf536b853a | ||
|
|
19c8537e91 | ||
|
|
8a62a5a8c7 | ||
|
|
d56c6bc069 | ||
|
|
ea73feb8e9 | ||
|
|
abf7a428f6 | ||
|
|
7bd3493366 | ||
|
|
20a80e5625 | ||
|
|
4a8db8eb0f | ||
|
|
f44dfeb79d | ||
|
|
098ba63066 | ||
|
|
b8255eb5bc | ||
|
|
812aa64c5e | ||
|
|
b3aaf5c5f7 | ||
|
|
9e6029faf3 | ||
|
|
b18f7a0cf6 | ||
|
|
62a97d86cb | ||
|
|
55fa0a8d3c | ||
|
|
2aac7fc022 | ||
|
|
ddaf24590c | ||
|
|
44f5cac2bf | ||
|
|
e1a519cc5f | ||
|
|
3dd1ec2674 | ||
|
|
438596f4e2 | ||
|
|
af5c9229d4 | ||
|
|
fb842ccc86 | ||
|
|
27d30a183c | ||
|
|
182943fd69 | ||
|
|
b352b16dc9 | ||
|
|
b1b18c75a1 | ||
|
|
7c53669fc4 | ||
|
|
249f07927d | ||
|
|
090e1a888d | ||
|
|
5dfa98c9ea | ||
|
|
088d007816 | ||
|
|
d49fdacf2a | ||
|
|
8efd601eda | ||
|
|
f2e3c69c0f | ||
|
|
85a16e3407 | ||
|
|
596c5b85b1 | ||
|
|
1115283df8 | ||
|
|
696a8a1dec | ||
|
|
1be06b0a6c | ||
|
|
7f71549831 | ||
|
|
08d9db05a5 | ||
|
|
cd2b8aee12 | ||
|
|
ec611f9d7d | ||
|
|
d6b1c7a613 | ||
|
|
9b1d3594ed | ||
|
|
3848c39743 | ||
|
|
7831bd48dc | ||
|
|
86d15cd335 | ||
|
|
bf880ac297 | ||
|
|
e1637cebef | ||
|
|
d7f3218657 | ||
|
|
0dede9b17c | ||
|
|
ec7b0904d2 | ||
|
|
937faf85e2 | ||
|
|
6c40044931 | ||
|
|
3d2425fb3d | ||
|
|
f7c8059f4c | ||
|
|
6206b6762a | ||
|
|
258c656815 | ||
|
|
68404f5e39 | ||
|
|
8c75541939 | ||
|
|
aa32782179 | ||
|
|
2cda891f02 | ||
|
|
9c8ed852d1 | ||
|
|
e75f65ab56 | ||
|
|
bdeda36043 | ||
|
|
151e7c3919 | ||
|
|
e6365b5592 | ||
|
|
ed76a086b6 | ||
|
|
c4526e2ff6 | ||
|
|
80c28669e1 | ||
|
|
5e48a4772e | ||
|
|
10234bed5a | ||
|
|
22d8a23386 | ||
|
|
6ef3e86820 | ||
|
|
0087801f83 | ||
|
|
55be3ebc2b | ||
|
|
7cf1b198ff | ||
|
|
fb231acaa6 | ||
|
|
827b8942c9 | ||
|
|
0fd4376e0d | ||
|
|
d6c5463619 | ||
|
|
9ade56dc6f | ||
|
|
484cd36565 | ||
|
|
1c27ae172e | ||
|
|
541352c3fd | ||
|
|
bfbf3e77a2 | ||
|
|
404e946e7c | ||
|
|
8077285e79 | ||
|
|
94c1bfd104 | ||
|
|
ed473ad659 | ||
|
|
b624ef693e | ||
|
|
c10a5cd82f | ||
|
|
25c6a70dc0 | ||
|
|
86c595abda | ||
|
|
35e6144733 | ||
|
|
565f229dac | ||
|
|
9bd4b9ccaf | ||
|
|
6750787bf9 | ||
|
|
c0770606ae | ||
|
|
4e03fec2a0 | ||
|
|
e7809be97c | ||
|
|
782e350ae5 | ||
|
|
6eab66032b | ||
|
|
c1842722b4 | ||
|
|
31aef02d5f | ||
|
|
bcdbb2ba06 | ||
|
|
ff8f35c52a | ||
|
|
a7188828fa | ||
|
|
db6917df5d | ||
|
|
8268c127a9 | ||
|
|
8fd5b895af | ||
|
|
10b9141815 | ||
|
|
d7783c5936 | ||
|
|
27122cd399 | ||
|
|
49740470ac | ||
|
|
7b693d7758 | ||
|
|
03439c3757 | ||
|
|
e83ae64714 | ||
|
|
3ecd13d1c3 | ||
|
|
28701ba007 | ||
|
|
3a84825cf9 | ||
|
|
4de8a43c3a | ||
|
|
d28e0a1bc8 | ||
|
|
bf8de40caa | ||
|
|
8e7d6ee420 | ||
|
|
d6e9bb100b | ||
|
|
374c6abbfc | ||
|
|
a728e8ba2c | ||
|
|
4371804429 | ||
|
|
fa38a22117 | ||
|
|
a7914406b4 | ||
|
|
57c1a7800c | ||
|
|
9426937a62 | ||
|
|
96d69d9ff6 | ||
|
|
defc1b43bd | ||
|
|
01fce477ef | ||
|
|
2f610ce0af | ||
|
|
d9772d9ffb | ||
|
|
569c465a50 | ||
|
|
8a5e66be48 | ||
|
|
e633f7dad7 | ||
|
|
bffd089192 | ||
|
|
bf3d892af5 | ||
|
|
424dd4ff1c | ||
|
|
93097db5cc | ||
|
|
c06aa9f885 | ||
|
|
f7bcad0a90 | ||
|
|
7120f846f7 | ||
|
|
f6ed37d2e4 | ||
|
|
99b210b3be | ||
|
|
7d52766c07 | ||
|
|
212d974ac6 | ||
|
|
a6e06e3005 | ||
|
|
56c1f2dcc2 | ||
|
|
a843888cb8 | ||
|
|
281bfb5cea | ||
|
|
79891238b6 | ||
|
|
0971f5b826 | ||
|
|
7e5f3be4fa | ||
|
|
e29e269c4c | ||
|
|
8f8e37aff3 | ||
|
|
6257a2e68c | ||
|
|
203bd40e8f | ||
|
|
c64cdababa | ||
|
|
1ecd38ee9f | ||
|
|
26292984a2 | ||
|
|
cffbcdae96 | ||
|
|
2026e2f896 | ||
|
|
9566beaf29 | ||
|
|
c99080168c | ||
|
|
9fa3351747 | ||
|
|
75f1eef587 | ||
|
|
43a6348b7a | ||
|
|
cc74107dfe | ||
|
|
afa40f437f | ||
|
|
95282a3f36 | ||
|
|
0331bb5faf | ||
|
|
0551f3e6a0 | ||
|
|
412acdd317 | ||
|
|
c42c708db5 | ||
|
|
552d612e7c | ||
|
|
5077c19fdc | ||
|
|
36c2302f1b | ||
|
|
a06a8af7df | ||
|
|
975b04fd0e | ||
|
|
811f437238 | ||
|
|
a25645ef77 | ||
|
|
ac8fd3af45 | ||
|
|
4f3952aeb1 | ||
|
|
c0740417e3 | ||
|
|
8700a77ba0 | ||
|
|
95267720a4 | ||
|
|
d97e5c192f | ||
|
|
a9d9d361e1 | ||
|
|
b78d9c28c9 | ||
|
|
f946cedb4d | ||
|
|
c659979851 | ||
|
|
940943b106 | ||
|
|
7ad1c4649c | ||
|
|
011b15c8d9 | ||
|
|
d28af907ec | ||
|
|
1b076ccea9 | ||
|
|
818896c734 | ||
|
|
3b6225d5f0 | ||
|
|
041a6cb768 | ||
|
|
acb6de85ad | ||
|
|
cd628b4f3d | ||
|
|
19d939a074 | ||
|
|
aeaa7ef76c | ||
|
|
7659e9831a | ||
|
|
22a416ebd8 | ||
|
|
dcc9749967 | ||
|
|
7f9ba66e38 | ||
|
|
f260379fa2 | ||
|
|
544c39168b | ||
|
|
eb9aa34323 | ||
|
|
e9df0f17db | ||
|
|
bf2ae51fb0 | ||
|
|
dc20fe5916 | ||
|
|
1a6ed38f8f | ||
|
|
d23d5e3806 | ||
|
|
365a4f2888 | ||
|
|
e270c886db | ||
|
|
18a1b0e94c | ||
|
|
3799ec648d | ||
|
|
8ff06f6d29 | ||
|
|
6ff1ef932c | ||
|
|
9e29135376 | ||
|
|
109f98db66 | ||
|
|
69201bc8b1 | ||
|
|
656fa2cfe3 | ||
|
|
1588bee5a3 | ||
|
|
75b79d0202 | ||
|
|
6d9f1af134 | ||
|
|
d38459bb8a | ||
|
|
c7954d2294 | ||
|
|
adbb8e380d | ||
|
|
4ff9e88185 | ||
|
|
27a10c58ca | ||
|
|
44b148f2a4 | ||
|
|
6434814d1d | ||
|
|
043f3e7ce4 | ||
|
|
61ac332cee | ||
|
|
a1284cc2ff | ||
|
|
cf82e9e892 | ||
|
|
bb1874d64a | ||
|
|
85c0e2473b | ||
|
|
9e3b584608 | ||
|
|
5568183821 | ||
|
|
d64186777f | ||
|
|
f2d45d2359 |
@@ -2,22 +2,9 @@ root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = crlf
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = false
|
||||
max_line_length = 120
|
||||
tab_width = 2
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.rs]
|
||||
max_line_length = 100
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{*.ats,*.cts,*.mts,*.ts}]
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
@@ -1,59 +1,40 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", "prettier"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
2
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"single"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"@typescript-eslint/ban-types": [
|
||||
"warn",
|
||||
{
|
||||
"extendDefaults": true,
|
||||
"types": {
|
||||
"{}": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["react", "@typescript-eslint"],
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-types": [
|
||||
"warn",
|
||||
{
|
||||
"extendDefaults": true,
|
||||
"types": {
|
||||
"{}": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
src-tauri/keys/* binary
|
||||
9
.github/workflows/backend-checks.yml
vendored
@@ -3,12 +3,12 @@ name: Check backend
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/backend-checks.yml"
|
||||
- "src-tauri/**"
|
||||
- '.github/workflows/backend-checks.yml'
|
||||
- 'src-tauri/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/backend-checks.yml"
|
||||
- "src-tauri/**"
|
||||
- '.github/workflows/backend-checks.yml'
|
||||
- 'src-tauri/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}-${{ github.workflow }}
|
||||
@@ -59,4 +59,3 @@ jobs:
|
||||
name: clippy (${{ runner.os }})
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --manifest-path ./src-tauri/Cargo.toml --no-default-features -- -D warnings
|
||||
|
||||
|
||||
85
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/build.yml'
|
||||
- 'src-tauri/**/*'
|
||||
- 'src/**/*'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/build.yml'
|
||||
- 'src-tauri/**/*'
|
||||
- 'src/**/*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}-${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-win:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Install deps and build
|
||||
run: yarn && yarn build --debug
|
||||
|
||||
- name: Compress build
|
||||
uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: src-tauri/target/debug/lang/ src-tauri/target/debug/keys/ src-tauri/target/debug/patch/ src-tauri/target/debug/Cultivation.exe src-tauri/target/debug/bundle/msi/
|
||||
recursive: true
|
||||
dest: Cultivation.zip
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: CultivationWin
|
||||
path: Cultivation.zip
|
||||
|
||||
build-ubuntu:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Install libraries
|
||||
run: sudo apt install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
|
||||
|
||||
- name: Install deps and build
|
||||
run: yarn && yarn build --debug
|
||||
|
||||
- name: Compress build
|
||||
uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: src-tauri/target/debug/lang/ src-tauri/target/debug/keys/ src-tauri/target/debug/patch/ src-tauri/target/debug/cultivation
|
||||
recursive: true
|
||||
dest: Cultivation.zip
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: CultivationLinux
|
||||
path: Cultivation.zip
|
||||
27
.github/workflows/frontend-checks.yml
vendored
@@ -2,36 +2,31 @@ name: Check frontend
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/frontend-checks.yml"
|
||||
- "src/**"
|
||||
- ".eslintrc.json"
|
||||
- "package.json"
|
||||
- "tsconfig.json"
|
||||
- "yarn.lock"
|
||||
paths-ignore:
|
||||
- '**.lock'
|
||||
- '**.rs'
|
||||
- '**.toml'
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/frontend-checks.yml"
|
||||
- "src/**"
|
||||
- ".eslintrc.json"
|
||||
- "package.json"
|
||||
- "tsconfig.json"
|
||||
- "yarn.lock"
|
||||
paths-ignore:
|
||||
- '**.lock'
|
||||
- '**.rs'
|
||||
- '**.toml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}-${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tsc-eslint-checks:
|
||||
prettier-tsc-eslint-checks:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install modules
|
||||
run: yarn
|
||||
- name: Run prettier
|
||||
run: yarn prettier --check .
|
||||
- name: Run tsc
|
||||
run: yarn tsc --noEmit
|
||||
- name: Run ESLint
|
||||
run: yarn eslint src
|
||||
|
||||
|
||||
1
.gitignore
vendored
@@ -17,6 +17,7 @@
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.vs
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
|
||||
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
yarn lint-staged --allow-empty
|
||||
4
.lintstagedrc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"src-tauri/**/*.rs": "rustfmt --edition 2021",
|
||||
"*": "yarn prettier --write --ignore-unknown"
|
||||
}
|
||||
4
.prettierignore
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
src-tauri/resources/
|
||||
src-tauri/target/
|
||||
src-tauri/WixTools/
|
||||
4
.prettierrc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
121
README.md
@@ -1,64 +1,115 @@
|
||||
# Cient Patching Notice
|
||||
For game versions 2.8 and above, Cultivation automatically makes a small patch to your game client when launching using Grasscutter, and restores it upon closing the game. In theory, you should still be totally safe, however it would be dishonest to not explicitly state that **modifying the game client could, theoretically, lead to a ban if you connect to official servers with it**. It is extremely unlikely AND there are no instances known of it happening, but the possibility exists.
|
||||
EN | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) |
|
||||
|
||||
# Cultivation
|
||||
|
||||
A game launcher designed to easily proxy traffic from anime game to private servers.
|
||||
|
||||
While the Cultivation repository is **open**. This does **not** mean it has released.
|
||||
Please do **NOT install, download, or use pre-compiled versions of Cultivation found elsewhere**. Only use releases from this GitHub repository.
|
||||
|
||||
# Table Of Contents
|
||||
* [Download](#download)
|
||||
* [Developer Quick-start](#developer-quickstart)
|
||||
* [Setup](#setup)
|
||||
* [Building](#building)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* [Theming](#theming)
|
||||
|
||||
- [Client Patching Notice](#client-patching-notice)
|
||||
- [Download](#download)
|
||||
- [Setup](#setup)
|
||||
- [Developer Quick-start](#developer-quickstart)
|
||||
- [Setup](#setup)
|
||||
- [Building](#building)
|
||||
- [Code Formatting and Linting](#code-formatting-and-linting)
|
||||
- [Generating Update Artifacts](#generating-update-artifacts)
|
||||
- [Theming](#theming)
|
||||
- [Screenshots](#screenshots)
|
||||
- [Credits](#credits)
|
||||
|
||||
# Client Patching Notice - RSA
|
||||
|
||||
For game versions 3.1 and above, Cultivation automatically makes a small patch to your game client when launching using Grasscutter, and restores it upon closing the game. In theory, you should still be totally safe, however it would be dishonest to not explicitly state that **modifying the game client could, theoretically, lead to a ban if you connect to official servers with it**. It is extremely unlikely AND there are no instances known of it happening, but the possibility exists.
|
||||
|
||||
# Download
|
||||
|
||||
[Find release builds here!](https://github.com/Grasscutters/Cultivation/releases)
|
||||
|
||||
Once downloaded, extract somewhere and open as administrator.
|
||||
Download and open the MSI, and once installed, run Cultivation as administrator. Refer below for more [detailed setup instructions](#setup).
|
||||
|
||||
**Windows 7 Users:** You will need to download [WebView](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section) manually, and download the `.zip` instead of the `.msi`.
|
||||
|
||||
# Setup
|
||||
|
||||
5-minute video for those who don't like to/cannot read: https://youtu.be/e0irOYbQe7I
|
||||
|
||||
- Download Cultivation
|
||||
- If you are on Windows 10 or 11, use the MSI
|
||||
- If you are on Windows 7, or the MSI doesn't work, use the zip and download [WebView](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)
|
||||
- If you are on Linux or MacOS, [help us port Windows-specific system calls to Linux/MacOS!](https://github.com/Grasscutters/Cultivation/issues/7)
|
||||
- Install or extract Cultivation
|
||||
- Open Cultivation **_as administrator_**
|
||||
- Before clicking randomly on stuff, in options (top right cog icon), set your Game Install Path.
|
||||
- If you are using an existing server installation from somewhere else, you can set the `.jar` file in settings as well. All downloads made through Culti will automatically use that path, no additional config needed.
|
||||
- If you use multiple Java versions, you can set the Java path to your Java 17 installation (only required if you are running your own server)
|
||||
- Decide if you want to download your own server, or just join a public one
|
||||
- If joining a public one, you're done. Just click "Connect with Grasscutter" and input the address and port. You do not have to continue these instructions.
|
||||
- If you are getting System Error, or 4214, ask the [Discord support channels](https://discord.gg/grasscutter)
|
||||
- Open the "Downloads" menu (top right)
|
||||
- Download "Grasscutter All-in-One" (top of the list)
|
||||
- Once that is done, click the icon next to "Launch"
|
||||
- To play on your new server:
|
||||
- Click "Connect with Grasscutter"
|
||||
- Input `localhost` as the address, and `443` as the port
|
||||
- Ensure HTTPS is disabled
|
||||
- Any generic "I am getting XYZ error!" should go in the [Discord support channels](https://discord.gg/grasscutter)
|
||||
- Any specific Cultivation issues should go in [the issues section](/issues)
|
||||
- Any Grasscutter server related issues should go in [the Grasscutter issues section](https://github.com/Grasscutters/Grasscutter)
|
||||
|
||||
# Developer Quickstart
|
||||
|
||||
### Setup
|
||||
* Install [NodeJS >12](https://nodejs.org/en/)
|
||||
* Install [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#debian-stable) (cry about it `npm` lovers)
|
||||
* Install [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) & [Rust compiler](https://www.rust-lang.org/tools/install)
|
||||
* `yarn install`
|
||||
* `yarn start:dev`
|
||||
|
||||
- Install [NodeJS >12](https://nodejs.org/en/)
|
||||
- Install [yarn](https://classic.yarnpkg.com/lang/en/docs/install) (cry about it `npm` lovers)
|
||||
- Install [Rust](https://www.rust-lang.org/tools/install)
|
||||
- `yarn install`
|
||||
- `yarn start:dev`
|
||||
|
||||
### Building
|
||||
`npm run build` or `yarn build`
|
||||
|
||||
Add `--release` or `--debug` depending on what release you are creating. This defaults to `--release`
|
||||
For a release build,
|
||||
|
||||
- `yarn build`
|
||||
|
||||
For a debug build,
|
||||
|
||||
- `yarn build --debug`
|
||||
|
||||
### Code Formatting and Linting
|
||||
|
||||
Format the code with `npm format` or `yarn format`. Run the lints with `npm lint` or `yarn lint`.
|
||||
Formatting:
|
||||
|
||||
### Updating
|
||||
* Add the `TAURI_PRIVATE_KEY` as an environment variable with a path to your private key.
|
||||
* Add the `TAURI_KEY_PASSWORD` as an environment variable with the password for your private key.
|
||||
* Run `npm run update` or `yarn build`
|
||||
* The update will be in `src-tauri/target/(release|debug)/msi/Cultivation_X.X.X_x64_xx-XX.msi.zip`
|
||||
- `yarn format`
|
||||
|
||||
# Troubleshooting
|
||||
TODO. Collect common issues before updating.
|
||||
Check Lints, fix (some) lints:
|
||||
|
||||
- `yarn lint`, `yarn lint:fix`
|
||||
|
||||
### Generating Update Artifacts
|
||||
|
||||
- Add the `TAURI_PRIVATE_KEY` as an environment variable with a path to your private key.
|
||||
- Add the `TAURI_KEY_PASSWORD` as an environment variable with the password for your private key.
|
||||
- `yarn build`
|
||||
|
||||
The update will be at `src-tauri/target/(release|debug)/msi/Cultivation_X.X.X_x64_xx-XX.msi.zip`
|
||||
|
||||
# Theming
|
||||
|
||||
A full theming reference can be found [here!](/THEMES.md)
|
||||
|
||||
# Screenshots
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Credits
|
||||
* [SpikeHD](https://github.com/SpikeHD): For originally creating **GrassClipper** and creating the amazing UI of Cultivation.
|
||||
* [KingRainbow44](https://github.com/KingRainbow44): For building a proxy daemon from scratch and integrating it with Cultivation.
|
||||
* [Tauri](https://tauri.app): For providing an amazing, efficient, and simple desktop application framework/library.
|
||||
|
||||
- [SpikeHD](https://github.com/SpikeHD): For originally creating **GrassClipper** and creating the amazing UI of Cultivation.
|
||||
- [KingRainbow44](https://github.com/KingRainbow44): For building a proxy daemon from scratch and integrating it with Cultivation.
|
||||
- [Benj](https://github.com/4Benj): For assistance in client patching.
|
||||
- [lilmayofuksu](https://github.com/lilmayofuksu): For assistance in client patching.
|
||||
- [Tauri](https://tauri.app): For providing an amazing, efficient, and simple desktop application framework/library.
|
||||
|
||||
87
README_zh-CN.md
Normal file
@@ -0,0 +1,87 @@
|
||||
[EN](README.md) | 简中 | [繁中](README_zh-TW.md)
|
||||
|
||||
# Cultivation
|
||||
|
||||
一个游戏启动器,旨在轻松将某动漫游戏的流量代理到私人服务器。
|
||||
|
||||
虽然此存储库是**开放的**。 但这**并不**意味着它已经发布。
|
||||
请不要**安装、下载或使用在其他地方找到的预编译版本的 Cultivation**。 仅使用此 GitHub 存储库中的版本。
|
||||
|
||||
# 目录
|
||||
|
||||
- [下载](#下载)
|
||||
- [开发人员快速入门](#开发人员快速入门)
|
||||
- [安装](#安装)
|
||||
- [编译](#编译)
|
||||
- [代码格式化与纠错](#代码格式化与纠错)
|
||||
- [生成更新项目](#生成更新项目)
|
||||
- [启动器主题](#启动器主题)
|
||||
- [画面](#画面)
|
||||
- [成员](#成员)
|
||||
|
||||
# 客户端修补通知
|
||||
|
||||
对于游戏版本为 3.1 及以上时,使用 Grasscutter 启动时,Cultivation 会自动为您的游戏客户端制作一个小补丁,并在关闭游戏时恢复它。 从理论上讲,你应该是完全安全的,但是不明确**如果您使用它连接到官方服务器,修改游戏客户端可能会导致封号**,但可能性是非常小的,并且从未接到发生过此类情况的问题,但存在这种可能性!
|
||||
|
||||
# 下载
|
||||
|
||||
[在此处查找发布版本!](https://github.com/Grasscutters/Cultivation/releases)
|
||||
|
||||
下载后,从某个位置解压缩并以管理员身份打开。
|
||||
|
||||
# 开发人员快速入门
|
||||
|
||||
### 安装
|
||||
|
||||
- 安装 [NodeJS >12](https://nodejs.org/en/)
|
||||
- 安装 [yarn](https://classic.yarnpkg.com/lang/en/docs/install)
|
||||
- 安装 [Rust](https://www.rust-lang.org/tools/install)
|
||||
- `yarn install`
|
||||
- `yarn start:dev`
|
||||
|
||||
### 编译
|
||||
|
||||
发布版本,
|
||||
|
||||
- `yarn build`
|
||||
|
||||
调试版本,
|
||||
|
||||
- `yarn build --debug`
|
||||
|
||||
### 代码格式化与纠错
|
||||
|
||||
格式化:
|
||||
|
||||
- `yarn format`
|
||||
|
||||
纠错, 修复(一些)错误:
|
||||
|
||||
- `yarn lint`, `yarn lint:fix`
|
||||
|
||||
### 生成更新项目
|
||||
|
||||
- 将 `TAURI_PRIVATE_KEY` 添加到环境变量,其中包含私钥的路径。
|
||||
- 将 `TAURI_KEY_PASSWORD` 添加到环境变量,其中包含私钥的密码。
|
||||
- `yarn build`
|
||||
|
||||
更新将生成在 `src-tauri/target/(release|debug)/msi/Cultivation_X.X.X_x64_xx-XX.msi.zip`
|
||||
|
||||
# 启动器主题
|
||||
|
||||
完整的主题参考可以[在这里找到!](/THEMES.md)
|
||||
|
||||
# 画面
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 成员
|
||||
|
||||
- [SpikeHD](https://github.com/SpikeHD): For originally creating **GrassClipper** and creating the amazing UI of Cultivation.
|
||||
- [KingRainbow44](https://github.com/KingRainbow44): For building a proxy daemon from scratch and integrating it with Cultivation.
|
||||
- [Benj](https://github.com/4Benj): For assistance in client patching.
|
||||
- [lilmayofuksu](https://github.com/lilmayofuksu): For assistance in client patching.
|
||||
- [Tauri](https://tauri.app): For providing an amazing, efficient, and simple desktop application framework/library.
|
||||
87
README_zh-TW.md
Normal file
@@ -0,0 +1,87 @@
|
||||
[EN](README.md) | [简中](README_zh-CN.md) | 繁中
|
||||
|
||||
# 客戶端修補通知
|
||||
|
||||
對於遊戲版本為 3.1 及以上時,使用 Grasscutter 啟動時,Cultivation 會自動為您的遊戲客戶端製作一個小修補,並在關閉遊戲時恢復它。 從理論上講,你應該是完全安全的,但是不明確**如果您使用它連接到官方伺服器,修改遊戲客戶端可能會導致封號**,但可能性是非常小的,並且從未接到發生過此類情況的問題,但存在這種可能性!
|
||||
|
||||
# Cultivation
|
||||
|
||||
一個遊戲啟動器,旨在輕松將某動漫遊戲的流量代理到私人伺服器。
|
||||
|
||||
雖然此存儲庫是**開放的**。 但這**並不**意味著它已經發布。
|
||||
請不要**安裝、下載或使用在其他地方找到的預編譯版本的 Cultivation**。 僅使用此 GitHub 存儲庫中的版本。
|
||||
|
||||
# 目錄
|
||||
|
||||
- [下載](#下載)
|
||||
- [開發人員快速入門](#開發人員快速入門)
|
||||
- [安裝](#安裝)
|
||||
- [編譯](#編譯)
|
||||
- [代碼格式化與糾錯](#代碼格式化與糾錯)
|
||||
- [生成更新項目](#生成更新項目)
|
||||
- [啟動器主題](#啟動器主題)
|
||||
- [畫面](#畫面)
|
||||
- [成員](#成員)
|
||||
|
||||
# 下載
|
||||
|
||||
[在此處查找發布版本!](https://github.com/Grasscutters/Cultivation/releases)
|
||||
|
||||
下載後,從某個位置解壓縮並以管理員身份打開。
|
||||
|
||||
# 開發人員快速入門
|
||||
|
||||
### 安裝
|
||||
|
||||
- 安裝 [NodeJS >12](https://nodejs.org/en/)
|
||||
- 安裝 [yarn](https://classic.yarnpkg.com/lang/en/docs/install) (`npm`愛好者去哭吧!(滑稽))
|
||||
- 安裝 [Rust](https://www.rust-lang.org/tools/install)
|
||||
- `yarn install`
|
||||
- `yarn start:dev`
|
||||
|
||||
### 編譯
|
||||
|
||||
發布版本,
|
||||
|
||||
- `yarn build`
|
||||
|
||||
調試版本,
|
||||
|
||||
- `yarn build --debug`
|
||||
|
||||
### 代碼格式化與糾錯
|
||||
|
||||
格式化:
|
||||
|
||||
- `yarn format`
|
||||
|
||||
糾錯, 修復(一些)錯誤:
|
||||
|
||||
- `yarn lint`, `yarn lint:fix`
|
||||
|
||||
### 生成更新項目
|
||||
|
||||
- 將 `TAURI_PRIVATE_KEY` 添加到環境變數,其中包含私鑰的路徑。
|
||||
- 將 `TAURI_KEY_PASSWORD` 添加到環境變數,其中包含私鑰的密碼。
|
||||
- `yarn build`
|
||||
|
||||
更新將生成在 `src-tauri/target/(release|debug)/msi/Cultivation_X.X.X_x64_xx-XX.msi.zip`
|
||||
|
||||
# 啟動器主題
|
||||
|
||||
完整的主題參考可以[在這裏找到!](/THEMES.md)
|
||||
|
||||
# 畫面
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 成員
|
||||
|
||||
- [SpikeHD](https://github.com/SpikeHD): For originally creating **GrassClipper** and creating the amazing UI of Cultivation.
|
||||
- [KingRainbow44](https://github.com/KingRainbow44): For building a proxy daemon from scratch and integrating it with Cultivation.
|
||||
- [Benj](https://github.com/4Benj): For assistance in client patching.
|
||||
- [lilmayofuksu](https://github.com/lilmayofuksu): For assistance in client patching.
|
||||
- [Tauri](https://tauri.app): For providing an amazing, efficient, and simple desktop application framework/library.
|
||||
47
THEMES.md
@@ -2,7 +2,7 @@
|
||||
|
||||
1. Download your favorite theme! (You can find some in the `#themes` channel on Discord)
|
||||
2. Place the unzipped theme folder inside of `%appdata%/cultivation/themes` (The path should look something like this: `cultivation/themes/theme_name/index.json`)
|
||||
4. Enable within Cultivation!
|
||||
3. Enable within Cultivation!
|
||||
|
||||
# Creating your own theme
|
||||
|
||||
@@ -16,16 +16,16 @@ You will need CSS and JS experience if you want to do anything cool.
|
||||
|
||||
`index.json` is where you tell Cultivation which files and images to include. It supports the following properties:
|
||||
|
||||
| Property | Description |
|
||||
| :--- | :--- |
|
||||
| `name` | The name of the theme. |
|
||||
| `version` | Not shown anywhere, the version of the theme. |
|
||||
| `description` | Not shown anywhere, the description of the theme. |
|
||||
| `includes` | The files and folders to include. |
|
||||
| `includes.css` | Array of CSS files to include. Example: `css: ["index.css"]` |
|
||||
| `includes.js` | Array of JS files to includes. Example `js: ["index.js"]` |
|
||||
| `customBackgroundURL` | A custom image URL to set as the background. Backgrounds that users set in their config supercede this. Example: `"https://website.com/image.png"` |
|
||||
| `customBackgroundFile` | Path to a custom background image file. Backgrounds that users set in their config supercede this. Example: `"/image.png"` |
|
||||
| Property | Description |
|
||||
| :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `name` | The name of the theme. |
|
||||
| `version` | Not shown anywhere, the version of the theme. |
|
||||
| `description` | Not shown anywhere, the description of the theme. |
|
||||
| `includes` | The files and folders to include. |
|
||||
| `includes.css` | Array of CSS files to include. Example: `css: ["index.css"]` |
|
||||
| `includes.js` | Array of JS files to includes. Example `js: ["index.js"]` |
|
||||
| `customBackgroundURL` | A custom image URL to set as the background. Backgrounds that users set in their config supercede this. Example: `"https://website.com/image.png"` |
|
||||
| `customBackgroundFile` | Path to a custom background image file. Backgrounds that users set in their config supercede this. Example: `"/image.png"` |
|
||||
|
||||
A full, complete `index.json` will look something like this:
|
||||
|
||||
@@ -55,15 +55,17 @@ Below are some small examples of what you can do:
|
||||
```css
|
||||
/* Change the font */
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif !important;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* Remove the news section */
|
||||
.NewsSection {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* Change the right bar width */
|
||||
.RightBar {
|
||||
@@ -72,6 +74,7 @@ body {
|
||||
```
|
||||
|
||||
## How can I change XYZ element?
|
||||
|
||||
Every element is documented and describe [here](/docs/elementIds.md). Every\* single DOM element is assigned an ID to allow for easy and hyper-specific editing.
|
||||
|
||||
## Writing your JS
|
||||
@@ -83,24 +86,26 @@ Below are some examples of what you can do:
|
||||
```js
|
||||
/* Change the version number every 500ms */
|
||||
setInterval(() => {
|
||||
document.getElementById("version").innerHTML = "v" + Math.floor(Math.random() * 100);
|
||||
}, 500);
|
||||
document.getElementById('version').innerHTML = 'v' + Math.floor(Math.random() * 100)
|
||||
}, 500)
|
||||
```
|
||||
|
||||
```js
|
||||
/* Load a custom font */
|
||||
const head = document.head
|
||||
const link = document.createElement("link")
|
||||
const link = document.createElement('link')
|
||||
|
||||
link.href = "https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap"
|
||||
link.rel = "stylesheet"
|
||||
link.type = "text/css"
|
||||
link.href = 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap'
|
||||
link.rel = 'stylesheet'
|
||||
link.type = 'text/css'
|
||||
|
||||
head.appendChild(link)
|
||||
```
|
||||
|
||||
```js
|
||||
/* Create a new button that does nothing */
|
||||
const newButton = document.createElement("button");
|
||||
newButton.innerHTML = "New Button";
|
||||
const newButton = document.createElement('button')
|
||||
newButton.innerHTML = 'New Button'
|
||||
|
||||
document.body.appendChild(newButton);
|
||||
document.body.appendChild(newButton)
|
||||
```
|
||||
|
||||
@@ -1,133 +1,135 @@
|
||||
# Documentation of Element ID's and Classes for custom theming
|
||||
|
||||
## IDs
|
||||
|
||||
This does not include commonly used components (buttons, divider lines, commit author and message, etc...) for accessing and modifying those elements, please check `Classes` section bellow.
|
||||
|
||||
| #ID | Description |
|
||||
|----------------------------------------|-----------------------------------------------------------------|
|
||||
| `#miniDialogContainer` | Main container of MiniDialog |
|
||||
| `#miniDialogContainerTop` | Affects only top section of MiniDialog |
|
||||
| `#miniDialogButtonClose` | Close button (SVG) of MiniDialog |
|
||||
| `#miniDialogContent` | MiniDialog content |
|
||||
| `#rightBarContainer` | Main container of RightBar |
|
||||
| `#rightBarContent` | RightBar content |
|
||||
| `#rightBarButtonDiscord` | Discord button on the RightBar |
|
||||
| `#rightBarButtonGithub` | Github button on the RightBar |
|
||||
| `#playButton` | Main container for whole launch buttons section |
|
||||
| `#serverControls` | Container of "play on grasscutter" checkbox |
|
||||
| `#enableGC` | "play on grasscutter" checkbox |
|
||||
| `#ip` | Server ip input if play on grasscutter is enabled |
|
||||
| `#port` | Server port input if play on grasscutter is enabled |
|
||||
| `#httpsEnable` | "Enable https" checkbox if play on grasscutter is enabled |
|
||||
| `#officialPlay` | Launch button |
|
||||
| `#serverLaunch` | Launch server button |
|
||||
| `#serverlaunchIcon` | Icon (SVG) of server launch button |
|
||||
| `#serverConfigContainer` | Main container of server configuration section |
|
||||
| `#serverLaunchContainer` | Main container of launch buttons (includes launch server) |
|
||||
| `#topBarContainer` | Main container of launcher TopBar (minimize, exit, settings...) |
|
||||
| `#title` | Title of the TopBar |
|
||||
| `#version` | Version of the launcher in TopBar |
|
||||
| `#topBarButtonContainer` | Container of launcher TopBar buttons only |
|
||||
| `#closeBtn` | Exit launcher button |
|
||||
| `#minBtn` | Minimize launcher button |
|
||||
| `#settingsBtn` | Settings button |
|
||||
| `#downloadsBtn` | Downloads button (grasscutter resources, grasscutter...) |
|
||||
| `#newsContainer` | Main container of the news section |
|
||||
| `#newsTabsContainer` | Container for news tabs |
|
||||
| `#commits` | News tabs container commits button |
|
||||
| `#latest_version` | News tabs for latest version button |
|
||||
| `#newsContent` | Content section of news container |
|
||||
| `#newsCommitsTable` | Commits table of news section |
|
||||
| `#downloadMenuContainerGCStable` | Grasscutter stable update container |
|
||||
| `#downloadMenuLabelGCStable` | Label for stable update button |
|
||||
| `#downloadMenuButtonGCStable` | Button container for stable update button |
|
||||
| `#grasscutterStableBtn` | "Update grasscutter stable" button |
|
||||
| `#downloadMenuContainerGCDev` | Grasscutter development update container |
|
||||
| `#downloadMenuLabelGCDev` | Label for latest update button |
|
||||
| `#downloadMenuButtonGCDev` | Button container for latest update button |
|
||||
| `grasscutterLatestBtn` | "Update grasscutter latest" button |
|
||||
| `#downloadMenuContainerGCStableData` | Grasscutter stable data update container |
|
||||
| `#downloadMenuLabelGCStableData` | Label for stable data update |
|
||||
| `#downloadMenuButtonGCStableData` | Button container for stable data update button |
|
||||
| `#grasscutterStableRepo` | "Update grasscutter stable data" button |
|
||||
| `#downloadMenuContainerGCDevData` | Grasscutter latest data update container |
|
||||
| `#downloadMenuLabelGCDevData` | Label for latest data update |
|
||||
| `#downloadMenuButtonGCDevData` | Button container for latest data update button |
|
||||
| `#grasscutterDevRepo` | "Update grasscutter latest data" button |
|
||||
| `#downloadMenuContainerResources` | Container for grasscutter resources download |
|
||||
| `#downloadMenuLabelResources` | label for resources download |
|
||||
| `#downloadMenuButtonResources` | Button container for resources download button |
|
||||
| `#resourcesBtn` | "Download grasscutter resources" button |
|
||||
| `#menuContainer` | Generic Popup modal like menu container |
|
||||
| `#menuContainerTop` | Top section of menu container |
|
||||
| `#menuHeading` | Menu title |
|
||||
| `#menuButtonCloseContainer` | Container for menu close button |
|
||||
| `#menuButtonCloseIcon` | Menu close icon (SVG) |
|
||||
| `#menuContent` | Content section of the menu |
|
||||
| `#menuOptionsContainerGameExec` | Container for game executable option section |
|
||||
| `#menuOptionsLabelGameExec` | Label for game executable option |
|
||||
| `#menuOptionsDirGameExec` | Set game executable file browser |
|
||||
| `#menuOptionsContainerGCJar` | Container for grasscutter jar option |
|
||||
| `#menuOptionsLabelGCJar` | Label for grasscutter jar option |
|
||||
| `#menuOptionsDirGCJar` | Set grasscutter jar file browser |
|
||||
| `#menuOptionsContainerToggleEnc` | Container for toggle encryption option |
|
||||
| `#menuOptionsLabelToggleEnc` | Label for toggle encryption option |
|
||||
| `#menuOptionsButtonToggleEnc` | Toggle encryption button container |
|
||||
| `#toggleEnc` | Toggle encryption button |
|
||||
| `#menuOptionsContainerGCWGame` | Container for "grasscutter with game" option |
|
||||
| `#menuOptionsLabelGCWDame` | Label for "grasscutter with game" option |
|
||||
| `#menuOptionsCheckboxGCWGame` | Container for "grasscutter with game" option checkbox |
|
||||
| `#gcWithGame` | Grasscutter with game checkbox |
|
||||
| `#menuOptionsContainerThemes` | Container for themes section |
|
||||
| `#menuOptionsLabelThemes` | Label for set themes option |
|
||||
| `#menuOptionsSelectThemes` | Container for themes select menu |
|
||||
| `#menuOptionsSelectMenuThemes` | Set theme select menu |
|
||||
| `#menuOptionsContainerJavaPath` | Container for Java Path option |
|
||||
| `#menuOptionsLabelJavaPath` | Label for Java path option |
|
||||
| `#menuOptionsDirJavaPath` | Container for java path file browser |
|
||||
| `#menuOptionsContainerBG` | Container for Background option |
|
||||
| `#menuOptionsLabelBG` | Label for background option |
|
||||
| `#menuOptionsDirBG` | Container for background url/local path option |
|
||||
| `#menuOptionsContainerLang` | Container for language change option |
|
||||
| `#menuOptionsLabelLang` | Label for language change option |
|
||||
| `#menuOptionsSelectLang` | Container for language change select menu |
|
||||
| `#menuOptionsSelectMenuLang` | Language select menu |
|
||||
| `#DownloadProgress` | Download progress container |
|
||||
| `#bottomSectionContainer` | Bottom section container |
|
||||
| `#miniDownloadContainer` | Container for mini download |
|
||||
| #ID | Description |
|
||||
| ------------------------------------ | --------------------------------------------------------------- |
|
||||
| `#miniDialogContainer` | Main container of MiniDialog |
|
||||
| `#miniDialogContainerTop` | Affects only top section of MiniDialog |
|
||||
| `#miniDialogButtonClose` | Close button (SVG) of MiniDialog |
|
||||
| `#miniDialogContent` | MiniDialog content |
|
||||
| `#rightBarContainer` | Main container of RightBar |
|
||||
| `#rightBarContent` | RightBar content |
|
||||
| `#rightBarButtonDiscord` | Discord button on the RightBar |
|
||||
| `#rightBarButtonGithub` | Github button on the RightBar |
|
||||
| `#playButton` | Main container for whole launch buttons section |
|
||||
| `#serverControls` | Container of "play on grasscutter" checkbox |
|
||||
| `#enableGC` | "play on grasscutter" checkbox |
|
||||
| `#ip` | Server ip input if play on grasscutter is enabled |
|
||||
| `#port` | Server port input if play on grasscutter is enabled |
|
||||
| `#httpsEnable` | "Enable https" checkbox if play on grasscutter is enabled |
|
||||
| `#officialPlay` | Launch button |
|
||||
| `#serverLaunch` | Launch server button |
|
||||
| `#serverlaunchIcon` | Icon (SVG) of server launch button |
|
||||
| `#serverConfigContainer` | Main container of server configuration section |
|
||||
| `#serverLaunchContainer` | Main container of launch buttons (includes launch server) |
|
||||
| `#topBarContainer` | Main container of launcher TopBar (minimize, exit, settings...) |
|
||||
| `#title` | Title of the TopBar |
|
||||
| `#version` | Version of the launcher in TopBar |
|
||||
| `#topBarButtonContainer` | Container of launcher TopBar buttons only |
|
||||
| `#closeBtn` | Exit launcher button |
|
||||
| `#minBtn` | Minimize launcher button |
|
||||
| `#settingsBtn` | Settings button |
|
||||
| `#downloadsBtn` | Downloads button (grasscutter resources, grasscutter...) |
|
||||
| `#newsContainer` | Main container of the news section |
|
||||
| `#newsTabsContainer` | Container for news tabs |
|
||||
| `#commits` | News tabs container commits button |
|
||||
| `#latest_version` | News tabs for latest version button |
|
||||
| `#newsContent` | Content section of news container |
|
||||
| `#newsCommitsTable` | Commits table of news section |
|
||||
| `#downloadMenuContainerGCStable` | Grasscutter stable update container |
|
||||
| `#downloadMenuLabelGCStable` | Label for stable update button |
|
||||
| `#downloadMenuButtonGCStable` | Button container for stable update button |
|
||||
| `#grasscutterStableBtn` | "Update grasscutter stable" button |
|
||||
| `#downloadMenuContainerGCDev` | Grasscutter development update container |
|
||||
| `#downloadMenuLabelGCDev` | Label for latest update button |
|
||||
| `#downloadMenuButtonGCDev` | Button container for latest update button |
|
||||
| `grasscutterLatestBtn` | "Update grasscutter latest" button |
|
||||
| `#downloadMenuContainerGCStableData` | Grasscutter stable data update container |
|
||||
| `#downloadMenuLabelGCStableData` | Label for stable data update |
|
||||
| `#downloadMenuButtonGCStableData` | Button container for stable data update button |
|
||||
| `#grasscutterStableRepo` | "Update grasscutter stable data" button |
|
||||
| `#downloadMenuContainerGCDevData` | Grasscutter latest data update container |
|
||||
| `#downloadMenuLabelGCDevData` | Label for latest data update |
|
||||
| `#downloadMenuButtonGCDevData` | Button container for latest data update button |
|
||||
| `#grasscutterDevRepo` | "Update grasscutter latest data" button |
|
||||
| `#downloadMenuContainerResources` | Container for grasscutter resources download |
|
||||
| `#downloadMenuLabelResources` | label for resources download |
|
||||
| `#downloadMenuButtonResources` | Button container for resources download button |
|
||||
| `#resourcesBtn` | "Download grasscutter resources" button |
|
||||
| `#menuContainer` | Generic Popup modal like menu container |
|
||||
| `#menuContainerTop` | Top section of menu container |
|
||||
| `#menuHeading` | Menu title |
|
||||
| `#menuButtonCloseContainer` | Container for menu close button |
|
||||
| `#menuButtonCloseIcon` | Menu close icon (SVG) |
|
||||
| `#menuContent` | Content section of the menu |
|
||||
| `#menuOptionsContainerGameExec` | Container for game executable option section |
|
||||
| `#menuOptionsLabelGameExec` | Label for game executable option |
|
||||
| `#menuOptionsDirGameExec` | Set game executable file browser |
|
||||
| `#menuOptionsContainerGCJar` | Container for grasscutter jar option |
|
||||
| `#menuOptionsLabelGCJar` | Label for grasscutter jar option |
|
||||
| `#menuOptionsDirGCJar` | Set grasscutter jar file browser |
|
||||
| `#menuOptionsContainerToggleEnc` | Container for toggle encryption option |
|
||||
| `#menuOptionsLabelToggleEnc` | Label for toggle encryption option |
|
||||
| `#menuOptionsButtonToggleEnc` | Toggle encryption button container |
|
||||
| `#toggleEnc` | Toggle encryption button |
|
||||
| `#menuOptionsContainerGCWGame` | Container for "grasscutter with game" option |
|
||||
| `#menuOptionsLabelGCWDame` | Label for "grasscutter with game" option |
|
||||
| `#menuOptionsCheckboxGCWGame` | Container for "grasscutter with game" option checkbox |
|
||||
| `#gcWithGame` | Grasscutter with game checkbox |
|
||||
| `#menuOptionsContainerThemes` | Container for themes section |
|
||||
| `#menuOptionsLabelThemes` | Label for set themes option |
|
||||
| `#menuOptionsSelectThemes` | Container for themes select menu |
|
||||
| `#menuOptionsSelectMenuThemes` | Set theme select menu |
|
||||
| `#menuOptionsContainerJavaPath` | Container for Java Path option |
|
||||
| `#menuOptionsLabelJavaPath` | Label for Java path option |
|
||||
| `#menuOptionsDirJavaPath` | Container for java path file browser |
|
||||
| `#menuOptionsContainerBG` | Container for Background option |
|
||||
| `#menuOptionsLabelBG` | Label for background option |
|
||||
| `#menuOptionsDirBG` | Container for background url/local path option |
|
||||
| `#menuOptionsContainerLang` | Container for language change option |
|
||||
| `#menuOptionsLabelLang` | Label for language change option |
|
||||
| `#menuOptionsSelectLang` | Container for language change select menu |
|
||||
| `#menuOptionsSelectMenuLang` | Language select menu |
|
||||
| `#DownloadProgress` | Download progress container |
|
||||
| `#bottomSectionContainer` | Bottom section container |
|
||||
| `#miniDownloadContainer` | Container for mini download |
|
||||
|
||||
## Classes
|
||||
|
||||
This is not full list of all classes, rather its list of classes for commonly used components that can not be accessed using element id system.
|
||||
|
||||
| .Class | Description |
|
||||
|-----------------------------|---------------------------------------------------------|
|
||||
| `.BigButton` | Class for all buttons |
|
||||
| `.BigButtonText` | Text inside a button | |
|
||||
| `.Checkbox` | Checkbox container |
|
||||
| `.CheckboxDisplay` | Content of checkbox |
|
||||
| `.DirInput` | Container for DirInput |
|
||||
| `.FileSelectIcon` | Icon of DirInput |
|
||||
| `.DownloadList` | List of all downloads |
|
||||
| `.DownloadSection` | Container for each download |
|
||||
| `.DownloadTitle` | Contains file download path and current status |
|
||||
| `.DownloadPath` | Path of a download |
|
||||
| `.DownloadStatus` | Status of a download |
|
||||
| `.DownloadSectionInner` | Contains progressbar of the download section |
|
||||
| `.HelpSection` | Container for help "?" circle button |
|
||||
| `.HelpButton` | HelpButton itself |
|
||||
| `.HelpContents` | Content of help button once expanded |
|
||||
| `.MainProgressBarWrapper` | Container for MainProgressBar |
|
||||
| `.ProgressBar` | ProgressBar (creativity left the brain) |
|
||||
| `.InnerProgress` | ProgressBar percentage |
|
||||
| `.MainProgressText` | Text for MainProgressBar |
|
||||
| `.ProgressBarWrapper` | Container for ProgressBar |
|
||||
| `.DownloadControls` | DownloadControls of ProgressBar |
|
||||
| `.downloadStop` | Container for download stop icon (SVG) |
|
||||
| `.ProgressText` | Text of the ProgressBar display current download status |
|
||||
| `.TextInputWrapper` | Container for TextInput |
|
||||
| `.TextClear` | Container for clear input content button |
|
||||
| `.TextInputClear` | TextInput clear button icon (SVG) |
|
||||
| `.Divider` | Container for line dividers |
|
||||
| `.DividerLine` | Divider line itself |
|
||||
| `.CommitAuthor` | Author of a commit |
|
||||
| `.CommitMessage` | Message of a commit |
|
||||
| .Class | Description |
|
||||
| ------------------------- | ------------------------------------------------------- |
|
||||
| `.BigButton` | Class for all buttons |
|
||||
| `.BigButtonText` | Text inside a button |
|
||||
| `.Checkbox` | Checkbox container |
|
||||
| `.CheckboxDisplay` | Content of checkbox |
|
||||
| `.DirInput` | Container for DirInput |
|
||||
| `.FileSelectIcon` | Icon of DirInput |
|
||||
| `.DownloadList` | List of all downloads |
|
||||
| `.DownloadSection` | Container for each download |
|
||||
| `.DownloadTitle` | Contains file download path and current status |
|
||||
| `.DownloadPath` | Path of a download |
|
||||
| `.DownloadStatus` | Status of a download |
|
||||
| `.DownloadSectionInner` | Contains progressbar of the download section |
|
||||
| `.HelpSection` | Container for help "?" circle button |
|
||||
| `.HelpButton` | HelpButton itself |
|
||||
| `.HelpContents` | Content of help button once expanded |
|
||||
| `.MainProgressBarWrapper` | Container for MainProgressBar |
|
||||
| `.ProgressBar` | ProgressBar (creativity left the brain) |
|
||||
| `.InnerProgress` | ProgressBar percentage |
|
||||
| `.MainProgressText` | Text for MainProgressBar |
|
||||
| `.ProgressBarWrapper` | Container for ProgressBar |
|
||||
| `.DownloadControls` | DownloadControls of ProgressBar |
|
||||
| `.downloadStop` | Container for download stop icon (SVG) |
|
||||
| `.ProgressText` | Text of the ProgressBar display current download status |
|
||||
| `.TextInputWrapper` | Container for TextInput |
|
||||
| `.TextClear` | Container for clear input content button |
|
||||
| `.TextInputClear` | TextInput clear button icon (SVG) |
|
||||
| `.Divider` | Container for line dividers |
|
||||
| `.DividerLine` | Divider line itself |
|
||||
| `.CommitAuthor` | Author of a commit |
|
||||
| `.CommitMessage` | Message of a commit |
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
# Troubleshooting
|
||||
|
||||
A guide dedicated for trying to troubleshoot Cultivation.
|
||||
|
||||
## The launcher doesn't appear to open.
|
||||
|
||||
Try running the launcher with **administrative privileges**.\
|
||||
If this fixes your issue, you can force enable it in the **Compatability**\
|
||||
tab for the launcher's executable.
|
||||
|
||||
## Unable to play on `localhost`.
|
||||
|
||||
Make sure your server is running with **encryption disabled** and `useInRouting` to **false**.\
|
||||
Additionally, make sure Cultivation **is set to not use HTTPS**.
|
||||
|
||||
## "I can't do anything requiring the internet after closing Cultivation!"
|
||||
|
||||
You probably didn't close Cultivation properly.\
|
||||
Go to your *Windows Settings*, then *Network*, then *Proxy*, then disable it.
|
||||
Go to your _Windows Settings_, then _Network_, then _Proxy_, then disable it.
|
||||
|
||||
22
package.json
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "cultivation",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.10",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.0.0-rc.5",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^13.0.0",
|
||||
"@testing-library/user-event": "^13.2.1",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/node": "^16.7.13",
|
||||
"@testing-library/user-event": "^14.2.6",
|
||||
"@types/jest": "^28.1.6",
|
||||
"@types/node": "^18.0.6",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"react": "^18.1.0",
|
||||
@@ -20,16 +20,18 @@
|
||||
"scripts": {
|
||||
"start": "cross-env BROWSER=none react-scripts start",
|
||||
"postbuild:windows": "xcopy /E /H /C /I /Y \".\\src-tauri\\lang\" \".\\src-tauri\\target\\release\\lang\"",
|
||||
"postbuild:linux": "cp -r \".\\src-tauri\\lang\" \".\\lang\"",
|
||||
"postbuild:linux": "cp -r \"./src-tauri/lang\" \"./lang\"",
|
||||
"build:windows": "yarn tauri build",
|
||||
"build:linux": "yarn tauri build",
|
||||
"build": "react-scripts build && run-script-os",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"tauri": "tauri",
|
||||
"start:dev": "tauri dev",
|
||||
"format": "cargo fmt --manifest-path ./src-tauri/Cargo.toml --all",
|
||||
"lint": "cargo clippy --manifest-path ./src-tauri/Cargo.toml --no-default-features && yarn tsc --noEmit && yarn eslint src"
|
||||
"start:dev": "tauri dev -- -- --no-admin",
|
||||
"format": "cargo fmt --manifest-path ./src-tauri/Cargo.toml --all && yarn prettier --write --cache --loglevel warn .",
|
||||
"lint": "cargo clippy --manifest-path ./src-tauri/Cargo.toml --no-default-features && yarn tsc --noEmit && yarn eslint src",
|
||||
"lint:fix": "cargo clippy --manifest-path ./src-tauri/Cargo.toml --no-default-features --fix --allow-dirty && yarn tsc --noEmit && yarn eslint --fix src",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -55,7 +57,11 @@
|
||||
"@typescript-eslint/parser": "^5.22.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.15.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"husky": "^8.0.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"prettier": "^2.7.1",
|
||||
"run-script-os": "^1.1.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Tauri-powered anime game launcher"
|
||||
/>
|
||||
<meta name="description" content="Tauri-powered anime game launcher" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>Cultivation</title>
|
||||
|
||||
697
src-tauri/Cargo.lock
generated
@@ -24,20 +24,21 @@ sudo = "0.6.0"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.0.0-rc.9", features = ["api-all"] }
|
||||
tauri = { version = "1.0.7", features = ["api-all"] }
|
||||
|
||||
# Access system process info.
|
||||
sysinfo = "0.24.6"
|
||||
|
||||
# ZIP-archive library.
|
||||
zip-extract = "0.1.1"
|
||||
unrar = "0.4.4"
|
||||
zip = "0.6.2"
|
||||
|
||||
# For creating a "global" downloads list.
|
||||
once_cell = "1.13.0"
|
||||
|
||||
# Program opener.
|
||||
open = "2.1.2"
|
||||
open = "3.0.2"
|
||||
duct = "0.13.5"
|
||||
|
||||
# Serialization.
|
||||
@@ -45,7 +46,7 @@ serde_json = "1"
|
||||
|
||||
# Dependencies for the HTTP(S) proxy.
|
||||
http = "0.2"
|
||||
hudsucker = "0.17.2"
|
||||
hudsucker = "0.19.1"
|
||||
tracing = "0.1.21"
|
||||
tokio-rustls = "0.23.0"
|
||||
tokio-tungstenite = "0.17.0"
|
||||
@@ -56,11 +57,12 @@ futures-util = "0.3.14"
|
||||
rcgen = { version = "0.9", features = ["x509-parser"] }
|
||||
|
||||
# metadata stuff
|
||||
libloading = "0.7"
|
||||
regex = "1"
|
||||
|
||||
# other
|
||||
file_diff = "1.0.0"
|
||||
rust-ini = "0.18.0"
|
||||
ctrlc = "3.2.3"
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
|
||||
@@ -2,17 +2,14 @@ fn main() {
|
||||
cc::Build::new()
|
||||
.include("mhycrypto")
|
||||
.cpp(true)
|
||||
|
||||
.file("mhycrypto/memecrypto.cpp")
|
||||
.file("mhycrypto/metadata.cpp")
|
||||
.file("mhycrypto/metadatastringdec.cpp")
|
||||
|
||||
.compile("mhycrypto");
|
||||
|
||||
cc::Build::new()
|
||||
cc::Build::new()
|
||||
.include("mhycrypto")
|
||||
.file("mhycrypto/aes.c")
|
||||
|
||||
.compile("mhycrypto-aes");
|
||||
|
||||
tauri_build::build()
|
||||
|
||||
BIN
src-tauri/icons/icon_resize.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
@@ -1,7 +1,7 @@
|
||||
<RSAKeyValue>
|
||||
<Exponent>AQAB</Exponent>
|
||||
<Modulus>yytg/H9lz7Lm0XcA8LMqIyXPVNApYTcSepT4VDLB4qqqFC3s
|
||||
/Huv8vN7zA/P4uoREIu8KMenADFk7uwrZSxoMWwJgn6A7sbAt1cqAaUXB
|
||||
9J4NzhL0x3AFTiHEQbw86hRvm2VGkbA5sWnr0NZw8SGBBY+EODwNIt51G
|
||||
dBA7eoUQU=</Modulus>
|
||||
<RSAKeyValue>
|
||||
<Exponent>AQAB</Exponent>
|
||||
<Modulus>yytg/H9lz7Lm0XcA8LMqIyXPVNApYTcSepT4VDLB4qqqFC3s
|
||||
/Huv8vN7zA/P4uoREIu8KMenADFk7uwrZSxoMWwJgn6A7sbAt1cqAaUXB
|
||||
9J4NzhL0x3AFTiHEQbw86hRvm2VGkbA5sWnr0NZw8SGBBY+EODwNIt51G
|
||||
dBA7eoUQU=</Modulus>
|
||||
</RSAKeyValue>
|
||||
@@ -11,15 +11,27 @@
|
||||
"files_extracting": "文件解压中:"
|
||||
},
|
||||
"options": {
|
||||
"enabled": "已启用",
|
||||
"disabled": "已禁用",
|
||||
"game_path": "选择游戏安装路径",
|
||||
"game_command": "启动游戏的命令行",
|
||||
"game_executable": "选择游戏可执行文件",
|
||||
"recover_rsa": "紧急情况下删除补丁文件",
|
||||
"grasscutter_jar": "选择 Grasscutter JAR 文件",
|
||||
"java_path": "设置自定义 Java 路径",
|
||||
"toggle_encryption": "启用加密",
|
||||
"install_certificate": "安装代理证书",
|
||||
"java_path": "选择自定义 Java 路径",
|
||||
"grasscutter_with_game": "随游戏自动启动 Grasscutter",
|
||||
"language": "语言",
|
||||
"language": "选择语言",
|
||||
"background": "设置自定义背景(链接或文件)",
|
||||
"theme": "设置主题"
|
||||
"theme": "设置主题",
|
||||
"patch_rsa": "自动修改RSA",
|
||||
"use_proxy": "使用内置代理",
|
||||
"wipe_login": "清除登录缓存",
|
||||
"horny_mode": "Horny 模式"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "下载 Grasscutter 一体化",
|
||||
"grasscutter_stable_data": "下载 Grasscutter 稳定版数据",
|
||||
"grasscutter_latest_data": "下载 Grasscutter 开发版数据",
|
||||
"grasscutter_stable_data_update": "更新 Grasscutter 稳定版数据",
|
||||
@@ -28,7 +40,10 @@
|
||||
"grasscutter_latest": "下载 Grasscutter 开发版",
|
||||
"grasscutter_stable_update": "更新 Grasscutter 稳定版",
|
||||
"grasscutter_latest_update": "更新 Grasscutter 开发版",
|
||||
"resources": "下载 Grasscutter 资源"
|
||||
"resources": "下载 Grasscutter 资源",
|
||||
"game": "下载游戏",
|
||||
"aio_header": "多合一下载:",
|
||||
"individual_header": "个别部分下载:"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "下载中",
|
||||
@@ -40,19 +55,34 @@
|
||||
"components": {
|
||||
"select_file": "选择文件或文件夹...",
|
||||
"select_folder": "选择文件夹...",
|
||||
"download": "下载"
|
||||
"download": "下载",
|
||||
"delete": "删除",
|
||||
"install": "安装"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "最近的PR",
|
||||
"latest_commits": "最近提交",
|
||||
"latest_version": "最新版本"
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "确保这是 Dispatch 服务器端口,而不是游戏服务器端口。大部分服务器的端口都是 443。",
|
||||
"game_help_text": "你不需要另外的游戏备份来使用 Grasscutter。这是给想要降级到 2.6 或没有安装游戏的人使用的。",
|
||||
"gc_stable_jar": "下载当前的 Grasscutter 稳定版,包括 JAR 文件和数据。",
|
||||
"gc_fullbuild": "下载完整的 Grasscutter 构建版本,包括存储库、jar 和资源。 已完全设置,不需要从此菜单下载任何其他内容",
|
||||
"gc_dev_jar": "下载最新的 Grasscutter 开发版,包括 JAR 文件和数据。",
|
||||
"gc_stable_data": "下载当前的 Grasscutter 稳定版数据,不包括 JAR 文件。此选项在更新时有帮助。",
|
||||
"gc_dev_data": "下载最新的 Grasscutter 开发版数据,不包括 JAR 文件。此选项在更新时有帮助。",
|
||||
"resources": "资源文件在运行 Grasscutter 服务器时是必要的。此选项在已经存在资源文件时不可选。"
|
||||
"encryption": "此项设置通常应该处于关闭状态。",
|
||||
"resources": "资源文件在运行 Grasscutter 服务器时是必要的。此选项在已经存在资源文件时不可选。",
|
||||
"emergency_rsa": "在出现意外情况时自动将 RSA 恢复到原始版本",
|
||||
"use_proxy": "使用 Cultivation 的内置代理。除非你使用 Fiddler 等软件,否则应启用此项。",
|
||||
"patch_rsa": "自动修改和恢复 RSA 补丁。 除非您玩的是旧版/非官方版本,或者您手动修改了 RSA,否则应该启用此功能。"
|
||||
},
|
||||
"swag": {
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "选择 Akebi 可执行文件",
|
||||
"migoto": "选择 3DMigoto 可执行文件",
|
||||
"reshade": "选择 Reshade 注入器"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,16 +13,25 @@
|
||||
"options": {
|
||||
"enabled": "已啟用",
|
||||
"disabled": "未啟用",
|
||||
"game_path": "選擇遊戲安裝路徑",
|
||||
"game_command": "遊戲啟動命令",
|
||||
"game_executable": "選擇遊戲執行檔",
|
||||
"recover_rsa": "緊急恢復RSA",
|
||||
"grasscutter_jar": "選擇伺服器JAR檔案",
|
||||
"toggle_encryption": "設定加密",
|
||||
"java_path": "設定自定義Java路徑",
|
||||
"install_certificate": "安裝代理憑證",
|
||||
"java_path": "選擇自定義Java路徑",
|
||||
"grasscutter_with_game": "伴隨遊戲一起啟動Grasscutter",
|
||||
"language": "語言",
|
||||
"background": "設定自定義背景(網址或檔案)",
|
||||
"theme": "設定主題"
|
||||
"background": "選擇自定義背景(網址或檔案)",
|
||||
"theme": "選擇主題",
|
||||
"patch_rsa": "自動修補RSA",
|
||||
"use_proxy": "使用內建代理伺服器",
|
||||
"wipe_login": "擦除登錄緩存",
|
||||
"horny_mode": "Horny模式"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "下載Grasscutter多合一下載",
|
||||
"grasscutter_stable_data": "下載Grasscutter穩定版數據(Data)",
|
||||
"grasscutter_latest_data": "下載Grasscutter開發板數據(Data)",
|
||||
"grasscutter_stable_data_update": "更新Grasscutter穩定版數據(Data)",
|
||||
@@ -31,7 +40,10 @@
|
||||
"grasscutter_latest": "下載Grasscutter開發板",
|
||||
"grasscutter_stable_update": "更新Grasscutter穩定版",
|
||||
"grasscutter_latest_update": "更新Grasscutter開發板",
|
||||
"resources": "下載Grasscutter資源(Resources)"
|
||||
"resources": "下載Grasscutter資源(Resources)",
|
||||
"game": "下載遊戲",
|
||||
"aio_header": "多合一下載:",
|
||||
"individual_header": "個別部分下載:"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "下載中",
|
||||
@@ -43,7 +55,9 @@
|
||||
"components": {
|
||||
"select_file": "選擇檔案或資料夾...",
|
||||
"select_folder": "選擇資料夾...",
|
||||
"download": "下載"
|
||||
"download": "下載",
|
||||
"delete": "刪除",
|
||||
"install": "安裝"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "最近的PR",
|
||||
@@ -51,11 +65,24 @@
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "確保這是Dispatch伺服器端口,不是遊戲伺服器端口。 大部分伺服器的端口都是443。",
|
||||
"game_help_text": "你不需要另外一個遊戲備份來使用Grasscutter。這是給想要降級到2.6或者還沒安裝遊戲的人使用的。",
|
||||
"game_help_text": "您不需要另外一個遊戲備份來使用Grasscutter。這是給想要降級到2.6或者還沒安裝遊戲的人使用的。",
|
||||
"gc_stable_jar": "下載當前的Grasscutter穩定版本,包括JAR答案還有資料文件。",
|
||||
"gc_fullbuild": "下載完整的 Grasscutter 構建版本,包括存儲庫、jar 和資源。 已完全設置,不需要從此菜單下載任何其他內容",
|
||||
"gc_dev_jar": "下載當前的Grasscutter穩定版本資料文件,其中不會附帶JAR文件。這個選項在更新時很有用。",
|
||||
"gc_stable_data": "下載當前最新的Grasscutter開發版本資料文件,其中不會附帶JAR文件。這個選項在更新時很有用。",
|
||||
"gc_dev_data": "下載當前最新的Grasscutter開發版本的資料文件,其中不會附帶JAR文件。這個選項在更新時很有用。",
|
||||
"resources": "資源文件在架設一個Grasscutter伺服器時是必要的。 這個選項會在你已經有裡面有檔案的資源資料夾時不可選。"
|
||||
"encryption": "在正常情況下,此選項應該被關閉。",
|
||||
"resources": "資源文件在架設一個Grasscutter伺服器時是必要的。 這個選項會在您已經有裡面有檔案的資源資料夾時不可選。",
|
||||
"emergency_rsa": "一旦有東西出了問題,此選項可以把您的rsa恢復成官方版本。",
|
||||
"use_proxy": "使用Cultivation內建的代理伺服器。此選項應該被啟用,除非你使用其他的代理伺服器。",
|
||||
"patch_rsa": "自動修補和恢復RSA。除非您的遊戲版本是舊的或者是非官方的,此選項應該被啟用。"
|
||||
},
|
||||
"swag": {
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "選擇Akebi執行檔",
|
||||
"migoto": "選擇3DMigoto執行檔",
|
||||
"reshade": "選擇Reshade注入器"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,86 @@
|
||||
{
|
||||
"lang_name": "Deutsch",
|
||||
"main": {
|
||||
"title": "Cultivation",
|
||||
"launch_button": "Starten",
|
||||
"gc_enable": "Über Grasscutter verbinden",
|
||||
"https_enable": "HTTPS nutzen",
|
||||
"ip_placeholder": "Server Adresse...",
|
||||
"port_placeholder": "Port...",
|
||||
"files_downloading": "Herunterladen von Dateien: ",
|
||||
"files_extracting": "Extrahieren von Dateien: "
|
||||
},
|
||||
"options": {
|
||||
"enabled": "Aktiviert",
|
||||
"disabled": "Deaktiviert",
|
||||
"game_executable": "Spiel Datei auswählen",
|
||||
"grasscutter_jar": "Grasscuter JAR auswählen",
|
||||
"toggle_encryption": "Verschlüsselung umschalten",
|
||||
"java_path": "Benutzerdefinierten Java Pfad setzen",
|
||||
"grasscutter_with_game": "Grasscutter automatisch mit dem Spiel starten",
|
||||
"language": "Sprache auswählen",
|
||||
"background": "Benutzerdefinierten Hintergrund festlegen (link oder bild)",
|
||||
"theme": "Theme auswählen"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "Stabile Grasscutter Daten herunterladen",
|
||||
"grasscutter_latest_data": "Aktuellste Grasscutter Daten herunterladen",
|
||||
"grasscutter_stable_data_update": "Stabile Grasscutter Daten aktualisieren",
|
||||
"grasscutter_latest_data_update": "Aktuellste Grasscutter Daten aktualisieren",
|
||||
"grasscutter_stable": "Stabile Grasscutter Version herunterladen",
|
||||
"grasscutter_latest": "Aktuellste Grasscutter Version herunterladen",
|
||||
"grasscutter_stable_update": "Stabile Grasscutter Version aktualisieren",
|
||||
"grasscutter_latest_update": "Aktuellste Grasscutter Version aktualisieren",
|
||||
"resources": "Grasscutter Ressourcen herunterladen"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Lädt herunter",
|
||||
"extracting": "Extrahiert",
|
||||
"error": "Fehler",
|
||||
"finished": "Fertig",
|
||||
"stopped": "Gestoppt"
|
||||
},
|
||||
"components": {
|
||||
"select_file": "Datei oder Ordner auswählen...",
|
||||
"select_folder": "Ordner auswählen...",
|
||||
"download": "Herunterladen"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Letzte Commits",
|
||||
"latest_version": "Letzte Version"
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "Vergewissern Sie sich, dass es sich um den Port des Dispatch-Servers handelt, nicht um den Port des Spiel-Servers. Dieser ist fast immer '443'.",
|
||||
"game_help_text": "Sie müssen keine separate Kopie verwenden, um mit Grasscutter zu spielen. Dies ist entweder für ein Downgrade auf die Version 2.6 oder wenn Sie das Spiel nicht installiert haben.",
|
||||
"gc_stable_jar": "Laden Sie den aktuellen stabilen Grasscutter-Build herunter, der eine Jar-Datei und Datendateien enthält.",
|
||||
"gc_dev_jar": "Laden Sie die neueste Grasscutter-Entwicklungsversion herunter, welche eine Jar-Datei und Datendateien enthält.",
|
||||
"gc_stable_data": "Laden Sie die stabilen Grasscutter Daten herunter, welche keine Jar-Datei enthalten. Dies ist nützlich zum Aktualisieren.",
|
||||
"gc_dev_data": "Laden Sie die neuesten Grasscutter-Entwicklungsdateien herunter, welche keine Jar-Datei enthält. Dies ist nützlich zum Aktualisieren.",
|
||||
"resources": "Diese werden auch benötigt, um einen Grasscutter-Server auszuführen. Diese Schaltfläche ist grau, wenn Sie einen bestehenden Ressourcenordner mit Inhalten haben"
|
||||
}
|
||||
}
|
||||
{
|
||||
"lang_name": "Deutsch",
|
||||
"main": {
|
||||
"title": "Cultivation",
|
||||
"launch_button": "Starten",
|
||||
"gc_enable": "Über Grasscutter verbinden",
|
||||
"https_enable": "HTTPS nutzen",
|
||||
"ip_placeholder": "Server Adresse...",
|
||||
"port_placeholder": "Port...",
|
||||
"files_downloading": "Herunterladen von Dateien: ",
|
||||
"files_extracting": "Extrahieren von Dateien: "
|
||||
},
|
||||
"options": {
|
||||
"enabled": "Aktiviert",
|
||||
"disabled": "Deaktiviert",
|
||||
"game_path": "Spielpfad",
|
||||
"game_executable": "Spiel Datei auswählen",
|
||||
"recover_rsa": "Notfall Wiederherstellung der RSA",
|
||||
"grasscutter_jar": "Grasscuter JAR auswählen",
|
||||
"toggle_encryption": "Verschlüsselung umschalten",
|
||||
"install_certificate": "Installeer proxy certificaat",
|
||||
"java_path": "Benutzerdefinierten Java Pfad setzen",
|
||||
"grasscutter_with_game": "Grasscutter automatisch mit dem Spiel starten",
|
||||
"language": "Sprache auswählen",
|
||||
"background": "Benutzerdefinierten Hintergrund festlegen (link oder bild)",
|
||||
"theme": "Theme auswählen",
|
||||
"patch_rsa": "RSA automatisch patchen",
|
||||
"use_proxy": "Gebruik interne proxy",
|
||||
"wipe_login": "Wis de inlogcache",
|
||||
"horny_mode": "Geile modus"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Alles in Einem Grasscutter Daten herunterladen",
|
||||
"grasscutter_stable_data": "Stabile Grasscutter Daten herunterladen",
|
||||
"grasscutter_latest_data": "Aktuellste Grasscutter Daten herunterladen",
|
||||
"grasscutter_stable_data_update": "Stabile Grasscutter Daten aktualisieren",
|
||||
"grasscutter_latest_data_update": "Aktuellste Grasscutter Daten aktualisieren",
|
||||
"grasscutter_stable": "Stabile Grasscutter Version herunterladen",
|
||||
"grasscutter_latest": "Aktuellste Grasscutter Version herunterladen",
|
||||
"grasscutter_stable_update": "Stabile Grasscutter Version aktualisieren",
|
||||
"grasscutter_latest_update": "Aktuellste Grasscutter Version aktualisieren",
|
||||
"resources": "Grasscutter Ressourcen herunterladen",
|
||||
"game": "Spiel herunterladen",
|
||||
"aio_header": "Alles in Einem herunterladen",
|
||||
"individual_header": "Einzelne Teile herunterladen:"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Lädt herunter",
|
||||
"extracting": "Extrahiert",
|
||||
"error": "Fehler",
|
||||
"finished": "Fertig",
|
||||
"stopped": "Gestoppt"
|
||||
},
|
||||
"components": {
|
||||
"select_file": "Datei oder Ordner auswählen...",
|
||||
"select_folder": "Ordner auswählen...",
|
||||
"download": "Herunterladen",
|
||||
"delete": "Löschen",
|
||||
"install": "Installieren"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Letzte Commits",
|
||||
"latest_version": "Letzte Version"
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "Vergewissern Sie sich, dass es sich um den Port des Dispatch-Servers handelt, nicht um den Port des Spiel-Servers. Dieser ist fast immer '443'.",
|
||||
"game_help_text": "Sie müssen keine separate Kopie verwenden, um mit Grasscutter zu spielen. Dies ist entweder für ein Downgrade auf die Version 2.6 oder wenn Sie das Spiel nicht installiert haben.",
|
||||
"gc_stable_jar": "Laden Sie den aktuellen stabilen Grasscutter-Build herunter, der eine Jar-Datei und Datendateien enthält.",
|
||||
"gc_fullbuild": "Download een volledige Grasscutter-build, inclusief repo, jar en bronnen. Is volledig ingesteld en vereist geen andere downloads uit dit menu",
|
||||
"gc_dev_jar": "Laden Sie die neueste Grasscutter-Entwicklungsversion herunter, welche eine Jar-Datei und Datendateien enthält.",
|
||||
"gc_stable_data": "Laden Sie die stabilen Grasscutter Daten herunter, welche keine Jar-Datei enthalten. Dies ist nützlich zum Aktualisieren.",
|
||||
"gc_dev_data": "Laden Sie die neuesten Grasscutter-Entwicklungsdateien herunter, welche keine Jar-Datei enthält. Dies ist nützlich zum Aktualisieren.",
|
||||
"resources": "Diese werden auch benötigt, um einen Grasscutter-Server auszuführen. Diese Schaltfläche ist grau, wenn Sie einen bestehenden Ressourcenordner mit Inhalten haben",
|
||||
"emergency_rsa": "Im Fall, dass etwas schief laufen sollte, kannst du deine RSA auf die letzte offizielle Version zurücksetzen",
|
||||
"use_proxy": "Nutze den internen Proxy von Cultivation. Du solltest dies aktivieren, es sei denn du nutzt Programme wie Fiddler",
|
||||
"patch_rsa": "Patche und aktualisiere deine RSA automatisch. Solange du nicht mit einer alten/nicht offiziellen Version spielst oder deine RSA manuell gepatcht hast, sollte dies aktiviert sein."
|
||||
},
|
||||
"swag": {
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "Akebi.exe festlegen",
|
||||
"migoto": "Migoto.exe festlegen",
|
||||
"reshade": "Reshade injector festlegen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"main": {
|
||||
"title": "Cultivation",
|
||||
"launch_button": "Launch",
|
||||
"gc_enable": "Connect via Grasscutter",
|
||||
"gc_enable": "Connect to Grasscutter",
|
||||
"https_enable": "Use HTTPS",
|
||||
"ip_placeholder": "Server Address...",
|
||||
"port_placeholder": "Port...",
|
||||
@@ -14,8 +14,9 @@
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"game_path": "Set Game Install Path",
|
||||
"game_command": "Game Launch Command",
|
||||
"game_executable": "Set Game Executable",
|
||||
"recover_metadata": "Emergency Metadata Recovery",
|
||||
"recover_rsa": "Emergency Delete RSA",
|
||||
"grasscutter_jar": "Set Grasscutter JAR",
|
||||
"toggle_encryption": "Toggle Encryption",
|
||||
"install_certificate": "Install Proxy Certificate",
|
||||
@@ -23,9 +24,14 @@
|
||||
"grasscutter_with_game": "Automatically launch Grasscutter with game",
|
||||
"language": "Select Language",
|
||||
"background": "Set Custom Background (link or image file)",
|
||||
"theme": "Set Theme"
|
||||
"theme": "Set Theme",
|
||||
"patch_rsa": "Automatically Patch RSA",
|
||||
"use_proxy": "Use Internal Proxy",
|
||||
"wipe_login": "Wipe Login Cache",
|
||||
"horny_mode": "Horny Mode"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Download Grasscutter All-in-One",
|
||||
"grasscutter_stable_data": "Download Grasscutter Stable Data",
|
||||
"grasscutter_latest_data": "Download Grasscutter Latest Data",
|
||||
"grasscutter_stable_data_update": "Update Grasscutter Stable Data",
|
||||
@@ -35,7 +41,9 @@
|
||||
"grasscutter_stable_update": "Update Grasscutter Stable",
|
||||
"grasscutter_latest_update": "Update Grasscutter Latest",
|
||||
"resources": "Download Grasscutter Resources",
|
||||
"game": "Download Game"
|
||||
"game": "Download Game",
|
||||
"aio_header": "All-in-One Downloads:",
|
||||
"individual_header": "Individual downloads:"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Downloading",
|
||||
@@ -48,6 +56,7 @@
|
||||
"select_file": "Select file or folder...",
|
||||
"select_folder": "Select folder...",
|
||||
"download": "Download",
|
||||
"delete": "Delete",
|
||||
"install": "Install"
|
||||
},
|
||||
"news": {
|
||||
@@ -58,12 +67,22 @@
|
||||
"port_help_text": "Ensure this is the Dispatch server port, not the Game server port. This is almost always '443'.",
|
||||
"game_help_text": "You do not need to use a separate copy to play with Grasscutter. This is for either downgrading to 2.6 or if you do not have the game installed.",
|
||||
"gc_stable_jar": "Download the current stable Grasscutter build, which includes jar file and data files.",
|
||||
"gc_fullbuild": "Download a full Grasscutter build, including repo, jar, and resources. Is fully set up and does not require any other downloads from this menu.",
|
||||
"gc_dev_jar": "Download the latest development Grasscutter build, which includes jar file and data files.",
|
||||
"gc_stable_data": "Download the current stable Grasscutter data files, which does not come with a jar file. This is useful for updating.",
|
||||
"gc_dev_data": "Download the latest development Grasscutter data files, which does not come with a jar file. This is useful for updating.",
|
||||
"resources": "These are also required to run a Grasscutter server. This button will be grey if you have an existing resources folder with contents inside"
|
||||
"encryption": "This should usually be disabled.",
|
||||
"resources": "These are also required to run a Grasscutter server. This button will be grey if you have an existing resources folder with contents inside",
|
||||
"emergency_rsa": "In case something went wrong, force delete RSA patch.",
|
||||
"use_proxy": "Use the Cultivation internal proxy. You should have this enabled unless you use something like Fiddler",
|
||||
"patch_rsa": "Patch and unpatch your game RSA automatically. Unless playing with old/non-official versions (3.0 and older), this should be enabled."
|
||||
},
|
||||
"swag": {
|
||||
"akebi": "Set Akebi Executable"
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "Set Akebi/Acrepi Executable",
|
||||
"migoto": "Set 3DMigoto Executable",
|
||||
"reshade": "Set Reshade Injector"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
79
src-tauri/lang/es.json
Normal file
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"lang_name": "Español",
|
||||
"main": {
|
||||
"title": "Cultivation",
|
||||
"launch_button": "Launch",
|
||||
"gc_enable": "Conectar Via Grasscutter",
|
||||
"https_enable": "Usar HTTPS",
|
||||
"ip_placeholder": "Dirección del servidor...",
|
||||
"port_placeholder": "Puerto...",
|
||||
"files_downloading": "Archivos Descargandose: ",
|
||||
"files_extracting": "Archivos Extrayendose: "
|
||||
},
|
||||
"options": {
|
||||
"enabled": "Activado",
|
||||
"disabled": "Desactivado",
|
||||
"game_path": "Ruta de instalación del juego",
|
||||
"game_command": "Comando de lanzamiento del juego",
|
||||
"game_executable": "Establecer ejecutable del juego",
|
||||
"recover_rsa": "Recuperación de RSA de Emergencia",
|
||||
"grasscutter_jar": "Establecer JAR de Grasscutter",
|
||||
"toggle_encryption": "Alternar Cifrado",
|
||||
"install_certificate": "Instalar Certificado Proxie",
|
||||
"java_path": "Establecer Ruta Personalizada de Java",
|
||||
"grasscutter_with_game": "Iniciar automáticamente Grasscutter con el juego",
|
||||
"language": "Seleccionar Idioma",
|
||||
"background": "Establecer Fondo Personalizado (link o archivo de imagen)",
|
||||
"patch_rsa": "Parchear RSA automáticamente",
|
||||
"theme": "Establecer Tema",
|
||||
"use_proxy": "Use Internal Proxy",
|
||||
"wipe_login": "Wipe Login Cache",
|
||||
"horny_mode": "Horny Mode"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Descargar Datos todo en uno de Grasscutter",
|
||||
"grasscutter_stable_data": "Descargar Datos Estables de Grasscutter",
|
||||
"grasscutter_latest_data": "Descargar Datos más Recientes de Grasscutter",
|
||||
"grasscutter_stable_data_update": "Actualizar Datos Estables de Grasscutter",
|
||||
"grasscutter_latest_data_update": "Actualizar Datos más Recientes de Grasscutter",
|
||||
"grasscutter_stable": "Descargar Grasscutter Estable",
|
||||
"grasscutter_latest": "Descargar Grasscutter más reciente",
|
||||
"grasscutter_stable_update": "Actualizar Grasscutter Estable",
|
||||
"grasscutter_latest_update": "Actualizar Grasscutter más reciente",
|
||||
"resources": "Descargar Recursos de Grasscutter",
|
||||
"game": "Descarga el juego",
|
||||
"aio_header": "Descargas todo en uno:",
|
||||
"individual_header": "Descargas de piezas individuales:"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Descargando",
|
||||
"extracting": "Extrayendo",
|
||||
"error": "Error",
|
||||
"finished": "Finalizado",
|
||||
"stopped": "Detenido"
|
||||
},
|
||||
"components": {
|
||||
"select_file": "Seleccionar el archivo o carpeta...",
|
||||
"select_folder": "Seleccionar la carpeta...",
|
||||
"download": "Descargar",
|
||||
"delete": "Borrar",
|
||||
"install": "Instalar"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Commits Recientes",
|
||||
"latest_version": "Ultima versión"
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "Asegúrese de que este sea el Dispatch server port, no el Game server port. Este es casi siempre '443'.",
|
||||
"game_help_text": "No necesitas usar una copia separada para jugar con Grasscutter. Esto es para cambiar a 2.6 o si no tienes el juego instalado.",
|
||||
"gc_stable_jar": "Descargue la versión Estable actual de Grasscutter, que incluye el archivo jar y los archivos de datos.",
|
||||
"gc_fullbuild": "Descarga una compilación completa de Grasscutter, incluyendo repo, jar y recursos. Está totalmente configurado y no requiere ninguna otra descarga desde este menú.",
|
||||
"gc_dev_jar": "Descargue la última versión de Desarrollo de Grasscutter, que incluye archivos jar y archivos de datos.",
|
||||
"gc_stable_data": "Descargue los archivos de Datos Estables actuales de Grasscutter, que no vienen con un archivo jar. Esto es útil para actualizar.",
|
||||
"gc_dev_data": "Descargue los últimos archivos de Datos de Desarrollo de Grasscutter, que no vienen con un archivo jar. Esto es útil para actualizar.",
|
||||
"resources": "Estos también son necesarios para ejecutar un servidor Grasscutter. Este botón estará gris si tiene una carpeta de recursos existente con contenido dentro."
|
||||
},
|
||||
"swag": {
|
||||
"akebi": "Establecer el ejecutable de Akebi"
|
||||
}
|
||||
}
|
||||
@@ -13,16 +13,25 @@
|
||||
"options": {
|
||||
"enabled": "active",
|
||||
"disabled": "desactiver",
|
||||
"game_path": "Définir le chemin d'installation du jeu",
|
||||
"game_command": "Commande de lancement du jeu",
|
||||
"game_executable": "definir l'executable du jeu",
|
||||
"recover_rsa": "Récupération d'urgence des RSA",
|
||||
"grasscutter_jar": "definir le Jar Grasscutter",
|
||||
"toggle_encryption": "activer l'encryption",
|
||||
"install_certificate": "Installer le certificat du proxy",
|
||||
"java_path": "definir un chemin java personnalise",
|
||||
"grasscutter_with_game": "Lancer Grasscutter automatiquement avec le jeu",
|
||||
"language": "Choisir la langue",
|
||||
"background": "definir un arriere plan personnalise (lien ou fichier image)",
|
||||
"theme": "definir un theme"
|
||||
"theme": "definir un theme",
|
||||
"patch_rsa": "Corriger automatiquement les RSA",
|
||||
"use_proxy": "Utiliser un proxy interne",
|
||||
"wipe_login": "Effacer le cache de connexion",
|
||||
"horny_mode": "Mode excitation"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Telecharger Grasscutter tout-en-un",
|
||||
"grasscutter_stable_data": "Telecharger les donnees de Grasscutter (version stable)",
|
||||
"grasscutter_latest_data": "Telecharger les donnees de Grasscutter (derniere version)",
|
||||
"grasscutter_stable_data_update": "Mettre a jour les donnees de Grasscutter (version stable)",
|
||||
@@ -31,7 +40,9 @@
|
||||
"grasscutter_latest": "Telecharger Grasscutter (derniere version)",
|
||||
"grasscutter_stable_update": "Mettre a jour Grasscutter (version stable)",
|
||||
"grasscutter_latest_update": "Mettre a jour Grasscutter (derniere version)",
|
||||
"resources": "Telecharger les ressources logicielles de Grasscutter"
|
||||
"resources": "Telecharger les ressources logicielles de Grasscutter",
|
||||
"aio_header": "Telechargements tout-en-un:",
|
||||
"individual_header": "Telechargements de pièces individuelles:"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Telechargement",
|
||||
@@ -43,7 +54,9 @@
|
||||
"components": {
|
||||
"select_file": "choisir fichier ou dossier...",
|
||||
"select_folder": "choisir dossier...",
|
||||
"download": "Telecharger"
|
||||
"download": "Telecharger",
|
||||
"delete": "Supprimer",
|
||||
"install": "Installer"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Recents Commits",
|
||||
@@ -53,6 +66,7 @@
|
||||
"port_help_text": "Assurez-vous que c'est le port serveur Dispatch, et non le port du serveur de jeu. C'est presque toujours '433'.",
|
||||
"game_help_text": "Vous n'avez pas besoin d'une copie differente du jeu pour jouer avec Grasscutter. Cela est ou pour retrograder en 2.6 ou si vous n'avez pas le jeu d'installe",
|
||||
"gc_stable_jar": "Telecharger le dernier build stable de Grasscutter, ce qui inclut le fichier jar et les fichiers de donnees",
|
||||
"gc_fullbuild": "Téléchargez un build complet de Grasscutter, incluant le repo, le jar et les ressources. Il est entièrement configuré et ne nécessite aucun autre téléchargement à partir de ce menu.",
|
||||
"gc_dev_jar": "Telecharger le dernier build en development de Grasscutter, ce qui inclut le fichier jar et les fichiers de donnees",
|
||||
"gc_stable_data": "Telecharger le dernier build stable de Grasscutter, ce qui n'inclut pasle fichier jar. Cela est utile pour mettre a jour",
|
||||
"gc_dev_data": "Telecharger le dernier build en development de Grasscutter, ce qui n'inclut pasle fichier jar. Cela est utile pour mettre a jour",
|
||||
|
||||
@@ -10,15 +10,27 @@
|
||||
"files_extracting": "MengExtract File: "
|
||||
},
|
||||
"options": {
|
||||
"game_executable": "Set Game Executable",
|
||||
"enabled": "Diaktifkan",
|
||||
"disabled": "Dinonaktifkan",
|
||||
"game_path": "Mengatur jalur penginstalan game",
|
||||
"game_command": "Perintah peluncuran game",
|
||||
"game_executable": "Mengatur game yang dapat dieksekusi",
|
||||
"recover_rsa": "Pemulihan RSA darurat",
|
||||
"grasscutter_jar": "Path ke Grasscutter JAR",
|
||||
"toggle_encryption": "Alihkan enkripsi",
|
||||
"install_certificate": "Instal sertifikat proxy",
|
||||
"java_path": "Atur kustom Java path",
|
||||
"grasscutter_with_game": "Otomatis Menjalankan Grasscutter Dengan Game",
|
||||
"language": "Pilih Bahasa",
|
||||
"background": "Atur Kustom Latar Belakang (link atau gambar file)",
|
||||
"theme": "Atur Tema"
|
||||
"theme": "Atur Tema",
|
||||
"patch_rsa": "Automatically Patch RSA",
|
||||
"use_proxy": "Gunakan Proxy Internal",
|
||||
"wipe_login": "Menghapus Cache Login",
|
||||
"horny_mode": "Mode Terangsang"
|
||||
},
|
||||
"downloads": {
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Sedang Mendownload Grasscutter Semua Dalam Satu",
|
||||
"grasscutter_stable_data": "Sedang Mendownload Grasscutter Versi Stabil",
|
||||
"grasscutter_latest_data": "Sedang Mendownload Grasscutter Data Terbaru",
|
||||
"grasscutter_stable_data_update": "Memperbaharui Grasscutter Data Stabil",
|
||||
@@ -27,7 +39,9 @@
|
||||
"grasscutter_latest": "Download Grasscutter Terbaru Version",
|
||||
"grasscutter_stable_update": "Sedang MengUpdate Grasscutter Stabil",
|
||||
"grasscutter_latest_update": "Sedang MengUpdate Grasscutter Terbaru",
|
||||
"resources": "Mendownload Grasscutter Resources"
|
||||
"resources": "Mendownload Grasscutter Resources",
|
||||
"aio_header": "Unduhan Semua Dalam Satu:",
|
||||
"individual_header": "Unduhan Bagian Individual:"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Sedang Mendownload",
|
||||
@@ -39,7 +53,8 @@
|
||||
"components": {
|
||||
"select_file": "Pilih File Atau Folder...",
|
||||
"select_folder": "Pilih Folder...",
|
||||
"download": "download"
|
||||
"download": "download",
|
||||
"delete": "Menghapus"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Commit Terbaru",
|
||||
@@ -49,9 +64,10 @@
|
||||
"port_help_text": "Pastikan ini adalah port server port, bukan port server Game. ini Hampir Selalu '443'.",
|
||||
"game_help_text": "Anda tidak perlu menggunakan salinan Genshin Impact untuk bermain dengan Grasscutter. Ini untuk menurunkan versi ke 2.6 atau jika Anda belum menginstal game..",
|
||||
"gc_stable_jar": "Unduh Build Stabil Grasscutter Saat ini, Dimana Ada Jar File Dan Data File.",
|
||||
"gc_fullbuild": "Unduh versi lengkap Grasscutter, termasuk repo, jar, dan sumber daya. Sudah diatur sepenuhnya dan tidak memerlukan unduhan lain dari menu ini.",
|
||||
"gc_dev_jar": "Unduh Build Development Grasscutter saat ini, Dimana Ada Jar File Dan Data File.",
|
||||
"gc_stable_data": "Unduh file data Grasscutter stabil saat ini, dimana Tidak Ada JAR file. Ini Berguna Untuk memperbarui.",
|
||||
"gc_dev_data": "Unduh file data Grasscutter Development saat ini, dimana Tidak Ada JAR file. Ini Berguna Untuk memperbarui.",
|
||||
"resources": "Ini juga diperlukan untuk menjalankan server Grasscutter. Tombol ini akan berwarna abu-abu jika Anda memiliki folder Resource yang ada dengan File di dalamnya"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
88
src-tauri/lang/ko.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"lang_name": "한국어",
|
||||
"main": {
|
||||
"title": "Cultivation",
|
||||
"launch_button": "게임 시작",
|
||||
"gc_enable": "Grasscutter 연결",
|
||||
"https_enable": "HTTPS 사용",
|
||||
"ip_placeholder": "서버 주소",
|
||||
"port_placeholder": "포트",
|
||||
"files_downloading": "파일 다운로드 중: ",
|
||||
"files_extracting": "파일 추출 중: "
|
||||
},
|
||||
"options": {
|
||||
"enabled": "활성",
|
||||
"disabled": "비활성",
|
||||
"game_path": "게임 설치 경로 설정",
|
||||
"game_command": "게임 시작 명령",
|
||||
"game_executable": "게임 실행 파일 설정",
|
||||
"recover_rsa": "긴급 RSA 복원",
|
||||
"grasscutter_jar": "그래스커터 JAR 설정",
|
||||
"toggle_encryption": "암호화 전환",
|
||||
"install_certificate": "프록시 인증서 설치",
|
||||
"java_path": "사용자 지정 Java 경로 설정",
|
||||
"grasscutter_with_game": "게임에서 자동으로 그래스커터 실행",
|
||||
"language": "언어 선택",
|
||||
"background": "사용자 지정 배경 설정(링크 또는 이미지 파일)",
|
||||
"theme": "테마 설정",
|
||||
"patch_rsa": "RSA 패치 자동 적용",
|
||||
"use_proxy": "내부 프록시 사용",
|
||||
"wipe_login": "로그인 캐시 지우기",
|
||||
"horny_mode": "Horny 모드"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "올인원 Grasscutter 다운로드",
|
||||
"grasscutter_stable_data": "안정적인 데이터 다운로드",
|
||||
"grasscutter_latest_data": "최신 데이터 다운로드",
|
||||
"grasscutter_stable_data_update": "안정적 데이터 업데이트",
|
||||
"grasscutter_latest_data_update": "최신 데이터 업데이트",
|
||||
"grasscutter_stable": "안정 다운로드",
|
||||
"grasscutter_latest": "최신 다운로드",
|
||||
"grasscutter_stable_update": "안정 업데이트",
|
||||
"grasscutter_latest_update": "최신 업데이트",
|
||||
"resources": "리소스 다운로드",
|
||||
"game": "게임 다운로드",
|
||||
"aio_header": "올인원 다운로드",
|
||||
"individual_header": "개별 부품 다운로드:"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "다운로드 중",
|
||||
"extracting": "추출 중",
|
||||
"error": "오류",
|
||||
"finished": "완료",
|
||||
"stopped": "중지"
|
||||
},
|
||||
"components": {
|
||||
"select_file": "파일 또는 폴더 선택...",
|
||||
"select_folder": "폴더 선택...",
|
||||
"download": "다운로드",
|
||||
"delete": "삭제",
|
||||
"install": "설치"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "공지 사항",
|
||||
"latest_version": "최신 버전"
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "게임 서버 포트가 아닌 패치 서버 포트인지 확인하십시오. (기본 포트: 443)",
|
||||
"game_help_text": "그래스커터를 사용하기 위해 별도의 복사본을 사용할 필요가 없습니다. 이는 2.6으로 다운그레이드 하거나 게임을 설치하지 않은 경우에 적용됩니다.",
|
||||
"gc_stable_jar": "jar 파일과 데이터 파일이 포함된 현재 안정적인 Grasscuter 빌드를 다운로드합니다.",
|
||||
"gc_fullbuild": "저장소, 저장소, 리소스를 포함한 Grasscutter 정식 버전을 다운로드하세요. 모든 준비가 완료되었으며 이 메뉴에서 다른 다운로드는 필요하지 않습니다.",
|
||||
"gc_dev_jar": "jar 파일 및 데이터 파일이 포함된 최신 개발 Grasscuter 빌드를 다운로드하십시오.",
|
||||
"gc_stable_data": "jar 파일과 함께 제공되지 않는 현재 안정적인 Grasscuter 데이터 파일을 다운로드합니다. 이것은 업데이트하는 데 유용합니다.",
|
||||
"gc_dev_data": "jar 파일과 함께 제공되지 않는 최신 개발 Grasscuter 데이터 파일을 다운로드합니다. 이것은 업데이트하는 데 유용합니다.",
|
||||
"encryption": "일반적으로 이 기능을 사용하지 않도록 설정해야 합니다.",
|
||||
"resources": "또한 Grasscutter 서버를 실행하는 데도 필요합니다. 내용이 포함된 기존 리소스 폴더가 있는 경우 이 버튼은 회색으로 표시됩니다",
|
||||
"emergency_rsa": "문제가 있는 경우 RSA 패치를 제거하십시오.",
|
||||
"use_proxy": "Culturation 내부 프록시를 사용합니다. 피들러와 같은 것을 사용하지 않는 한 이 기능을 활성화해야 합니다",
|
||||
"patch_rsa": "게임 RSA를 자동으로 패치 및 패치 해제합니다. 이전/비공식 버전을 사용하거나 RSA를 수동으로 패치하지 않은 경우 이 기능을 활성화해야 합니다."
|
||||
},
|
||||
"swag": {
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "Akebi 실행 파일 설정",
|
||||
"migoto": "3DMigoto 실행 파일 설정",
|
||||
"reshade": "Reshade 인젝터 설정"
|
||||
}
|
||||
}
|
||||
@@ -14,15 +14,22 @@
|
||||
"enabled": "Iespējots",
|
||||
"disabled": "Atspējots",
|
||||
"game_executable": "Iestatīt spēles izpildāmu",
|
||||
"recover_rsa": "Avārijas RSA atjaunošana",
|
||||
"grasscutter_jar": "Iestatiet Grasscutter JAR",
|
||||
"toggle_encryption": "Pārslēgt Šifrēšanu",
|
||||
"install_certificate": "Proxy sertifikāta instalēšana",
|
||||
"java_path": "Iestatiet pielāgotu Java ceļu",
|
||||
"grasscutter_with_game": "Automātiski palaidiet Grasscutter ar spēli",
|
||||
"language": "Izvēlēties valodu",
|
||||
"background": "Iestatīt pielāgotu fonu (saite vai attēla fails)",
|
||||
"theme": "Iestatīt tēmu"
|
||||
"theme": "Iestatīt tēmu",
|
||||
"patch_rsa": "Automātiski ielāpot RSA",
|
||||
"use_proxy": "Izmantot iekšējo starpniekserveri",
|
||||
"wipe_login": "Noslaucīt pieteikšanās kešatmiņu",
|
||||
"horny_mode": "Uzbudināts režīms"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Lejupielādējiet Grasscutter viss vienā",
|
||||
"grasscutter_stable_data": "Lejupielādējiet Grasscutter stabilos datus",
|
||||
"grasscutter_latest_data": "Lejupielādējiet Grasscutter jaunākos datus",
|
||||
"grasscutter_stable_data_update": "Atjauniniet Grasscutter stabilos datus",
|
||||
@@ -31,7 +38,9 @@
|
||||
"grasscutter_latest": "Lejupielādēt Grasscutter jaunāko",
|
||||
"grasscutter_stable_update": "Atjauniet Grasscutter stabilo",
|
||||
"grasscutter_latest_update": "Atjauniet Grasscutter jaunāko",
|
||||
"resources": "Lejupielādējiet Grasscutter resursi"
|
||||
"resources": "Lejupielādējiet Grasscutter resursi",
|
||||
"aio_header": "Lejupielādes viss vienā",
|
||||
"individual_header": "Atsevišķu daļu lejupielādes:"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Notiek lejupielāde",
|
||||
@@ -43,7 +52,8 @@
|
||||
"components": {
|
||||
"select_file": "Izvēlēties failu vai mapu...",
|
||||
"select_folder": "Izvēlēties mapu...",
|
||||
"download": "Lejupielādēt"
|
||||
"download": "Lejupielādēt",
|
||||
"delete": "Dzēst"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Nesen kommitus",
|
||||
@@ -53,6 +63,7 @@
|
||||
"port_help_text": "Pārliecinieties, vai tas ir Dispatch-servera ports, nevis spēļu servera ports. Tas gandrīz vienmēr ir '443'.",
|
||||
"game_help_text": "Lai spēlētu ar Grasscutter, jums nav jāizmanto atsevišķa kopija. Tas ir izveidots, lai pazeminātu versiju uz 2.6 vai ja jums nav instalēta spēle.",
|
||||
"gc_stable_jar": "Lejupielādējiet pašreizējo stabilo Grasscutter versiju, kuram ir jar failu un datu failus.",
|
||||
"gc_fullbuild": "Lejupielādējiet pilnu Grasscutter versiju, tostarp repozitorijus, krātuves un resursus. Viss ir gatavs, un no šīs izvēlnes nav nepieciešama nekāda cita lejupielāde.",
|
||||
"gc_dev_jar": "Lejupielādējiet jaunāko izstrāde Grasscutter versiju, kuram ir jar failu un datu failus.",
|
||||
"gc_stable_data": "Lejupielādējiet pašreizējos stabilos Grasscutter datu failus, kuriem nav jar fails. Tas ir noderīgi atjaunināšanai.",
|
||||
"gc_dev_data": "Lejupielādējiet jaunāko izstrāde Grasscutter datu failus, kuriem nav pievienots jar fails. Tas ir noderīgi atjaunināšanai.",
|
||||
|
||||
87
src-tauri/lang/nl.json
Normal file
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"lang_name": "Nederlands",
|
||||
"main": {
|
||||
"title": "Cultivation",
|
||||
"launch_button": "Start",
|
||||
"gc_enable": "Verbind Met Grasscutter",
|
||||
"https_enable": "Gebruik HTTPS",
|
||||
"ip_placeholder": "Server Address...",
|
||||
"port_placeholder": "Poort...",
|
||||
"files_downloading": "Bestanden Aan Downloaden: ",
|
||||
"files_extracting": "Bestanden Uitpakken: "
|
||||
},
|
||||
"options": {
|
||||
"enabled": "Ingeschakeld",
|
||||
"disabled": "Uitgeschakeld",
|
||||
"game_path": "Spel Installatie Pad Instellen",
|
||||
"game_executable": "Stel De Exe Van Het Spel In",
|
||||
"recover_rsa": "Noodherstel Van De RSA",
|
||||
"grasscutter_jar": "Stel De Grasscutter JAR In",
|
||||
"toggle_encryption": "Versleuteling Inschakelen",
|
||||
"install_certificate": "Proxy-certificaat installeren",
|
||||
"java_path": "Aangepast Java-pad Instellen",
|
||||
"grasscutter_with_game": "Start Automatisch Grasscutter Met Spel",
|
||||
"language": "Selecteer Taal",
|
||||
"background": "Aangepaste Achtergrond Instellen (link of afbeeldingsbestand)",
|
||||
"theme": "Thema instellen",
|
||||
"patch_rsa": "RSA Automatisch Bijwerken",
|
||||
"use_proxy": "Gebruik Interne Proxy",
|
||||
"wipe_login": "Login cache wissen",
|
||||
"horny_mode": "Geile modus"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Grasscutter Alles-in-één Downloaden",
|
||||
"grasscutter_stable_data": "Download Stabiele Gegevens Van Grasscutter",
|
||||
"grasscutter_latest_data": "Download De Nieuwste Gegevens Van Grasscutter",
|
||||
"grasscutter_stable_data_update": "Stabiele gegevens Van Grasscutter bijwerken",
|
||||
"grasscutter_latest_data_update": "Nieuwste gegevens Van Grasscutter bijwerken",
|
||||
"grasscutter_stable": "Download Stabiele Versie Van Grasscutter",
|
||||
"grasscutter_latest": "Download De Nieuwste Versie Van Grasscutter",
|
||||
"grasscutter_stable_update": "Update Grasscutter Naar De Stabiele Versie",
|
||||
"grasscutter_latest_update": "Update Grasscutter Naar De Nieuwste Versie",
|
||||
"resources": "Download Grasscutter bronnen",
|
||||
"game": "Download Spel",
|
||||
"aio_header": "Alles-in-één Downloads:",
|
||||
"individual_header": "Downloads van afzonderlijke onderdelen:"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Aan Het Downloading",
|
||||
"extracting": "Uitpakken",
|
||||
"error": "Fout",
|
||||
"finished": "Voltooid",
|
||||
"stopped": "Gestopt"
|
||||
},
|
||||
"components": {
|
||||
"select_file": "Select file or folder...",
|
||||
"select_folder": "Select folder...",
|
||||
"download": "Download",
|
||||
"delete": "Verwijder",
|
||||
"install": "Install"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Recente Opdrachten",
|
||||
"latest_version": "Nieuwste Versie"
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "Zorg ervoor dat dit de Dispatch server poort is, niet de Game server poort. Dit is bijna altijd '443'.",
|
||||
"game_help_text": "U hoeft geen aparte kopie te gebruiken om met Grasscutter te spelen. Dit is voor downgraden naar 2.6 of als u het spel niet geinstalleerd heeft.",
|
||||
"gc_stable_jar": "Download de huidige stabiele Grasscutter build, die jar file en data bestanden bevat.",
|
||||
"gc_fullbuild": "Download een volledige Grasscutter build, inclusief repo, jar en resources. Is volledig ingesteld en vereist geen andere downloads uit dit menu.",
|
||||
"gc_dev_jar": "Download de laatste ontwikkeling Grasscutter build, die jar file en data bestanden bevat.",
|
||||
"gc_stable_data": "Download de huidige stabiele versie van de Grasscutter data bestanden, die niet met een jar file komen. Dit is handig voor het bijwerken.",
|
||||
"gc_dev_data": "Download de nieuwste versie van de Grasscutter data bestanden, die niet met een jar file komen. Dit is handig voor het bijwerken.",
|
||||
"encryption": "Dit wordt meestal uitgeschakeld.",
|
||||
"resources": "Deze zijn ook nodig om een Grasscutter server te draaien. Deze knop zal grijs zijn als u een bestaande resources map heeft met inhoud erin",
|
||||
"emergency_rsa": "Voor het geval er iets fout is gegaan, herstel uw rsa naar de laatste offici<63>le versies rsa.",
|
||||
"use_proxy": "Gebruik de Cultivation interne proxy. U zou dit ingeschakeld moeten hebben, tenzij u iets als Fiddler gebruikt",
|
||||
"patch_rsa": "Patch en unpatch je spel rsa automatisch. Tenzij je met oude/niet-offici<63>le versies speelt, of je hebt je rsa handmatig gepatcht, zou dit ingeschakeld moeten zijn."
|
||||
},
|
||||
"swag": {
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "Stel Akebi Exe Bestand In",
|
||||
"migoto": "Stel 3DMigoto Exe Bestand In",
|
||||
"reshade": "Stel Reshade Injector In"
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,87 @@
|
||||
{
|
||||
"lang_name": "Русский",
|
||||
"main": {
|
||||
"title": "Cultivation",
|
||||
"launch_button": "Запустить",
|
||||
"gc_enable": "Подключиться с Grasscutter",
|
||||
"https_enable": "Исп. HTTPS",
|
||||
"ip_placeholder": "Айпи адрес...",
|
||||
"port_placeholder": "Порт...",
|
||||
"files_downloading": "Файлов скачано: ",
|
||||
"files_extracting": "Извлечено файлов: "
|
||||
},
|
||||
"options": {
|
||||
"enabled": "Включено",
|
||||
"disabled": "Выключено",
|
||||
"game_executable": "Установить исполняемый файл игры",
|
||||
"grasscutter_jar": "Установить Grasscutter JAR",
|
||||
"toggle_encryption": "Переключить шифрование",
|
||||
"java_path": "Установить пользовательский путь Java",
|
||||
"grasscutter_with_game": "Автоматически запускать Grasscutter вместе с игрой",
|
||||
"language": "Установить язык",
|
||||
"background": "Установить свой фон (ссылка или файл)",
|
||||
"theme": "Установить тему"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "Скачать стабильные данные Grasscutter",
|
||||
"grasscutter_latest_data": "Скачать последние данные Grasscutter",
|
||||
"grasscutter_stable_data_update": "Обновить стабильные данные Grasscutter",
|
||||
"grasscutter_latest_data_update": "Обновить последние данные Grasscutter",
|
||||
"grasscutter_stable": "Скачать стабильную версию Grasscutter",
|
||||
"grasscutter_latest": "Скачать последнюю версию Grasscutter",
|
||||
"grasscutter_stable_update": "Обновить стабильную версию Grasscutter",
|
||||
"grasscutter_latest_update": "Обновить последнюю версию Grasscutter",
|
||||
"resources": "Скачать ресурсы Grasscutter"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Скачивание",
|
||||
"extracting": "Извлечение",
|
||||
"error": "Ошибка",
|
||||
"finished": "Закончено",
|
||||
"stopped": "Остановлено"
|
||||
},
|
||||
"components": {
|
||||
"select_file": "Выберите файл или папку...",
|
||||
"select_folder": "Выберите папку...",
|
||||
"download": "Скачать"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Последние коммиты",
|
||||
"latest_version": "Последняя версия"
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "Убедитесь, что это порт Dispatch-сервера, не порт игрового сервера. Обычно это '443'.",
|
||||
"game_help_text": "Вам не нужно устанавливать еще одну копию, что бы играть с Grascutter. Это нужно или для версии 2.6, или если у Вас не установлена игра.",
|
||||
"gc_stable_jar": "Скачать последнюю стабильную версию Grasscutter, которая содержит jar файл и данные.",
|
||||
"gc_dev_jar": "Скачать последнюю версию для разработки Grasscutter, которая содержит jar файл и данные.",
|
||||
"gc_stable_data": "Скачать стабильные данные Grasscutter, в которой нету jar файла. Это полезно для обновления.",
|
||||
"gc_dev_data": "Скачать последнюю версию для разработки Grasscutter, в которой нету jar файла. Это полезно для обновления.",
|
||||
"resources": "Это необходимо для запуска сервера Grasscutter. Эта кнопка будет серой, если у Вас уже есть не пустая папка с ресурсами."
|
||||
}
|
||||
}
|
||||
{
|
||||
"lang_name": "Русский",
|
||||
"main": {
|
||||
"title": "Cultivation",
|
||||
"launch_button": "Запустить",
|
||||
"gc_enable": "Подключиться с Grasscutter",
|
||||
"https_enable": "Исп. HTTPS",
|
||||
"ip_placeholder": "Айпи адрес...",
|
||||
"port_placeholder": "Порт...",
|
||||
"files_downloading": "Файлов скачано: ",
|
||||
"files_extracting": "Извлечено файлов: "
|
||||
},
|
||||
"options": {
|
||||
"enabled": "Включено",
|
||||
"disabled": "Выключено",
|
||||
"game_path": "Установить путь к файлам игры",
|
||||
"game_executable": "Установить исполняемый файл игры",
|
||||
"recover_rsa": "Принудительное удаление RSA",
|
||||
"grasscutter_jar": "Установить Grasscutter JAR",
|
||||
"toggle_encryption": "Переключить шифрование",
|
||||
"install_certificate": "Установить сертификат для работы Прокси",
|
||||
"java_path": "Установить пользовательский путь Java",
|
||||
"grasscutter_with_game": "Автоматически запускать Grasscutter вместе с игрой",
|
||||
"language": "Установить язык",
|
||||
"background": "Установить свой фон (ссылка или файл)",
|
||||
"theme": "Установить тему",
|
||||
"patch_rsa": "Автоматическое исправление RSA",
|
||||
"use_proxy": "Использовать встроенный Прокси",
|
||||
"wipe_login": "Очистить кэш входа в систему",
|
||||
"horny_mode": "роговой режим"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Скачать все в одном Grasscutter",
|
||||
"grasscutter_stable_data": "Скачать стабильные данные Grasscutter",
|
||||
"grasscutter_latest_data": "Скачать последние данные Grasscutter",
|
||||
"grasscutter_stable_data_update": "Обновить стабильные данные Grasscutter",
|
||||
"grasscutter_latest_data_update": "Обновить последние данные Grasscutter",
|
||||
"grasscutter_stable": "Скачать стабильную версию Grasscutter",
|
||||
"grasscutter_latest": "Скачать последнюю версию Grasscutter",
|
||||
"grasscutter_stable_update": "Обновить стабильную версию Grasscutter",
|
||||
"grasscutter_latest_update": "Обновить последнюю версию Grasscutter",
|
||||
"resources": "Скачать ресурсы Grasscutter",
|
||||
"game": "Скачать Игру",
|
||||
"aio_header": "Все в одной загрузке:",
|
||||
"individual_header": "загрузка отдельных частей:"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Скачивание",
|
||||
"extracting": "Извлечение",
|
||||
"error": "Ошибка",
|
||||
"finished": "Закончено",
|
||||
"stopped": "Остановлено"
|
||||
},
|
||||
"components": {
|
||||
"select_file": "Выберите файл или папку...",
|
||||
"select_folder": "Выберите папку...",
|
||||
"download": "Скачать",
|
||||
"delete": "Удалить",
|
||||
"install": "Установить"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Последние коммиты",
|
||||
"latest_version": "Последняя версия"
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "Убедитесь, что это порт Dispatch-сервера, не порт игрового сервера. Обычно это '443'.",
|
||||
"game_help_text": "Вам не нужно устанавливать еще одну копию, что бы играть с Grascutter. Это нужно или для версии 2.6, или если у Вас не установлена игра.",
|
||||
"gc_stable_jar": "Скачать последнюю стабильную версию Grasscutter, которая содержит jar файл и данные.",
|
||||
"gc_fullbuild": "Загрузите полную сборку Grasscutter, включая репо, jar и ресурсы. Полностью настроена и не требует других загрузок из этого меню.",
|
||||
"gc_dev_jar": "Скачать последнюю версию для разработки Grasscutter, которая содержит jar файл и данные.",
|
||||
"gc_stable_data": "Скачать стабильные данные Grasscutter, в которой нету jar файла. Это полезно для обновления.",
|
||||
"gc_dev_data": "Скачать последнюю версию для разработки Grasscutter, в которой нету jar файла. Это полезно для обновления.",
|
||||
"encryption": "Обычно это должно быть выключено.",
|
||||
"resources": "Это необходимо для запуска сервера Grasscutter. Эта кнопка будет серой, если у Вас уже есть не пустая папка с ресурсами.",
|
||||
"emergency_rsa": "Если что-то пошло не так, восстановит RSA до последней официальной версии.",
|
||||
"use_proxy": "Использовать встроенный Прокси. Отключите если используете Fiddler или подобную программу",
|
||||
"patch_rsa": "Патчит и восстанавливает RSA автоматически. Если вы не играете на старых/модифицированых версиях, или сами в ручную патчите Метаданные, эта опция должна быть включена."
|
||||
},
|
||||
"swag": {
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "Путь к исполняемому файлу Akebi",
|
||||
"migoto": "Путь к исполняемому файлу 3DMigoto ",
|
||||
"reshade": "Путь к инжектору Reshade"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,59 +3,86 @@
|
||||
"main": {
|
||||
"title": "Cultivation",
|
||||
"launch_button": "Khởi Chạy",
|
||||
"gc_enable": "Kết nối đến Grasscutter",
|
||||
"https_enable": "Sử dụng HTTPS",
|
||||
"gc_enable": "Kết nối qua Grasscutter",
|
||||
"https_enable": "Dùng HTTPS",
|
||||
"ip_placeholder": "Địa chỉ máy chủ...",
|
||||
"port_placeholder": "Cổng...",
|
||||
"files_downloading": "Đang tải file: ",
|
||||
"files_extracting": "Đang giải nén tệp tin: "
|
||||
"files_downloading": "Đang tải tập tin: ",
|
||||
"files_extracting": "Đang giải nén tập tin: "
|
||||
},
|
||||
"options": {
|
||||
"enabled": "Bật",
|
||||
"disabled": "Tắt",
|
||||
"game_exec": "Đường dẫn đến GenshinImpact.exe",
|
||||
"grasscutter_jar": "Đường dẫn đến Grasscutter.jar",
|
||||
"toggle_encryption": "Bật/Tắt mã hoá",
|
||||
"java_path": "Đường dẫn Java tuỳ chỉnh",
|
||||
"grasscutter_with_game": "Tự động khởi chạy Grasscutter cùng game",
|
||||
"language": "Chọn ngôn ngữ",
|
||||
"background": "Ảnh nền tuỳ chỉnh (đường dẫn hoặc tệp tin ảnh)",
|
||||
"theme": "Chọn giao diện"
|
||||
"game_path": "Đường dẫn cài game",
|
||||
"game_command": "Lệnh khởi chạy game",
|
||||
"game_executable": "Tập tin thực thi game",
|
||||
"recover_rsa": "Khôi phục RSA khẩn cấp",
|
||||
"grasscutter_jar": "Tập tin JAR Grasscutter",
|
||||
"toggle_encryption": "Bật/tắt mã hóa",
|
||||
"install_certificate": "Cài chứng chỉ proxy",
|
||||
"java_path": "Đường dẫn Java tùy chỉnh",
|
||||
"grasscutter_with_game": "Tự động chạy Grasscutter cùng với game",
|
||||
"language": "Ngôn ngữ",
|
||||
"background": "Hình nền tùy chỉnh (liên kết hoặc tập tin hình ảnh)",
|
||||
"theme": "Giao diện",
|
||||
"patch_rsa": "Tự động vá RSA",
|
||||
"use_proxy": "Sử dụng proxy nội bộ",
|
||||
"wipe_login": "Tẩy sạch cache đăng nhập",
|
||||
"horny_mode": "Chế độ hứng tình"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "Tải xuống dữ liệu Grasscutter bản ổn định",
|
||||
"grasscutter_latest_data": "Tải xuống dữ liệu Grasscutter bản mới nhất",
|
||||
"grasscutter_fullbuild": "Tải Grasscutter tất cả trong một",
|
||||
"grasscutter_stable_data": "Tải dữ liệu Grasscutter bản ổn định",
|
||||
"grasscutter_latest_data": "Tải dữ liệu Grasscutter bản mới nhất",
|
||||
"grasscutter_stable_data_update": "Cập nhật dữ liệu Grasscutter bản ổn định",
|
||||
"grasscutter_latest_data_update": "Cập nhật dữ liệu Grasscutter bản mới nhất",
|
||||
"grasscutter_stable": "Tải xuống Grasscutter phiên bản ổn định",
|
||||
"grasscutter_latest": "Tải xuống Grasscutter phiển bản mới nhất",
|
||||
"grasscutter_stable_update": "Cập nhật Grasscutter ổn định",
|
||||
"grasscutter_latest_update": "Cập nhật Grasscutter mới nhất",
|
||||
"resources": "Tải xuống tài nguyên cho Grasscutter"
|
||||
"grasscutter_stable": "Tải Grasscutter bản ổn định",
|
||||
"grasscutter_latest": "Tải Grasscutter bản mới nhất",
|
||||
"grasscutter_stable_update": "Cập nhật Grasscutter bản ổn định",
|
||||
"grasscutter_latest_update": "Cập nhật Grasscutter bản mới nhất",
|
||||
"resources": "Tải tài nguyên Grasscutter",
|
||||
"game": "Tải game",
|
||||
"aio_header": "Tải xuống tất cả trong một:",
|
||||
"individual_header": "Tải xuống từng phần:"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Đang tải",
|
||||
"extracting": "Đang giải nén",
|
||||
"error": "Lỗi",
|
||||
"finished": "Đã xong",
|
||||
"finished": "Đã hoàn thành",
|
||||
"stopped": "Đã dừng"
|
||||
},
|
||||
"components": {
|
||||
"select_file": "Chọn tệp tin hoặc thư mục...",
|
||||
"select_file": "Chọn tập tin hoặc thư mục...",
|
||||
"select_folder": "Chọn thư mục...",
|
||||
"download": "Tải xuống"
|
||||
"download": "Tải",
|
||||
"delete": "Xóa bỏ",
|
||||
"install": "Cài"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Cập nhật gần đây",
|
||||
"latest_version": "Phiên bản mới nhất"
|
||||
"latest_commits": "Thay Đổi Gần Đây",
|
||||
"latest_version": "Phiên Bản Mới Nhất"
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "Đảm bảo đây là cổng của server Dispatch, không phải cổng của server Game. Thường là '443'.",
|
||||
"game_help_text": "Bạn không cần phải sử dụng một bản sao riêng để chơi với Grasscutter. Việc này chỉ xảy ra nếu bạn hạ phiên bản xuống 2.6 hoặc chưa cài đặt trò chơi.",
|
||||
"gc_stable_jar": "Tải xuống phiên bản ổn định của Grasscutter, bảo gồm file jar và các file dữ liệu.",
|
||||
"gc_dev_jar": "Tải xuống phiên bản phát triển mới nhất của Grasscutter, bảo gồm file jar và các file dữ liệu.",
|
||||
"gc_stable_data": "Tải xuống bản ổn định các tệp dữ liệu của Grasscutter, không bao gồm file jar. Phù hợp khi cập nhật.",
|
||||
"gc_dev_data": "Tải xuống bản phát triển mới nhất các tệp dữ liệu của Grasscutter, không bao gồm file jar. Phù hợp khi cập nhật.",
|
||||
"resources": "Chúng được yêu cầu để chạy máy chủ Grasscutter. Nút này sẽ có màu xám nếu bạn có một thư mục tài nguyên có nội dung bên trong"
|
||||
"port_help_text": "Hãy đảm bảo đây là cổng của máy chủ Dispatch, không phải cổng của máy chủ game. Thường sẽ là '443'.",
|
||||
"game_help_text": "Bạn không cần phải sử dụng một bản sao riêng để chơi với Grasscutter. Việc này chỉ xảy ra nếu bạn hạ phiên bản xuống 2.6 hoặc chưa cài game.",
|
||||
"gc_stable_jar": "Tải xuống phiên bản ổn định của Grasscutter, bao gồm tập tin jar và các tệp dữ liệu.",
|
||||
"gc_fullbuild": "Tải xuống bản dựng Grasscutter đầy đủ, bao gồm repo, jar và tài nguyên. Được thiết lập đầy đủ và không yêu cầu bất kỳ tải xuống nào khác từ menu này",
|
||||
"gc_dev_jar": "Tải xuống phiên bản phát triển mới nhất của Grasscutter, bao gồm tập tin jar và các tệp dữ liệu.",
|
||||
"gc_stable_data": "Tải xuống tệp dữ liệu phiên bản ổn định hiện hành của Grasscutter. Bản này không đi kèm với tập tin jar, hữu dụng khi muốn cập nhật.",
|
||||
"gc_dev_data": "Tải xuống tệp dữ liệu phiên bản mới nhất của Grasscutter. Bản này không đi kèm với tập tin jar, hữu dụng khi muốn cập nhật.",
|
||||
"encryption": "Mục này nên được tắt.",
|
||||
"resources": "Tài nguyên được yêu cầu để chạy máy chủ Grasscutter. Nút này sẽ có màu xám nếu bạn đã có sẵn một thư mục tài nguyên (resources) có nội dung bên trong",
|
||||
"emergency_rsa": "Trong trường hợp gặp vấn đề, khôi phục lại RSA về phiên bản chính thức mới nhất.",
|
||||
"use_proxy": "Sử dụng proxy nội bộ của Cultivation. Nên bật tùy chọn này trừ khi bạn đang sử dụng một ứng dụng khác, như Fiddler",
|
||||
"patch_rsa": "Tự động vá và sửa lại RSA của game. Tùy chọn này nên được bật trừ khi bạn đang sử dụng phiên bản cũ, phiên bản không chính thức hoặc bạn đã tự vá rsa rồi."
|
||||
},
|
||||
"swag": {
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "Tập tin thực thi Akebi",
|
||||
"migoto": "Tập tin thực thi 3dMigoto",
|
||||
"reshade": "Tập tin inject Reshade"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ bool gen_global_metadata_key(uint8_t* src, size_t srcn) {
|
||||
|
||||
uint64_t* key = (uint64_t*)src;
|
||||
|
||||
for (int i = 0; i < srcn / sizeof(uint64_t); i++)
|
||||
for (size_t i = 0; i < srcn / sizeof(uint64_t); i++)
|
||||
key[i] = rand();
|
||||
|
||||
*(uint16_t *) (src + 0xc8) = 0xfc2e; // Magic
|
||||
@@ -53,7 +53,7 @@ bool gen_global_metadata_key(uint8_t* src, size_t srcn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" void decrypt_global_metadata(uint8_t *data, size_t size) {
|
||||
void decrypt_global_metadata_inner(uint8_t *data, size_t size) {
|
||||
uint8_t longkey[0xB00];
|
||||
uint8_t longkeyp[0xB0];
|
||||
uint8_t shortkey[16];
|
||||
@@ -87,7 +87,16 @@ extern "C" void decrypt_global_metadata(uint8_t *data, size_t size) {
|
||||
recrypt_global_metadata_header_string_literals(data, size, literal_dec_key);
|
||||
}
|
||||
|
||||
extern "C" void encrypt_global_metadata(uint8_t* data, size_t size) {
|
||||
extern "C" int decrypt_global_metadata(uint8_t *data, size_t size) {
|
||||
try {
|
||||
decrypt_global_metadata_inner(data, size);
|
||||
return 0;
|
||||
} catch (...) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void encrypt_global_metadata_inner(uint8_t* data, size_t size) {
|
||||
uint8_t literal_dec_key[0x5000];
|
||||
|
||||
gen_global_metadata_key(data + size - 0x4000, 0x4000);
|
||||
@@ -126,3 +135,12 @@ extern "C" void encrypt_global_metadata(uint8_t* data, size_t size) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int encrypt_global_metadata(uint8_t* data, size_t size) {
|
||||
try {
|
||||
encrypt_global_metadata_inner(data, size);
|
||||
return 0;
|
||||
} catch (...) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
extern "C" void decrypt_global_metadata(uint8_t *data, size_t size);
|
||||
extern "C" void encrypt_global_metadata(uint8_t *data, size_t size);
|
||||
extern "C" int decrypt_global_metadata(uint8_t *data, size_t size);
|
||||
extern "C" int encrypt_global_metadata(uint8_t *data, size_t size);
|
||||
|
||||
#endif //METADATA_H
|
||||
|
||||
BIN
src-tauri/patch/version.dll
Normal file
@@ -1,3 +1,4 @@
|
||||
newline_style = "Unix"
|
||||
tab_spaces = 2
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
||||
|
||||
23
src-tauri/src/admin.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use std::process::exit;
|
||||
use std::process::Command;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn reopen_as_admin() {
|
||||
let install = std::env::current_exe().unwrap();
|
||||
|
||||
println!("Opening as admin: {}", install.to_str().unwrap());
|
||||
|
||||
Command::new("powershell.exe")
|
||||
.arg("powershell")
|
||||
.arg(format!(
|
||||
"-command \"&{{Start-Process -filepath '{}' -verb runas}}\"",
|
||||
install.to_str().unwrap()
|
||||
))
|
||||
.spawn()
|
||||
.expect("Error starting exec as admin");
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn reopen_as_admin() {}
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::fs;
|
||||
use file_diff::diff;
|
||||
use std::{io::{Read, Write}};
|
||||
use std::fs;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn rename(path: String, new_name: String) {
|
||||
@@ -18,22 +19,37 @@ pub fn rename(path: String, new_name: String) {
|
||||
|
||||
let path_replaced = &path.replace(&new_path.split('/').last().unwrap(), &new_name);
|
||||
|
||||
fs::rename(path, &path_replaced).unwrap();
|
||||
match fs::rename(&path, &path_replaced) {
|
||||
Ok(_) => {
|
||||
println!("Renamed {} to {}", &path, path_replaced);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn dir_create(path: String) {
|
||||
fs::create_dir_all(path).unwrap();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn dir_exists(path: &str) -> bool {
|
||||
fs::metadata(&path).is_ok()
|
||||
let path_buf = PathBuf::from(path);
|
||||
fs::metadata(path_buf).is_ok()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn dir_is_empty(path: &str) -> bool {
|
||||
fs::read_dir(&path).unwrap().count() == 0
|
||||
let path_buf = PathBuf::from(path);
|
||||
fs::read_dir(path_buf).unwrap().count() == 0
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn dir_delete(path: &str) {
|
||||
fs::remove_dir_all(path).unwrap();
|
||||
let path_buf = PathBuf::from(path);
|
||||
fs::remove_dir_all(path_buf).unwrap();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -44,18 +60,20 @@ pub fn are_files_identical(path1: &str, path2: &str) -> bool {
|
||||
#[tauri::command]
|
||||
pub fn copy_file(path: String, new_path: String) -> bool {
|
||||
let filename = &path.split('/').last().unwrap();
|
||||
let mut new_path_buf = std::path::PathBuf::from(&new_path);
|
||||
let path_buf = PathBuf::from(&path);
|
||||
|
||||
// If the new path doesn't exist, create it.
|
||||
if !dir_exists(new_path_buf.pop().to_string().as_str()) {
|
||||
if !dir_exists(PathBuf::from(&new_path).pop().to_string().as_str()) {
|
||||
std::fs::create_dir_all(&new_path).unwrap();
|
||||
}
|
||||
|
||||
// Copy old to new
|
||||
match std::fs::copy(&path, format!("{}/{}", new_path, filename)) {
|
||||
match std::fs::copy(&path_buf, format!("{}/{}", new_path, filename)) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
println!("Failed to copy file: {}", e);
|
||||
println!("Path: {}", path);
|
||||
println!("New Path: {}", new_path);
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -63,10 +81,11 @@ pub fn copy_file(path: String, new_path: String) -> bool {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn copy_file_with_new_name(path: String, new_path: String, new_name: String) -> bool {
|
||||
let mut new_path_buf = std::path::PathBuf::from(&new_path);
|
||||
let mut new_path_buf = PathBuf::from(&new_path);
|
||||
let path_buf = PathBuf::from(&path);
|
||||
|
||||
// If the new path doesn't exist, create it.
|
||||
if !dir_exists(new_path_buf.pop().to_string().as_str()) {
|
||||
if !dir_exists(PathBuf::from(&new_path).pop().to_string().as_str()) {
|
||||
match std::fs::create_dir_all(&new_path) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
@@ -76,11 +95,15 @@ pub fn copy_file_with_new_name(path: String, new_path: String, new_name: String)
|
||||
};
|
||||
}
|
||||
|
||||
new_path_buf.push(new_name);
|
||||
|
||||
// Copy old to new
|
||||
match std::fs::copy(&path, format!("{}/{}", new_path, new_name)) {
|
||||
match std::fs::copy(&path_buf, &new_path_buf) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
println!("Failed to copy file: {}", e);
|
||||
println!("Path: {}", path);
|
||||
println!("New Path: {}", new_path);
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -88,7 +111,9 @@ pub fn copy_file_with_new_name(path: String, new_path: String, new_name: String)
|
||||
|
||||
#[tauri::command]
|
||||
pub fn delete_file(path: String) -> bool {
|
||||
match std::fs::remove_file(path) {
|
||||
let path_buf = PathBuf::from(&path);
|
||||
|
||||
match std::fs::remove_file(path_buf) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
println!("Failed to delete file: {}", e);
|
||||
@@ -101,11 +126,18 @@ pub fn delete_file(path: String) -> bool {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn read_file(path: String) -> String {
|
||||
let mut file = match fs::File::open(path) {
|
||||
let path_buf = PathBuf::from(&path);
|
||||
|
||||
let mut file = match fs::File::open(path_buf) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
println!("Failed to open file: {}", e);
|
||||
return String::new();
|
||||
if path.contains("config") {
|
||||
// Server.ts won't print the error so handle the message here for the user
|
||||
println!("Server config not found or invalid. Be sure to run the server at least once to generate it before making edits.");
|
||||
} else {
|
||||
println!("Failed to open file: {}", e);
|
||||
}
|
||||
return String::new(); // Send back error for handling by the caller
|
||||
}
|
||||
};
|
||||
|
||||
@@ -117,20 +149,14 @@ pub fn read_file(path: String) -> String {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn write_file(path: String, contents: String) {
|
||||
let path_buf = PathBuf::from(&path);
|
||||
|
||||
// Create file if it exists, otherwise just open and rewrite
|
||||
let mut file = match fs::File::open(&path) {
|
||||
let mut file = match fs::File::create(&path_buf) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
println!("Failed to open file: {}", e);
|
||||
|
||||
// attempt to create file. otherwise return
|
||||
match fs::File::create(&path) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
println!("Failed to create file: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,7 +165,6 @@ pub fn write_file(path: String, contents: String) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!("Failed to write to file: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
84
src-tauri/src/gamebanana.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use crate::file_helpers;
|
||||
use crate::web;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::read_dir;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
|
||||
static SITE_URL: &str = "https://gamebanana.com";
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_download_links(mod_id: String) -> String {
|
||||
web::query(format!("{}/apiv9/Mod/{}/DownloadPage", SITE_URL, mod_id).as_str()).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_submissions(mode: String, page: String) -> String {
|
||||
web::query(
|
||||
format!(
|
||||
"{}/apiv9/Util/Game/Submissions?_idGameRow=8552&_nPage={}&_nPerpage=50&_sMode={}",
|
||||
SITE_URL, page, mode
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_mods(path: String) -> HashMap<String, String> {
|
||||
let mut path_buf = PathBuf::from(path);
|
||||
|
||||
// If the path includes a file, remove it
|
||||
if path_buf.file_name().is_some() {
|
||||
path_buf.pop();
|
||||
}
|
||||
|
||||
// Ensure we are in the Mods folder
|
||||
path_buf.push("Mods");
|
||||
|
||||
// Check if dir is empty
|
||||
if file_helpers::dir_is_empty(path_buf.to_str().unwrap()) {
|
||||
return HashMap::new();
|
||||
}
|
||||
|
||||
let mut mod_info_files = vec![];
|
||||
let mut mod_info_strings = HashMap::new();
|
||||
|
||||
for entry in read_dir(path_buf).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path();
|
||||
|
||||
// Check each dir for a modinfo.json file
|
||||
if path.is_dir() {
|
||||
let mut mod_info_path = path.clone();
|
||||
mod_info_path.push("modinfo.json");
|
||||
if mod_info_path.exists() {
|
||||
// Push path AND file contents into the hashmap using path as key
|
||||
mod_info_files.push(mod_info_path.to_str().unwrap().to_string());
|
||||
} else {
|
||||
// No modinfo, but we can still push a JSON obj with the folder name
|
||||
mod_info_strings.insert(
|
||||
path.to_str().unwrap().to_string(),
|
||||
format!(
|
||||
"{{ \"name\": \"{}\" }}",
|
||||
path.file_name().unwrap().to_str().unwrap()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read each modinfo.json file
|
||||
for mod_info_file in mod_info_files {
|
||||
let mut mod_info_string = String::new();
|
||||
|
||||
// It is safe to unwrap here since we *know* that the file exists
|
||||
let mut file = std::fs::File::open(&mod_info_file).unwrap();
|
||||
file.read_to_string(&mut mod_info_string).unwrap();
|
||||
|
||||
// Push into hashmap using path as key
|
||||
mod_info_strings.insert(mod_info_file, mod_info_string);
|
||||
}
|
||||
|
||||
mod_info_strings
|
||||
}
|
||||
@@ -3,64 +3,133 @@
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use file_helpers::dir_exists;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
use system_helpers::is_elevated;
|
||||
use tauri::api::path::data_dir;
|
||||
use tauri::async_runtime::block_on;
|
||||
|
||||
use std::thread;
|
||||
use structs::APIQuery;
|
||||
use sysinfo::{System, SystemExt};
|
||||
|
||||
use crate::admin::reopen_as_admin;
|
||||
|
||||
mod admin;
|
||||
mod downloader;
|
||||
mod file_helpers;
|
||||
mod gamebanana;
|
||||
mod lang;
|
||||
mod proxy;
|
||||
mod structs;
|
||||
mod system_helpers;
|
||||
mod unzip;
|
||||
mod web;
|
||||
mod metadata_patcher;
|
||||
|
||||
static WATCH_GAME_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
|
||||
|
||||
fn try_flush() {
|
||||
std::io::stdout().flush().unwrap_or(())
|
||||
}
|
||||
|
||||
fn has_arg(args: &[String], arg: &str) -> bool {
|
||||
args.contains(&arg.to_string())
|
||||
}
|
||||
|
||||
async fn arg_handler(args: &[String]) {
|
||||
if has_arg(args, "--proxy") {
|
||||
let mut pathbuf = tauri::api::path::data_dir().unwrap();
|
||||
pathbuf.push("cultivation");
|
||||
pathbuf.push("ca");
|
||||
|
||||
connect(8035, pathbuf.to_str().unwrap().to_string()).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
enable_process_watcher,
|
||||
connect,
|
||||
disconnect,
|
||||
req_get,
|
||||
get_bg_file,
|
||||
is_game_running,
|
||||
get_theme_list,
|
||||
system_helpers::run_command,
|
||||
system_helpers::run_program,
|
||||
system_helpers::run_jar,
|
||||
system_helpers::open_in_browser,
|
||||
system_helpers::install_location,
|
||||
system_helpers::is_elevated,
|
||||
proxy::set_proxy_addr,
|
||||
proxy::generate_ca_files,
|
||||
unzip::unzip,
|
||||
file_helpers::rename,
|
||||
file_helpers::dir_exists,
|
||||
file_helpers::dir_is_empty,
|
||||
file_helpers::dir_delete,
|
||||
file_helpers::copy_file,
|
||||
file_helpers::copy_file_with_new_name,
|
||||
file_helpers::delete_file,
|
||||
file_helpers::are_files_identical,
|
||||
file_helpers::read_file,
|
||||
file_helpers::write_file,
|
||||
downloader::download_file,
|
||||
downloader::stop_download,
|
||||
lang::get_lang,
|
||||
lang::get_languages,
|
||||
web::valid_url,
|
||||
web::web_get,
|
||||
metadata_patcher::patch_metadata
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
if !is_elevated() && !has_arg(&args, "--no-admin") {
|
||||
println!("===============================================================================");
|
||||
println!("You running as a non-elevated user. Some stuff will almost definitely not work.");
|
||||
println!("===============================================================================");
|
||||
|
||||
reopen_as_admin();
|
||||
}
|
||||
|
||||
// Setup datadir/cultivation just in case something went funky and it wasn't made
|
||||
if !dir_exists(data_dir().unwrap().join("cultivation").to_str().unwrap()) {
|
||||
fs::create_dir_all(&data_dir().unwrap().join("cultivation")).unwrap();
|
||||
}
|
||||
|
||||
// Always set CWD to the location of the executable.
|
||||
let mut exe_path = std::env::current_exe().unwrap();
|
||||
exe_path.pop();
|
||||
std::env::set_current_dir(&exe_path).unwrap();
|
||||
|
||||
block_on(arg_handler(&args));
|
||||
|
||||
// For disabled GUI
|
||||
ctrlc::set_handler(|| {
|
||||
disconnect();
|
||||
std::process::exit(0);
|
||||
})
|
||||
.unwrap_or(());
|
||||
|
||||
if !has_arg(&args, "--no-gui") {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
enable_process_watcher,
|
||||
connect,
|
||||
disconnect,
|
||||
req_get,
|
||||
is_game_running,
|
||||
get_theme_list,
|
||||
system_helpers::run_command,
|
||||
system_helpers::run_program,
|
||||
system_helpers::run_program_relative,
|
||||
system_helpers::run_jar,
|
||||
system_helpers::open_in_browser,
|
||||
system_helpers::install_location,
|
||||
system_helpers::is_elevated,
|
||||
system_helpers::set_migoto_target,
|
||||
system_helpers::wipe_registry,
|
||||
system_helpers::get_platform,
|
||||
proxy::set_proxy_addr,
|
||||
proxy::generate_ca_files,
|
||||
unzip::unzip,
|
||||
file_helpers::rename,
|
||||
file_helpers::dir_create,
|
||||
file_helpers::dir_exists,
|
||||
file_helpers::dir_is_empty,
|
||||
file_helpers::dir_delete,
|
||||
file_helpers::copy_file,
|
||||
file_helpers::copy_file_with_new_name,
|
||||
file_helpers::delete_file,
|
||||
file_helpers::are_files_identical,
|
||||
file_helpers::read_file,
|
||||
file_helpers::write_file,
|
||||
downloader::download_file,
|
||||
downloader::stop_download,
|
||||
lang::get_lang,
|
||||
lang::get_languages,
|
||||
web::valid_url,
|
||||
web::web_get,
|
||||
gamebanana::get_download_links,
|
||||
gamebanana::list_submissions,
|
||||
gamebanana::list_mods
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
} else {
|
||||
try_flush();
|
||||
println!("Press enter or CTRL-C twice to quit...");
|
||||
std::io::stdin().read_line(&mut String::new()).unwrap();
|
||||
}
|
||||
|
||||
// Always disconnect upon closing the program
|
||||
disconnect();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -72,7 +141,7 @@ fn is_game_running() -> bool {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn enable_process_watcher(window: tauri::Window,process: String) {
|
||||
fn enable_process_watcher(window: tauri::Window, process: String) {
|
||||
*WATCH_GAME_PROCESS.lock().unwrap() = process;
|
||||
|
||||
window.listen("disable_process_watcher", |_e| {
|
||||
@@ -82,10 +151,14 @@ fn enable_process_watcher(window: tauri::Window,process: String) {
|
||||
println!("Starting process watcher...");
|
||||
|
||||
thread::spawn(move || {
|
||||
// Initial sleep for 8 seconds, since running 20 different injectors or whatever can take a while
|
||||
std::thread::sleep(std::time::Duration::from_secs(10));
|
||||
|
||||
let mut system = System::new_all();
|
||||
|
||||
loop {
|
||||
thread::sleep(std::time::Duration::from_secs(5));
|
||||
// Shorten loop timer to avoid user closing Cultivation before unpatching/proxy disconnecting
|
||||
thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
// Refresh system info
|
||||
system.refresh_all();
|
||||
@@ -174,53 +247,3 @@ async fn get_theme_list(data_dir: String) -> Vec<HashMap<String, String>> {
|
||||
|
||||
themes
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
// TODO: Replace with downloading the background file & saving it.
|
||||
async fn get_bg_file(bg_path: String, appdata: String) -> String {
|
||||
let copy_loc = appdata;
|
||||
let query = web::query("https://api.grasscutter.io/cultivation/query").await;
|
||||
let response_data: APIQuery = match serde_json::from_str(&query) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
println!("Failed to parse response: {}", e);
|
||||
return "".to_string();
|
||||
}
|
||||
};
|
||||
|
||||
let file_name = response_data.bg_file.to_string();
|
||||
|
||||
// First we see if the file already exists in our local bg folder.
|
||||
if file_helpers::dir_exists(format!("{}\\bg\\{}", copy_loc, file_name).as_str()) {
|
||||
return format!("{}\\{}", copy_loc, response_data.bg_file.as_str());
|
||||
}
|
||||
|
||||
// Now we check if the bg folder, which is one directory above the game_path, exists.
|
||||
let bg_img_path = format!("{}\\{}", &bg_path, &file_name);
|
||||
|
||||
// If it doesn't, then we do not have backgrounds to grab.
|
||||
if !file_helpers::dir_exists(&bg_path) {
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
// BG folder does exist, lets see if the image exists.
|
||||
if !file_helpers::dir_exists(&bg_img_path) {
|
||||
// Image doesn't exist
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
// The image exists, lets copy it to our local '\bg' folder.
|
||||
let bg_img_path_local = format!("{}\\bg\\{}", copy_loc, file_name.as_str());
|
||||
|
||||
match std::fs::copy(bg_img_path, bg_img_path_local) {
|
||||
Ok(_) => {
|
||||
// Copy was successful, lets return true.
|
||||
format!("{}\\{}", copy_loc, response_data.bg_file)
|
||||
}
|
||||
Err(e) => {
|
||||
// Copy failed, lets return false
|
||||
println!("Failed to copy background image: {}", e);
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
use regex::Regex;
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
|
||||
extern {
|
||||
fn decrypt_global_metadata(data : *mut u8, size : u64);
|
||||
fn encrypt_global_metadata(data : *mut u8, size : u64);
|
||||
}
|
||||
|
||||
fn dll_decrypt_global_metadata(data : *mut u8, size : u64) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
unsafe {
|
||||
decrypt_global_metadata(data, size);
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn dll_encrypt_global_metadata(data : *mut u8, size : u64) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
unsafe {
|
||||
encrypt_global_metadata(data, size);
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn patch_metadata(metadata_folder: &str) -> bool {
|
||||
let metadata_file = &(metadata_folder.to_owned() + "\\global-metadata-unpatched.dat");
|
||||
println!("Patching metadata file: {}", metadata_file);
|
||||
let decrypted = decrypt_metadata(metadata_file);
|
||||
if do_vecs_match(&decrypted, &Vec::new()) {
|
||||
println!("Failed to decrypt metadata file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
let modified = replace_keys(&decrypted);
|
||||
if do_vecs_match(&modified, &Vec::new()) {
|
||||
println!("Failed to replace keys in metadata file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
let encrypted = encrypt_metadata(&modified);
|
||||
if do_vecs_match(&encrypted, &Vec::new()) {
|
||||
println!("Failed to re-encrypt metadata file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
//write encrypted to file
|
||||
let mut file = match OpenOptions::new().create(true).write(true).open(&(metadata_folder.to_owned() + "\\global-metadata-patched.dat")) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
println!("Failed to open global-metadata: {}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
file.write_all(&encrypted).unwrap();
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn decrypt_metadata(file_path: &str) -> Vec<u8> {
|
||||
let mut file = match File::open(file_path) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
println!("Failed to open global-metadata: {}", e);
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let mut data = Vec::new();
|
||||
|
||||
// Read metadata file
|
||||
match file.read_to_end(&mut data) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!("Failed to read global-metadata: {}", e);
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt metadata file
|
||||
match dll_decrypt_global_metadata(data.as_mut_ptr(), data.len().try_into().unwrap()) {
|
||||
Ok(_) => {
|
||||
println!("Successfully decrypted global-metadata");
|
||||
data
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to decrypt global-metadata: {}", e);
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn replace_keys(data: &[u8]) -> Vec<u8> {
|
||||
let mut new_data = String::new();
|
||||
|
||||
unsafe {
|
||||
let data_str = String::from_utf8_unchecked(data.to_vec());
|
||||
|
||||
let re = Regex::new(r"<RSAKeyValue>((.|\n|\r)*?)</RSAKeyValue>").unwrap();
|
||||
let matches = re.find_iter(&data_str);
|
||||
|
||||
// dispatch key is index 3
|
||||
// password key is index 2
|
||||
|
||||
for (i, rmatch) in matches.enumerate() {
|
||||
let key = rmatch.as_str();
|
||||
|
||||
if i == 2 {
|
||||
println!("Replacing password key");
|
||||
new_data = replace_rsa_key(&data_str, key, "passwordKey.txt");
|
||||
} else if i == 3 {
|
||||
println!("Replacing dispatch key");
|
||||
new_data = replace_rsa_key(&new_data, key, "dispatchKey.txt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new_data.as_bytes().to_vec();
|
||||
}
|
||||
|
||||
fn replace_rsa_key(old_data: &str, to_replace: &str, file_name: &str) -> String {
|
||||
// Read dispatch key file
|
||||
unsafe {
|
||||
let mut new_key_file = match File::open(&("keys/".to_owned() + file_name)) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
println!("Failed to open keys/{}: {}", file_name, e);
|
||||
return String::new();
|
||||
}
|
||||
};
|
||||
let mut key_data = Vec::new();
|
||||
new_key_file.read_to_end(&mut key_data).unwrap();
|
||||
let new_key = String::from_utf8_unchecked(key_data.to_vec());
|
||||
|
||||
// Replace old key with new key
|
||||
old_data.replace(to_replace, &new_key)
|
||||
}
|
||||
}
|
||||
|
||||
fn encrypt_metadata(old_data: &[u8]) -> Vec<u8> {
|
||||
let mut data = old_data.to_vec();
|
||||
match dll_encrypt_global_metadata(data.as_mut_ptr(), data.len().try_into().unwrap()) {
|
||||
Ok(_) => {
|
||||
println!("Successfully encrypted global-metadata");
|
||||
data
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to encrypt global-metadata: {}", e);
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn do_vecs_match<T: PartialEq>(a: &Vec<T>, b: &Vec<T>) -> bool {
|
||||
let matching = a.iter().zip(b.iter()).filter(|&(a, b)| a == b).count();
|
||||
matching == a.len() && matching == b.len()
|
||||
}
|
||||
@@ -3,13 +3,16 @@
|
||||
* https://github.com/omjadas/hudsucker/blob/main/examples/log.rs
|
||||
*/
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::system_helpers::run_command;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{str::FromStr, sync::Mutex};
|
||||
use std::{path::PathBuf, str::FromStr, sync::Mutex};
|
||||
|
||||
use hudsucker::{
|
||||
async_trait::async_trait,
|
||||
certificate_authority::RcgenAuthority,
|
||||
hyper::{Body, Request, Response},
|
||||
hyper::{Body, Request, Response, StatusCode},
|
||||
*,
|
||||
};
|
||||
use rcgen::*;
|
||||
@@ -19,7 +22,7 @@ use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
|
||||
use rustls_pemfile as pemfile;
|
||||
use tauri::http::Uri;
|
||||
use tauri::{api::path::data_dir, http::Uri};
|
||||
|
||||
#[cfg(windows)]
|
||||
use registry::{Data, Hive, Security};
|
||||
@@ -45,21 +48,33 @@ pub fn set_proxy_addr(addr: String) {
|
||||
impl HttpHandler for ProxyHandler {
|
||||
async fn handle_request(
|
||||
&mut self,
|
||||
_context: &HttpContext,
|
||||
mut request: Request<Body>,
|
||||
_ctx: &HttpContext,
|
||||
mut req: Request<Body>,
|
||||
) -> RequestOrResponse {
|
||||
let uri = request.uri().to_string();
|
||||
let uri_path = request.uri().path();
|
||||
let uri = req.uri().to_string();
|
||||
|
||||
if uri.contains("hoyoverse.com") || uri.contains("mihoyo.com") || uri.contains("yuanshen.com") {
|
||||
// Create new URI.
|
||||
let new_uri =
|
||||
Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path).as_str()).unwrap();
|
||||
// Set request URI to the new one.
|
||||
*request.uri_mut() = new_uri;
|
||||
// Handle CONNECTs
|
||||
if req.method().as_str() == "CONNECT" {
|
||||
let builder = Response::builder()
|
||||
.header("DecryptEndpoint", "Created")
|
||||
.status(StatusCode::OK);
|
||||
let res = builder.body(()).unwrap();
|
||||
|
||||
// Respond to CONNECT
|
||||
*res.body()
|
||||
} else {
|
||||
let uri_path_and_query = req.uri().path_and_query().unwrap().as_str();
|
||||
// Create new URI.
|
||||
let new_uri =
|
||||
Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path_and_query).as_str())
|
||||
.unwrap();
|
||||
// Set request URI to the new one.
|
||||
*req.uri_mut() = new_uri;
|
||||
}
|
||||
}
|
||||
|
||||
RequestOrResponse::Request(request)
|
||||
req.into()
|
||||
}
|
||||
|
||||
async fn handle_response(
|
||||
@@ -69,17 +84,42 @@ impl HttpHandler for ProxyHandler {
|
||||
) -> Response<Body> {
|
||||
response
|
||||
}
|
||||
|
||||
async fn should_intercept(&mut self, _ctx: &HttpContext, _req: &Request<Body>) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an HTTP(S) proxy server.
|
||||
*/
|
||||
pub async fn create_proxy(proxy_port: u16, certificate_path: String) {
|
||||
let cert_path = PathBuf::from(certificate_path);
|
||||
let pk_path = cert_path.join("private.key");
|
||||
let ca_path = cert_path.join("cert.crt");
|
||||
|
||||
// Get the certificate and private key.
|
||||
let mut private_key_bytes: &[u8] =
|
||||
&fs::read(format!("{}\\private.key", certificate_path)).expect("Could not read private key");
|
||||
let mut ca_cert_bytes: &[u8] =
|
||||
&fs::read(format!("{}\\cert.crt", certificate_path)).expect("Could not read certificate");
|
||||
let mut private_key_bytes: &[u8] = &match fs::read(&pk_path) {
|
||||
// Try regenerating the CA stuff and read it again. If that doesn't work, quit.
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
println!("Encountered {}. Regenerating CA cert and retrying...", e);
|
||||
generate_ca_files(&data_dir().unwrap().join("cultivation"));
|
||||
|
||||
fs::read(&pk_path).expect("Could not read private key")
|
||||
}
|
||||
};
|
||||
|
||||
let mut ca_cert_bytes: &[u8] = &match fs::read(&ca_path) {
|
||||
// Try regenerating the CA stuff and read it again. If that doesn't work, quit.
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
println!("Encountered {}. Regenerating CA cert and retrying...", e);
|
||||
generate_ca_files(&data_dir().unwrap().join("cultivation"));
|
||||
|
||||
fs::read(&ca_path).expect("Could not read certificate")
|
||||
}
|
||||
};
|
||||
|
||||
// Parse the private key and certificate.
|
||||
let private_key = rustls::PrivateKey(
|
||||
@@ -138,9 +178,29 @@ pub fn connect_to_proxy(proxy_port: u16) {
|
||||
println!("Connected to the proxy.");
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[cfg(unix)]
|
||||
pub fn connect_to_proxy(proxy_port: u16) {
|
||||
// Edit /etc/environment to set $http_proxy and $https_proxy
|
||||
let mut env_file = match fs::read_to_string("/etc/environment") {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
println!("Error opening /etc/environment: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Append the proxy configuration.
|
||||
// We will not remove the current proxy config if it exists, so we can just remove these last lines when we disconnect
|
||||
env_file += format!("\nhttps_proxy=127.0.0.1:{}", proxy_port).as_str();
|
||||
env_file += format!("\nhttp_proxy=127.0.0.1:{}", proxy_port).as_str();
|
||||
|
||||
// Save
|
||||
fs::write("/etc/environment", env_file).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(target_od = "macos")]
|
||||
pub fn connect_to_proxy(_proxy_port: u16) {
|
||||
println!("Connecting to the proxy is not implemented on this platform.");
|
||||
println!("No Mac support yet. Someone mail me a Macbook and I will do it B)")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,7 +222,26 @@ pub fn disconnect_from_proxy() {
|
||||
println!("Disconnected from proxy.");
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn disconnect_from_proxy() {
|
||||
println!("Re-writing environment variables");
|
||||
|
||||
let regexp = regex::Regex::new(
|
||||
// This has to be specific as possible or we risk fuckin up their environment LOL
|
||||
r"(https|http)_proxy=.*127.0.0.1:.*",
|
||||
)
|
||||
.unwrap();
|
||||
let environment = &fs::read_to_string("/etc/environment").expect("Failed to open environment");
|
||||
|
||||
let new_environment = regexp.replace_all(environment, "").to_string();
|
||||
|
||||
// Write new environment
|
||||
fs::write("/etc/environment", new_environment.trim_end()).expect(
|
||||
"Could not write environment, remove proxy declarations manually if they are still set",
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn disconnect_from_proxy() {}
|
||||
|
||||
/*
|
||||
@@ -242,6 +321,7 @@ pub fn install_ca_files(cert_path: &Path) {
|
||||
crate::system_helpers::run_command(
|
||||
"certutil",
|
||||
vec!["-user", "-addstore", "Root", cert_path.to_str().unwrap()],
|
||||
None,
|
||||
);
|
||||
println!("Installed certificate.");
|
||||
}
|
||||
@@ -259,11 +339,27 @@ pub fn install_ca_files(cert_path: &Path) {
|
||||
"/Library/Keychains/System.keychain",
|
||||
cert_path.to_str().unwrap(),
|
||||
],
|
||||
None,
|
||||
);
|
||||
println!("Installed certificate.");
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows, target_os = "macos")))]
|
||||
// If this is borked on non-debian platforms, so be it
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn install_ca_files(cert_path: &Path) {
|
||||
let usr_certs = PathBuf::from("/usr/local/share/ca-certificates");
|
||||
let usr_cert_path = usr_certs.join("cultivation.crt");
|
||||
|
||||
// Create dir if it doesn't exist
|
||||
fs::create_dir_all(&usr_certs).expect("Unable to create local certificate directory");
|
||||
|
||||
fs::copy(cert_path, &usr_cert_path).expect("Unable to copy cert to local certificate directory");
|
||||
run_command("update-ca-certificates", vec![], None);
|
||||
|
||||
println!("Installed certificate.");
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
|
||||
pub fn install_ca_files(_cert_path: &Path) {
|
||||
println!("Certificate installation is not supported on this platform.");
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct APIQuery {
|
||||
pub bg_file: String,
|
||||
}
|
||||
@@ -1,17 +1,59 @@
|
||||
use duct::cmd;
|
||||
use ini::Ini;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(windows)]
|
||||
use registry::{Data, Hive, Security};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn run_program(path: String) {
|
||||
// Open in new thread to prevent blocking.
|
||||
std::thread::spawn(move || {
|
||||
// Without unwrap_or, this can crash when UAC prompt is denied
|
||||
open::that(&path).unwrap_or(());
|
||||
});
|
||||
pub fn run_program(path: String, args: Option<String>) {
|
||||
// Without unwrap_or, this can crash when UAC prompt is denied
|
||||
open::that(format!("{} {}", &path, &args.unwrap_or_else(|| "".into()))).unwrap_or(());
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn run_command(program: &str, args: Vec<&str>) {
|
||||
cmd(program, args).run().expect("Failed to run command");
|
||||
pub fn run_program_relative(path: String, args: Option<String>) {
|
||||
// Save the current working directory
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
|
||||
// Set the new working directory to the path before the executable
|
||||
let mut path_buf = std::path::PathBuf::from(&path);
|
||||
path_buf.pop();
|
||||
|
||||
// Set new working directory
|
||||
std::env::set_current_dir(&path_buf).unwrap();
|
||||
|
||||
// Without unwrap_or, this can crash when UAC prompt is denied
|
||||
open::that(format!("{} {}", &path, args.unwrap_or_else(|| "".into()))).unwrap_or(());
|
||||
|
||||
// Restore the original working directory
|
||||
std::env::set_current_dir(&cwd).unwrap();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn run_command(program: &str, args: Vec<&str>, relative: Option<bool>) {
|
||||
let prog = program.to_string();
|
||||
let args = args.iter().map(|s| s.to_string()).collect::<Vec<String>>();
|
||||
|
||||
// Commands should not block (this is for the reshade injector mostly)
|
||||
std::thread::spawn(move || {
|
||||
// Save the current working directory
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
|
||||
if relative.unwrap_or(false) {
|
||||
// Set the new working directory to the path before the executable
|
||||
let mut path_buf = std::path::PathBuf::from(&prog);
|
||||
path_buf.pop();
|
||||
|
||||
// Set new working directory
|
||||
std::env::set_current_dir(&path_buf).unwrap();
|
||||
}
|
||||
|
||||
cmd(prog, args).run().unwrap();
|
||||
|
||||
// Restore the original working directory
|
||||
std::env::set_current_dir(&cwd).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -51,6 +93,69 @@ pub fn install_location() -> String {
|
||||
return exe_path.to_str().unwrap().to_string();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_migoto_target(_path: String, migoto_path: String) -> bool {
|
||||
let mut migoto_pathbuf = PathBuf::from(migoto_path);
|
||||
|
||||
migoto_pathbuf.pop();
|
||||
migoto_pathbuf.push("d3dx.ini");
|
||||
|
||||
let mut conf = match Ini::load_from_file(&migoto_pathbuf) {
|
||||
Ok(c) => {
|
||||
println!("Loaded migoto ini");
|
||||
c
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error loading migoto config: {}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Set options
|
||||
conf
|
||||
.with_section(Some("Loader"))
|
||||
.set("target", "GenshinImpact.exe");
|
||||
|
||||
// Write file
|
||||
match conf.write_to_file(&migoto_pathbuf) {
|
||||
Ok(_) => {
|
||||
println!("Wrote config!");
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error writing config: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tauri::command]
|
||||
pub fn wipe_registry(exec_name: String) {
|
||||
// Fetch the 'Internet Settings' registry key.
|
||||
let settings =
|
||||
match Hive::CurrentUser.open(format!("Software\\miHoYo\\{}", exec_name), Security::Write) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
println!("Error getting registry setting: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Wipe login cache
|
||||
match settings.set_value(
|
||||
"MIHOYOSDK_ADL_PROD_OVERSEA_h1158948810",
|
||||
&Data::String("".parse().unwrap()),
|
||||
) {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("Error wiping registry: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tauri::command]
|
||||
pub fn wipe_registry(_exec_name: String) {}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tauri::command]
|
||||
pub fn is_elevated() -> bool {
|
||||
@@ -62,3 +167,8 @@ pub fn is_elevated() -> bool {
|
||||
pub fn is_elevated() -> bool {
|
||||
sudo::check() == sudo::RunningAs::Root
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_platform() -> &'static str {
|
||||
std::env::consts::OS
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
use std::fs::File;
|
||||
use std::fs::{read_dir, File};
|
||||
use std::path;
|
||||
use std::thread;
|
||||
use unrar::archive::Archive;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn unzip(window: tauri::Window, zipfile: String, destpath: String) {
|
||||
pub fn unzip(
|
||||
window: tauri::Window,
|
||||
zipfile: String,
|
||||
destpath: String,
|
||||
top_level: Option<bool>,
|
||||
folder_if_loose: Option<bool>,
|
||||
) {
|
||||
// Read file TODO: replace test file
|
||||
let f = match File::open(&zipfile) {
|
||||
Ok(f) => f,
|
||||
@@ -15,53 +22,150 @@ pub fn unzip(window: tauri::Window, zipfile: String, destpath: String) {
|
||||
|
||||
let write_path = path::PathBuf::from(&destpath);
|
||||
|
||||
// Get a list of all current directories
|
||||
let mut dirs = vec![];
|
||||
for entry in read_dir(&write_path).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let entry_path = entry.path();
|
||||
if entry_path.is_dir() {
|
||||
dirs.push(entry_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Run extraction in seperate thread
|
||||
thread::spawn(move || {
|
||||
let full_path = write_path;
|
||||
let mut full_path = write_path.clone();
|
||||
|
||||
window.emit("extract_start", &zipfile).unwrap();
|
||||
|
||||
match zip_extract::extract(&f, &full_path, true) {
|
||||
Ok(_) => {
|
||||
println!(
|
||||
"Extracted zip file to: {}",
|
||||
full_path.to_str().unwrap_or("Error")
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to extract zip file: {}", e);
|
||||
let mut res_hash = std::collections::HashMap::new();
|
||||
if folder_if_loose.unwrap_or(false) {
|
||||
// Create a new folder with the same name as the zip file
|
||||
let mut file_name = path::Path::new(&zipfile)
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
|
||||
res_hash.insert("error".to_string(), e.to_string());
|
||||
// remove ".zip" from the end of the file name
|
||||
file_name = &file_name[..file_name.len() - 4];
|
||||
|
||||
res_hash.insert("path".to_string(), zipfile.to_string());
|
||||
let new_path = full_path.join(file_name);
|
||||
match std::fs::create_dir_all(&new_path) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("Failed to create directory: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
window.emit("download_error", &res_hash).unwrap();
|
||||
}
|
||||
};
|
||||
full_path = new_path;
|
||||
}
|
||||
|
||||
// Get the name of the inenr file in the zip file
|
||||
let mut zip = zip::ZipArchive::new(&f).unwrap();
|
||||
let file = zip.by_index(0).unwrap();
|
||||
let name = file.name();
|
||||
println!("Is rar file? {}", zipfile.ends_with(".rar"));
|
||||
|
||||
let name;
|
||||
let success;
|
||||
|
||||
// If file ends in zip, OR is unknown, extract as zip, otherwise extract as rar
|
||||
if zipfile.ends_with(".rar") {
|
||||
success = extract_rar(&zipfile, &f, &full_path, top_level.unwrap_or(true));
|
||||
|
||||
let archive = Archive::new(zipfile.clone());
|
||||
name = archive.list().unwrap().next().unwrap().unwrap().filename;
|
||||
} else {
|
||||
success = extract_zip(&zipfile, &f, &full_path, top_level.unwrap_or(true));
|
||||
|
||||
// Get the name of the inenr file in the zip file
|
||||
let mut zip = zip::ZipArchive::new(&f).unwrap();
|
||||
let file = zip.by_index(0).unwrap();
|
||||
name = file.name().to_string();
|
||||
}
|
||||
|
||||
if !success {
|
||||
let mut res_hash = std::collections::HashMap::new();
|
||||
|
||||
res_hash.insert("path".to_string(), zipfile.to_string());
|
||||
|
||||
window.emit("download_error", &res_hash).unwrap();
|
||||
}
|
||||
|
||||
// If the contents is a jar file, emit that we have extracted a new jar file
|
||||
if name.ends_with(".jar") {
|
||||
window
|
||||
.emit("jar_extracted", destpath.to_string() + name)
|
||||
.emit("jar_extracted", destpath.to_string() + name.as_str())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// If downloading full build, emit that the jar was extracted with it
|
||||
if zipfile.ends_with("Culti3.4.zip") {
|
||||
window
|
||||
.emit("jar_extracted", destpath.to_string() + "grasscutter.jar")
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Delete zip file
|
||||
match std::fs::remove_file(&zipfile) {
|
||||
Ok(_) => {
|
||||
// Get any new directory that could have been created
|
||||
let mut new_dir: String = String::new();
|
||||
for entry in read_dir(&write_path).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let entry_path = entry.path();
|
||||
if entry_path.is_dir() && !dirs.contains(&entry_path) {
|
||||
new_dir = entry_path.to_str().unwrap().to_string();
|
||||
}
|
||||
}
|
||||
|
||||
let mut res_hash = std::collections::HashMap::new();
|
||||
res_hash.insert("file", zipfile.to_string());
|
||||
res_hash.insert("new_folder", new_dir);
|
||||
|
||||
window.emit("extract_end", &res_hash).unwrap();
|
||||
println!("Deleted zip file: {}", zipfile);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to delete zip file: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
window.emit("extract_end", &zipfile).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn extract_rar(rarfile: &str, _f: &File, full_path: &path::Path, _top_level: bool) -> bool {
|
||||
let archive = Archive::new(rarfile.to_string());
|
||||
|
||||
let mut open_archive = archive
|
||||
.extract_to(full_path.to_str().unwrap().to_string())
|
||||
.unwrap();
|
||||
|
||||
match open_archive.process() {
|
||||
Ok(_) => {
|
||||
println!(
|
||||
"Extracted rar file to: {}",
|
||||
full_path.to_str().unwrap_or("Error")
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to extract rar file: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_zip(_zipfile: &str, f: &File, full_path: &path::Path, top_level: bool) -> bool {
|
||||
match zip_extract::extract(f, full_path, top_level) {
|
||||
Ok(_) => {
|
||||
println!(
|
||||
"Extracted zip file to: {}",
|
||||
full_path.to_str().unwrap_or("Error")
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to extract zip file: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use reqwest::header::USER_AGENT;
|
||||
use reqwest::header::{CONTENT_TYPE, USER_AGENT};
|
||||
|
||||
pub(crate) async fn query(site: &str) -> String {
|
||||
let client = reqwest::Client::new();
|
||||
@@ -6,10 +6,16 @@ pub(crate) async fn query(site: &str) -> String {
|
||||
let response = client
|
||||
.get(site)
|
||||
.header(USER_AGENT, "cultivation")
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
response.text().await.unwrap()
|
||||
.ok();
|
||||
|
||||
if response.is_some() {
|
||||
return response.unwrap().text().await.unwrap();
|
||||
} else {
|
||||
false.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -22,9 +28,13 @@ pub(crate) async fn valid_url(url: String) -> bool {
|
||||
.header(USER_AGENT, "cultivation")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
.ok();
|
||||
|
||||
response.status().as_str() == "200"
|
||||
if response.is_some() {
|
||||
return response.unwrap().status().as_str() == "200";
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -7,25 +7,17 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "Cultivation",
|
||||
"version": "1.0.2"
|
||||
"version": "1.0.10"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"fs": {
|
||||
"scope": [
|
||||
"$DATA",
|
||||
"$DATA/cultivation",
|
||||
"$DATA/cultivation/*"
|
||||
]
|
||||
"scope": ["$DATA", "$DATA/cultivation", "$DATA/cultivation/*"]
|
||||
},
|
||||
"protocol": {
|
||||
"all": true,
|
||||
"asset": true,
|
||||
"assetScope": [
|
||||
"$DATA",
|
||||
"$DATA/cultivation",
|
||||
"$DATA/cultivation/*"
|
||||
]
|
||||
"assetScope": ["$DATA", "$DATA/cultivation", "$DATA/cultivation/*"]
|
||||
},
|
||||
"all": true
|
||||
},
|
||||
@@ -37,13 +29,7 @@
|
||||
"depends": []
|
||||
},
|
||||
"externalBin": [],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
|
||||
"identifier": "io.grasscutter",
|
||||
"shortDescription": "A game launcher.",
|
||||
"longDescription": "A launcher for a certain anime game that proxies all related game traffic to external servers.",
|
||||
@@ -54,11 +40,7 @@
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null
|
||||
},
|
||||
"resources": [
|
||||
"lang/*.json",
|
||||
"keys/*",
|
||||
"./mhycrypto.dll"
|
||||
],
|
||||
"resources": ["lang/*.json", "keys/*", "patch/*"],
|
||||
"targets": "all",
|
||||
"windows": {
|
||||
"allowDowngrades": false,
|
||||
@@ -93,4 +75,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'MiHoYo_SDK_Web', 'Helvetica Neue', BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
|
||||
sans-serif;
|
||||
font-family: 'MiHoYo_SDK_Web', 'Helvetica Neue', BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
|
||||
'Cantarell', 'Fira Sans', 'Droid Sans', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
}
|
||||
|
||||
@@ -7,23 +7,15 @@ import Debug from './ui/Debug'
|
||||
|
||||
import { getConfigOption } from './utils/configuration'
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
)
|
||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
||||
|
||||
let isDebug = false;
|
||||
let isDebug = false
|
||||
|
||||
(async() => {
|
||||
;async () => {
|
||||
isDebug = await getConfigOption('debug_enabled')
|
||||
})
|
||||
}
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
{
|
||||
isDebug ? <Debug /> : <App />
|
||||
}
|
||||
</React.StrictMode>
|
||||
)
|
||||
root.render(<React.StrictMode>{isDebug ? <Debug /> : <App />}</React.StrictMode>)
|
||||
|
||||
import reportWebVitals from './utils/reportWebVitals'
|
||||
isDebug && reportWebVitals(console.log)
|
||||
isDebug && reportWebVitals(console.log)
|
||||
|
||||
BIN
src/resources/icons/FALLBACK_BG.jpg
Normal file
|
After Width: | Height: | Size: 642 KiB |
10
src/resources/icons/back.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
||||
<desc>Created with Fabric.js 1.7.22</desc>
|
||||
<defs>
|
||||
</defs>
|
||||
<g transform="translate(128 128) scale(0.72 0.72)" style="">
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(-175.05 -175.05) scale(3.89 3.89)" >
|
||||
<path d="M 45 0 c 24.813 0 45 20.187 45 45 c 0 24.813 -20.187 45 -45 45 C 20.186 90 0 69.813 0 45 C 0 20.187 20.186 0 45 0 z M 51.263 73.4 l 8.6 -8.6 L 40.064 45 l 19.799 -19.799 l -8.6 -8.6 L 22.864 45 L 51.263 73.4 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1002 B |
11
src/resources/icons/eye.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
||||
<desc>Created with Fabric.js 1.7.22</desc>
|
||||
<defs>
|
||||
</defs>
|
||||
<g transform="translate(128 128) scale(0.72 0.72)" style="">
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(-175.05 -175.05) scale(3.89 3.89)" >
|
||||
<path d="M 89.307 43.082 C 74.775 25.601 59.868 16.737 45 16.737 c -14.869 0 -29.775 8.864 -44.307 26.345 c -0.924 1.112 -0.924 2.724 0 3.836 C 15.225 64.399 30.131 73.264 45 73.264 c 14.868 0 29.775 -8.864 44.307 -26.346 C 90.231 45.806 90.231 44.194 89.307 43.082 z M 45 62 c -9.374 0 -17 -7.626 -17 -17 s 7.626 -17 17 -17 s 17 7.626 17 17 S 54.374 62 45 62 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||
<circle cx="45" cy="45" r="9" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
11
src/resources/icons/like.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
||||
<desc>Created with Fabric.js 1.7.22</desc>
|
||||
<defs>
|
||||
</defs>
|
||||
<g transform="translate(128 128) scale(0.72 0.72)" style="">
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(-175.05 -175.05) scale(3.89 3.89)" >
|
||||
<path d="M 0 87.201 h 18.478 c 1.44 0 2.607 -1.167 2.607 -2.607 V 35.343 c 0 -1.44 -1.167 -2.607 -2.607 -2.607 H 0 L 0 87.201 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||
<path d="M 83.186 46.32 c 3.763 0 6.814 -3.051 6.814 -6.814 c 0 -3.763 -3.051 -6.814 -6.814 -6.814 H 61.591 c 3.758 -6.768 3.872 -27.328 -5.046 -29.797 c -1.689 -0.468 -3.365 0.823 -3.554 2.565 c -1.568 14.428 -10.395 32.362 -19.37 32.881 h -6.991 v 43.003 h 3.627 c 1.952 0 3.817 0.666 5.444 1.743 c 4.063 2.691 10.906 4.265 17.465 4.101 h 3.172 v 0.012 h 21.788 c 3.763 0 6.814 -3.051 6.814 -6.814 c 0 -3.763 -3.051 -6.814 -6.814 -6.814 h 3.037 c 3.763 0 6.814 -3.051 6.814 -6.814 c 0 -3.763 -3.051 -6.814 -6.814 -6.814 h 2.025 c 3.763 0 6.814 -3.051 6.814 -6.814 S 86.949 46.32 83.186 46.32 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
11
src/resources/icons/plus.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
||||
<desc>Created with Fabric.js 1.7.22</desc>
|
||||
<defs>
|
||||
</defs>
|
||||
<g transform="translate(128 128) scale(0.72 0.72)" style="">
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(-175.05 -175.05) scale(3.89 3.89)" >
|
||||
<path d="M 58.921 90 H 31.079 c -1.155 0 -2.092 -0.936 -2.092 -2.092 V 2.092 C 28.988 0.936 29.924 0 31.079 0 h 27.841 c 1.155 0 2.092 0.936 2.092 2.092 v 85.817 C 61.012 89.064 60.076 90 58.921 90 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||
<path d="M 90 31.079 v 27.841 c 0 1.155 -0.936 2.092 -2.092 2.092 H 2.092 C 0.936 61.012 0 60.076 0 58.921 V 31.079 c 0 -1.155 0.936 -2.092 2.092 -2.092 h 85.817 C 89.064 28.988 90 29.924 90 31.079 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
10
src/resources/icons/wrench.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
||||
<desc>Created with Fabric.js 1.7.22</desc>
|
||||
<defs>
|
||||
</defs>
|
||||
<g transform="translate(128 128) scale(0.72 0.72)" style="">
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(-175.05 -175.05) scale(3.89 3.89)" >
|
||||
<path d="M 87.12 73.212 L 48.774 34.866 c 3.723 -9.153 1.873 -20.042 -5.553 -27.468 c -7.008 -7.008 -17.094 -9.025 -25.9 -6.104 L 28.96 12.934 c 4.387 4.387 4.833 11.594 0.579 16.112 c -4.402 4.674 -11.761 4.757 -16.268 0.25 L 1.295 17.32 c -2.922 8.807 -0.904 18.892 6.104 25.9 c 7.426 7.426 18.315 9.276 27.468 5.553 L 73.212 87.12 c 3.84 3.84 10.067 3.84 13.908 0 l 0 0 C 90.96 83.279 90.96 77.052 87.12 73.212 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -22,7 +22,8 @@ select:focus {
|
||||
border-bottom-color: #ffd326;
|
||||
}
|
||||
|
||||
#root, .App {
|
||||
#root,
|
||||
.App {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -34,12 +35,29 @@ select:focus {
|
||||
.TopButton {
|
||||
height: 60%;
|
||||
margin: 0px 10px;
|
||||
|
||||
color: #c5c5c5;
|
||||
transition: color 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.TopButton span {
|
||||
display: inline-block;
|
||||
line-height: normal;
|
||||
border-bottom: 1px solid #c5c5c5;
|
||||
}
|
||||
|
||||
.TopButton img {
|
||||
filter: invert(95%) sepia(0%) saturate(18%) hue-rotate(153deg) brightness(88%) contrast(81%);
|
||||
|
||||
transition: filter 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.TopButton:hover {
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.TopButton:hover img {
|
||||
filter: invert(100%) sepia(0%) saturate(18%) hue-rotate(153deg) brightness(100%) contrast(100%);
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -64,8 +82,8 @@ select:focus {
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
width: 0;
|
||||
height: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 50px solid transparent;
|
||||
border-right: 50px solid transparent;
|
||||
border-top: 50px solid transparent;
|
||||
@@ -82,28 +100,28 @@ select:focus {
|
||||
|
||||
.BottomSection {
|
||||
position: absolute;
|
||||
bottom: 0%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0%);
|
||||
bottom: 0%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0%);
|
||||
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: inset 0px 5px 12px -3px rgb(50 50 50 / 75%);
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media(max-height: 580px) {
|
||||
.BottomSection {
|
||||
height: 150px;
|
||||
}
|
||||
@media (max-height: 580px) {
|
||||
.BottomSection {
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-height: 500px) {
|
||||
.BottomSection {
|
||||
height: 140px;
|
||||
}
|
||||
}
|
||||
@media (max-height: 500px) {
|
||||
.BottomSection {
|
||||
height: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
242
src/ui/App.tsx
@@ -1,99 +1,35 @@
|
||||
import React from 'react'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import './App.css'
|
||||
|
||||
import DownloadHandler from '../utils/download'
|
||||
|
||||
// Major Components
|
||||
import TopBar from './components/TopBar'
|
||||
import ServerLaunchSection from './components/ServerLaunchSection'
|
||||
import MainProgressBar from './components/common/MainProgressBar'
|
||||
import Options from './components/menu/Options'
|
||||
import MiniDialog from './components/MiniDialog'
|
||||
import DownloadList from './components/common/DownloadList'
|
||||
import Downloads from './components/menu/Downloads'
|
||||
import NewsSection from './components/news/NewsSection'
|
||||
import Game from './components/menu/Game'
|
||||
|
||||
import RightBar from './components/RightBar'
|
||||
import { getConfigOption, setConfigOption } from '../utils/configuration'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { appWindow } from '@tauri-apps/api/window'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { getConfigOption } from '../utils/configuration'
|
||||
import { getTheme, loadTheme } from '../utils/themes'
|
||||
import { unpatchGame } from '../utils/metadata'
|
||||
import { convertFileSrc, invoke } from '@tauri-apps/api/tauri'
|
||||
import { Main } from './Main'
|
||||
import { Mods } from './Mods'
|
||||
|
||||
interface IProps {
|
||||
[key: string]: never;
|
||||
}
|
||||
// From https://www.pixiv.net/en/artworks/93995273
|
||||
import FALLBACK_BG from '../resources/icons/FALLBACK_BG.jpg'
|
||||
|
||||
interface IState {
|
||||
isDownloading: boolean;
|
||||
optionsOpen: boolean;
|
||||
miniDownloadsOpen: boolean;
|
||||
downloadsOpen: boolean;
|
||||
gameDownloadsOpen: boolean;
|
||||
bgFile: string;
|
||||
page: string
|
||||
bgFile: string
|
||||
}
|
||||
|
||||
const downloadHandler = new DownloadHandler()
|
||||
const DEFAULT_BG = 'https://api.grasscutter.io/cultivation/bgfile'
|
||||
|
||||
const downloadHandler = new DownloadHandler()
|
||||
|
||||
class App extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
class App extends React.Component<Readonly<unknown>, IState> {
|
||||
constructor(props: Readonly<unknown>) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
isDownloading: false,
|
||||
optionsOpen: false,
|
||||
miniDownloadsOpen: false,
|
||||
downloadsOpen: false,
|
||||
gameDownloadsOpen: false,
|
||||
page: 'main',
|
||||
bgFile: DEFAULT_BG,
|
||||
}
|
||||
|
||||
listen('lang_error', (payload) => {
|
||||
console.log(payload)
|
||||
})
|
||||
|
||||
listen('jar_extracted', ({ payload }: { payload: string}) => {
|
||||
setConfigOption('grasscutter_path', payload)
|
||||
})
|
||||
|
||||
// Emitted for metadata replacing-purposes
|
||||
listen('game_closed', async () => {
|
||||
const unpatched = await unpatchGame()
|
||||
|
||||
console.log(`unpatched game? ${unpatched}`)
|
||||
|
||||
if (!unpatched) {
|
||||
alert(`Could not unpatch game! (You should be able to find your metadata backup in ${await dataDir()}\\cultivation\\)`)
|
||||
}
|
||||
})
|
||||
|
||||
let min = false
|
||||
|
||||
// periodically check if we need to min/max based on whether the game is open
|
||||
setInterval(async () => {
|
||||
const gameOpen = await invoke('is_game_running')
|
||||
|
||||
if (gameOpen && !min) {
|
||||
appWindow.minimize()
|
||||
min = true
|
||||
} else if (!gameOpen && min) {
|
||||
appWindow.unminimize()
|
||||
min = false
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const cert_generated = await getConfigOption('cert_generated')
|
||||
const game_exe = await getConfigOption('game_install_path')
|
||||
const game_path = game_exe?.substring(0, game_exe.replace(/\\/g, '/').lastIndexOf('/')) || ''
|
||||
const root_path = game_path?.substring(0, game_path.replace(/\\/g, '/').lastIndexOf('/')) || ''
|
||||
|
||||
// Load a theme if it exists
|
||||
const theme = await getConfigOption('theme')
|
||||
if (theme && theme !== 'default') {
|
||||
@@ -104,135 +40,75 @@ class App extends React.Component<IProps, IState> {
|
||||
// Get custom bg AFTER theme is loaded !! important !!
|
||||
const custom_bg = await getConfigOption('customBackground')
|
||||
|
||||
if(!custom_bg || !/png|jpg|jpeg$/.test(custom_bg)) {
|
||||
if(game_path) {
|
||||
// Get the bg by invoking, then set the background to that bg.
|
||||
const bgLoc: string = await invoke('get_bg_file', {
|
||||
bgPath: root_path,
|
||||
appdata: await dataDir()
|
||||
})
|
||||
|
||||
bgLoc && this.setState({
|
||||
bgFile: bgLoc
|
||||
}, this.forceUpdate)
|
||||
}
|
||||
} else {
|
||||
if (custom_bg) {
|
||||
const isUrl = /^http(s)?:\/\//gm.test(custom_bg)
|
||||
|
||||
if (!isUrl) {
|
||||
const isValid = await invoke('dir_exists', {
|
||||
path: custom_bg
|
||||
path: custom_bg,
|
||||
})
|
||||
|
||||
this.setState({
|
||||
bgFile: isValid ? convertFileSrc(custom_bg) : DEFAULT_BG
|
||||
}, this.forceUpdate)
|
||||
this.setState(
|
||||
{
|
||||
bgFile: isValid ? convertFileSrc(custom_bg) : DEFAULT_BG,
|
||||
},
|
||||
this.forceUpdate
|
||||
)
|
||||
} else {
|
||||
// Check if URL returns a valid image.
|
||||
const isValid = await invoke('valid_url', {
|
||||
url: custom_bg
|
||||
url: custom_bg,
|
||||
})
|
||||
|
||||
this.setState({
|
||||
bgFile: isValid ? custom_bg : DEFAULT_BG
|
||||
}, this.forceUpdate)
|
||||
this.setState(
|
||||
{
|
||||
bgFile: isValid ? custom_bg : DEFAULT_BG,
|
||||
},
|
||||
this.forceUpdate
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!cert_generated) {
|
||||
// Generate the certificate
|
||||
await invoke('generate_ca_files', {
|
||||
path: await dataDir() + 'cultivation'
|
||||
} else {
|
||||
// Check if api bg is accessible
|
||||
const isDefaultValid = await invoke('valid_url', {
|
||||
url: DEFAULT_BG,
|
||||
})
|
||||
|
||||
await setConfigOption('cert_generated', true)
|
||||
this.setState(
|
||||
{
|
||||
bgFile: isDefaultValid ? DEFAULT_BG : FALLBACK_BG,
|
||||
},
|
||||
this.forceUpdate
|
||||
)
|
||||
}
|
||||
|
||||
// Period check to only show progress bar when downloading files
|
||||
setInterval(() => {
|
||||
window.addEventListener('changePage', (e) => {
|
||||
this.setState({
|
||||
isDownloading: downloadHandler.getDownloads().filter(d => d.status !== 'finished')?.length > 0
|
||||
// @ts-expect-error - TS doesn't like our custom event
|
||||
page: e.detail,
|
||||
})
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="App" style={
|
||||
this.state.bgFile ? {
|
||||
background: `url("${this.state.bgFile}") fixed`,
|
||||
} : {}
|
||||
}>
|
||||
<TopBar
|
||||
optFunc={() => {
|
||||
this.setState({ optionsOpen: !this.state.optionsOpen })
|
||||
}}
|
||||
downFunc={() => this.setState({ downloadsOpen: !this.state.downloadsOpen })}
|
||||
gameFunc={() => this.setState({ gameDownloadsOpen: !this.state.gameDownloadsOpen })}
|
||||
/>
|
||||
|
||||
<RightBar />
|
||||
|
||||
<NewsSection />
|
||||
|
||||
{
|
||||
// Mini downloads section
|
||||
this.state.miniDownloadsOpen ? (
|
||||
<div className="MiniDownloads" id="miniDownloadContainer">
|
||||
<MiniDialog
|
||||
title="Downloads"
|
||||
closeFn={() => {
|
||||
this.setState({ miniDownloadsOpen: false })
|
||||
}}
|
||||
>
|
||||
<DownloadList downloadManager={downloadHandler} />
|
||||
</MiniDialog>
|
||||
<div className="arrow-down"></div>
|
||||
</div>
|
||||
) : null
|
||||
<div
|
||||
className="App"
|
||||
style={
|
||||
this.state.bgFile
|
||||
? {
|
||||
background: `url("${this.state.bgFile}") fixed`,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
|
||||
{
|
||||
// Download menu
|
||||
this.state.downloadsOpen ? (
|
||||
<Downloads
|
||||
downloadManager={downloadHandler}
|
||||
closeFn={() => this.setState({ downloadsOpen: false })}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
||||
{
|
||||
// Options menu
|
||||
this.state.optionsOpen ? (
|
||||
<Options
|
||||
downloadManager={downloadHandler}
|
||||
closeFn={() => this.setState({ optionsOpen: !this.state.optionsOpen })}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
||||
{
|
||||
// Game downloads menu
|
||||
this.state.gameDownloadsOpen ? (
|
||||
<Game
|
||||
downloadManager={downloadHandler}
|
||||
closeFn={() => this.setState({ gameDownloadsOpen: false })}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
||||
<div className="BottomSection" id="bottomSectionContainer">
|
||||
<ServerLaunchSection />
|
||||
|
||||
<div id="DownloadProgress"
|
||||
onClick={() => this.setState({ miniDownloadsOpen: !this.state.miniDownloadsOpen })}
|
||||
>
|
||||
{ this.state.isDownloading ?
|
||||
<MainProgressBar downloadManager={downloadHandler} />
|
||||
: null }
|
||||
</div>
|
||||
</div>
|
||||
>
|
||||
{(() => {
|
||||
switch (this.state.page) {
|
||||
case 'modding':
|
||||
return <Mods downloadHandler={downloadHandler} />
|
||||
default:
|
||||
return <Main downloadHandler={downloadHandler} />
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import './App.css'
|
||||
|
||||
import TopBar from './components/TopBar'
|
||||
|
||||
import {invoke} from '@tauri-apps/api/tauri'
|
||||
import {dataDir} from '@tauri-apps/api/path'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import TextInput from './components/common/TextInput'
|
||||
|
||||
let proxyAddress = ''
|
||||
@@ -15,7 +15,7 @@ async function setProxyAddress(address: string) {
|
||||
}
|
||||
|
||||
async function startProxy() {
|
||||
await invoke('connect', { port: 2222, certificatePath: await dataDir() + '\\cultivation\\ca' })
|
||||
await invoke('connect', { port: 2222, certificatePath: (await dataDir()) + '\\cultivation\\ca' })
|
||||
await invoke('open_in_browser', { url: 'https://hoyoverse.com' })
|
||||
}
|
||||
|
||||
@@ -24,27 +24,23 @@ async function stopProxy() {
|
||||
}
|
||||
|
||||
async function generateCertificates() {
|
||||
await invoke('generate_ca_files', { path: await dataDir() + '\\cultivation' })
|
||||
await invoke('generate_ca_files', { path: (await dataDir()) + '\\cultivation' })
|
||||
}
|
||||
|
||||
async function generateInfo() {
|
||||
console.log({
|
||||
certificatePath: await dataDir() + '\\cultivation\\ca',
|
||||
certificatePath: (await dataDir()) + '\\cultivation\\ca',
|
||||
isAdmin: await invoke('is_elevated'),
|
||||
connectingTo: proxyAddress
|
||||
connectingTo: proxyAddress,
|
||||
})
|
||||
alert('check your dev console and send that in #cultivation')
|
||||
}
|
||||
|
||||
function none() {
|
||||
alert('none')
|
||||
}
|
||||
|
||||
class Debug extends React.Component{
|
||||
class Debug extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="App">
|
||||
<TopBar optFunc={none} downFunc={none} gameFunc={none} />
|
||||
<TopBar />
|
||||
<TextInput readOnly={false} initalValue={'change to set proxy address'} onChange={setProxyAddress} />
|
||||
<button onClick={startProxy}>start proxy</button>
|
||||
<button onClick={stopProxy}>stop proxy</button>
|
||||
@@ -55,4 +51,4 @@ class Debug extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
export default Debug
|
||||
export default Debug
|
||||
|
||||
243
src/ui/Main.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
import React from 'react'
|
||||
|
||||
// Major Components
|
||||
import TopBar from './components/TopBar'
|
||||
import ServerLaunchSection from './components/ServerLaunchSection'
|
||||
import MainProgressBar from './components/common/MainProgressBar'
|
||||
import Options from './components/menu/Options'
|
||||
import MiniDialog from './components/MiniDialog'
|
||||
import DownloadList from './components/common/DownloadList'
|
||||
import Downloads from './components/menu/Downloads'
|
||||
import NewsSection from './components/news/NewsSection'
|
||||
import Game from './components/menu/Game'
|
||||
import RightBar from './components/RightBar'
|
||||
|
||||
import { getConfigOption, setConfigOption } from '../utils/configuration'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { appWindow } from '@tauri-apps/api/window'
|
||||
import { unpatchGame } from '../utils/rsa'
|
||||
import DownloadHandler from '../utils/download'
|
||||
|
||||
// Graphics
|
||||
import cogBtn from '../resources/icons/cog.svg'
|
||||
import downBtn from '../resources/icons/download.svg'
|
||||
import wrenchBtn from '../resources/icons/wrench.svg'
|
||||
import { ExtrasMenu } from './components/menu/ExtrasMenu'
|
||||
|
||||
interface IProps {
|
||||
downloadHandler: DownloadHandler
|
||||
}
|
||||
|
||||
interface IState {
|
||||
isDownloading: boolean
|
||||
optionsOpen: boolean
|
||||
miniDownloadsOpen: boolean
|
||||
downloadsOpen: boolean
|
||||
gameDownloadsOpen: boolean
|
||||
extrasOpen: boolean
|
||||
migotoSet: boolean
|
||||
playGame: (exe?: string, proc_name?: string) => void
|
||||
}
|
||||
|
||||
export class Main extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props)
|
||||
this.state = {
|
||||
isDownloading: false,
|
||||
optionsOpen: false,
|
||||
miniDownloadsOpen: false,
|
||||
downloadsOpen: false,
|
||||
gameDownloadsOpen: false,
|
||||
extrasOpen: false,
|
||||
migotoSet: false,
|
||||
playGame: () => {
|
||||
alert('Error launching game')
|
||||
},
|
||||
}
|
||||
|
||||
listen('lang_error', (payload) => {
|
||||
console.log(payload)
|
||||
})
|
||||
|
||||
listen('jar_extracted', ({ payload }: { payload: string }) => {
|
||||
setConfigOption('grasscutter_path', payload)
|
||||
})
|
||||
|
||||
// Emitted for rsa replacing-purposes
|
||||
listen('game_closed', async () => {
|
||||
const wasPatched = await getConfigOption('patch_rsa')
|
||||
|
||||
if (wasPatched) {
|
||||
const unpatched = await unpatchGame()
|
||||
|
||||
if (unpatched) {
|
||||
alert(`Could not unpatch game! (Delete version.dll in your game folder)`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let min = false
|
||||
|
||||
// periodically check if we need to min/max based on whether the game is open
|
||||
setInterval(async () => {
|
||||
const gameOpen = await invoke('is_game_running')
|
||||
|
||||
if (gameOpen && !min) {
|
||||
appWindow.minimize()
|
||||
min = true
|
||||
} else if (!gameOpen && min) {
|
||||
appWindow.unminimize()
|
||||
min = false
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
this.openExtrasMenu = this.openExtrasMenu.bind(this)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const cert_generated = await getConfigOption('cert_generated')
|
||||
|
||||
this.setState({
|
||||
migotoSet: !!(await getConfigOption('migoto_path')),
|
||||
})
|
||||
|
||||
if (!cert_generated) {
|
||||
// Generate the certificate
|
||||
await invoke('generate_ca_files', {
|
||||
path: (await dataDir()) + 'cultivation',
|
||||
})
|
||||
|
||||
await setConfigOption('cert_generated', true)
|
||||
}
|
||||
|
||||
// Ensure old configs are updated to use RSA
|
||||
const updatedConfig = await getConfigOption('patch_rsa')
|
||||
await setConfigOption('patch_rsa', updatedConfig)
|
||||
|
||||
// Period check to only show progress bar when downloading files
|
||||
setInterval(() => {
|
||||
this.setState({
|
||||
isDownloading: this.props.downloadHandler.getDownloads().filter((d) => d.status !== 'finished')?.length > 0,
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
async openExtrasMenu(playGame: () => void) {
|
||||
this.setState({
|
||||
extrasOpen: true,
|
||||
playGame,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<TopBar>
|
||||
<div
|
||||
id="settingsBtn"
|
||||
onClick={() => this.setState({ optionsOpen: !this.state.optionsOpen })}
|
||||
className="TopButton"
|
||||
>
|
||||
<img src={cogBtn} alt="settings" />
|
||||
</div>
|
||||
<div
|
||||
id="downloadsBtn"
|
||||
className="TopButton"
|
||||
onClick={() => this.setState({ downloadsOpen: !this.state.downloadsOpen })}
|
||||
>
|
||||
<img src={downBtn} alt="downloads" />
|
||||
</div>
|
||||
{this.state.migotoSet && (
|
||||
<div
|
||||
id="modsBtn"
|
||||
onClick={() => {
|
||||
// Create and dispatch a custom "openMods" event
|
||||
const event = new CustomEvent('changePage', { detail: 'modding' })
|
||||
window.dispatchEvent(event)
|
||||
}}
|
||||
className="TopButton"
|
||||
>
|
||||
<img src={wrenchBtn} alt="mods" />
|
||||
</div>
|
||||
)}
|
||||
{/* <div id="gameBtn" className="TopButton" onClick={() => this.setState({ gameDownloadsOpen: !this.state.gameDownloadsOpen })}>
|
||||
<img src={gameBtn} alt="game" />
|
||||
</div> */}
|
||||
</TopBar>
|
||||
|
||||
<RightBar />
|
||||
|
||||
<NewsSection />
|
||||
|
||||
{
|
||||
// Extras section
|
||||
this.state.extrasOpen && (
|
||||
<ExtrasMenu closeFn={() => this.setState({ extrasOpen: false })} playGame={this.state.playGame}>
|
||||
Yo
|
||||
</ExtrasMenu>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
// Mini downloads section
|
||||
this.state.miniDownloadsOpen ? (
|
||||
<div className="MiniDownloads" id="miniDownloadContainer">
|
||||
<MiniDialog
|
||||
title="Downloads"
|
||||
closeFn={() => {
|
||||
this.setState({ miniDownloadsOpen: false })
|
||||
}}
|
||||
>
|
||||
<DownloadList downloadManager={this.props.downloadHandler} />
|
||||
</MiniDialog>
|
||||
<div className="arrow-down"></div>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
|
||||
{
|
||||
// Download menu
|
||||
this.state.downloadsOpen ? (
|
||||
<Downloads
|
||||
downloadManager={this.props.downloadHandler}
|
||||
closeFn={() => this.setState({ downloadsOpen: false })}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
||||
{
|
||||
// Options menu
|
||||
this.state.optionsOpen ? (
|
||||
<Options
|
||||
downloadManager={this.props.downloadHandler}
|
||||
closeFn={() => this.setState({ optionsOpen: !this.state.optionsOpen })}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
||||
{
|
||||
// Game downloads menu
|
||||
this.state.gameDownloadsOpen ? (
|
||||
<Game
|
||||
downloadManager={this.props.downloadHandler}
|
||||
closeFn={() => this.setState({ gameDownloadsOpen: false })}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
||||
<div className="BottomSection" id="bottomSectionContainer">
|
||||
<ServerLaunchSection openExtras={this.openExtrasMenu} />
|
||||
|
||||
<div
|
||||
id="DownloadProgress"
|
||||
onClick={() => this.setState({ miniDownloadsOpen: !this.state.miniDownloadsOpen })}
|
||||
>
|
||||
{this.state.isDownloading ? <MainProgressBar downloadManager={this.props.downloadHandler} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
41
src/ui/Mods.css
Normal file
@@ -0,0 +1,41 @@
|
||||
.Mods {
|
||||
backdrop-filter: blur(10px);
|
||||
height: 90%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Stuff for the top bar progress bar */
|
||||
.TopDownloads {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 35.5%;
|
||||
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.TopDownloads .ProgressBar {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.ModMenu {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.ModMenu .BigButton {
|
||||
font-size: 16px;
|
||||
padding: 6px 30px;
|
||||
}
|
||||
|
||||
.ModDownloadList {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.ModDownloadItem {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
margin: 10px;
|
||||
}
|
||||
169
src/ui/Mods.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import React from 'react'
|
||||
import DownloadHandler from '../utils/download'
|
||||
import { getModDownload, ModData } from '../utils/gamebanana'
|
||||
import { getModsFolder } from '../utils/mods'
|
||||
import { unzip } from '../utils/zipUtils'
|
||||
import ProgressBar from './components/common/MainProgressBar'
|
||||
import { ModHeader } from './components/mods/ModHeader'
|
||||
import { ModList } from './components/mods/ModList'
|
||||
import TopBar from './components/TopBar'
|
||||
|
||||
import './Mods.css'
|
||||
import Back from '../resources/icons/back.svg'
|
||||
import Menu from './components/menu/Menu'
|
||||
import BigButton from './components/common/BigButton'
|
||||
import Tr from '../utils/language'
|
||||
|
||||
interface IProps {
|
||||
downloadHandler: DownloadHandler
|
||||
}
|
||||
|
||||
interface IState {
|
||||
isDownloading: boolean
|
||||
category: string
|
||||
downloadList: { name: string; url: string; mod: ModData }[] | null
|
||||
}
|
||||
|
||||
const headers = [
|
||||
{
|
||||
name: 'ripe',
|
||||
title: 'Hot',
|
||||
},
|
||||
{
|
||||
name: 'new',
|
||||
title: 'New',
|
||||
},
|
||||
{
|
||||
name: 'installed',
|
||||
title: 'Installed',
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Mods currently install into folder labelled with their GB ID
|
||||
*
|
||||
* @TODO Categorizaiton/sorting (by likes, views, etc)
|
||||
*/
|
||||
export class Mods extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
isDownloading: false,
|
||||
category: '',
|
||||
downloadList: null,
|
||||
}
|
||||
|
||||
this.setCategory = this.setCategory.bind(this)
|
||||
this.addDownload = this.addDownload.bind(this)
|
||||
}
|
||||
|
||||
async addDownload(mod: ModData) {
|
||||
const dlLinks = await getModDownload(String(mod.id))
|
||||
|
||||
// Not gonna bother allowing sorting for now
|
||||
const firstLink = dlLinks[0].downloadUrl
|
||||
const fileExt = firstLink.split('.').pop()
|
||||
|
||||
const modName = `${mod.id}.${fileExt}`
|
||||
|
||||
if (dlLinks.length === 0) return
|
||||
|
||||
// If there is one download we don't care to choose
|
||||
if (dlLinks.length === 1) {
|
||||
this.downloadMod(firstLink, modName, mod)
|
||||
return
|
||||
}
|
||||
|
||||
this.setState({
|
||||
downloadList: dlLinks.map((link) => ({
|
||||
name: link.filename,
|
||||
url: link.downloadUrl,
|
||||
mod: mod,
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
async downloadMod(link: string, modName: string, mod: ModData) {
|
||||
const modFolder = await getModsFolder()
|
||||
const path = `${modFolder}/${modName}`
|
||||
|
||||
if (!modFolder) return
|
||||
|
||||
this.props.downloadHandler.addDownload(link, path, async () => {
|
||||
const unzipRes = await unzip(path, modFolder, false, true)
|
||||
|
||||
// Write a modinfo.json file
|
||||
invoke('write_file', {
|
||||
path: `${unzipRes.new_folder}/modinfo.json`,
|
||||
contents: JSON.stringify(mod),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async setCategory(value: string) {
|
||||
this.setState(
|
||||
{
|
||||
category: value,
|
||||
},
|
||||
this.forceUpdate
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="Mods">
|
||||
<TopBar>
|
||||
<div
|
||||
id="backbtn"
|
||||
className="TopButton"
|
||||
onClick={() => {
|
||||
// Create and dispatch a custom "changePage" event
|
||||
const event = new CustomEvent('changePage', { detail: 'main' })
|
||||
window.dispatchEvent(event)
|
||||
}}
|
||||
>
|
||||
<img src={Back} alt="back" />
|
||||
</div>
|
||||
</TopBar>
|
||||
|
||||
{this.state.downloadList && (
|
||||
<Menu className="ModMenu" heading="Links" closeFn={() => this.setState({ downloadList: null })}>
|
||||
<div className="ModDownloadList">
|
||||
{this.state.downloadList.map((o) => {
|
||||
return (
|
||||
<div className="ModDownloadItem" key={o.name}>
|
||||
<div className="ModDownloadName">{o.name}</div>
|
||||
<BigButton
|
||||
id={o.url}
|
||||
onClick={() => {
|
||||
const fileExt = o.url.split('.').pop()
|
||||
const modName = `${o.mod.id}.${fileExt}`
|
||||
|
||||
this.downloadMod(o.url, modName, o.mod)
|
||||
this.setState({
|
||||
downloadList: null,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Menu>
|
||||
)}
|
||||
|
||||
<div className="TopDownloads">
|
||||
<ProgressBar downloadManager={this.props.downloadHandler} withStats={false} />
|
||||
</div>
|
||||
|
||||
<ModHeader onChange={this.setCategory} headers={headers} defaultHeader={'ripe'} />
|
||||
|
||||
<ModList key={this.state.category} mode={this.state.category} addDownload={this.addDownload} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
.MiniDialog {
|
||||
position: fixed;
|
||||
z-index: 99;
|
||||
|
||||
|
||||
/* Len and width */
|
||||
height: 30%;
|
||||
width: 30%;
|
||||
@@ -32,4 +32,4 @@
|
||||
|
||||
.MiniDialog .ProgressText {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import Close from '../../resources/icons/close.svg'
|
||||
import './MiniDialog.css'
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode[] | React.ReactNode;
|
||||
title?: string;
|
||||
closeable?: boolean;
|
||||
closeFn: () => void;
|
||||
children: React.ReactNode[] | React.ReactNode
|
||||
title?: string
|
||||
closeable?: boolean
|
||||
closeFn: () => void
|
||||
}
|
||||
|
||||
export default class MiniDialog extends React.Component<IProps, never> {
|
||||
@@ -19,7 +19,7 @@ export default class MiniDialog extends React.Component<IProps, never> {
|
||||
document.addEventListener('mousedown', (evt) => {
|
||||
const tgt = evt.target as HTMLElement
|
||||
const isInside = tgt.closest('.MiniDialog') !== null
|
||||
|
||||
|
||||
if (!isInside) {
|
||||
this.props.closeFn()
|
||||
}
|
||||
@@ -33,13 +33,12 @@ export default class MiniDialog extends React.Component<IProps, never> {
|
||||
render() {
|
||||
return (
|
||||
<div className="MiniDialog" id="miniDialogContainer">
|
||||
{
|
||||
this.props.closeable !== undefined && this.props.closeable ?
|
||||
<div className="MiniDialogTop" id="miniDialogContainerTop" onClick={this.props.closeFn}>
|
||||
<span>{this.props?.title}</span>
|
||||
<img src={Close} className="MiniDialogClose" id="miniDialogButtonClose" />
|
||||
</div> : null
|
||||
}
|
||||
{this.props.closeable !== undefined && this.props.closeable ? (
|
||||
<div className="MiniDialogTop" id="miniDialogContainerTop" onClick={this.props.closeFn}>
|
||||
<span>{this.props?.title}</span>
|
||||
<img src={Close} className="MiniDialogClose" id="miniDialogButtonClose" />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="MiniDialogInner" id="miniDialogContent">
|
||||
{this.props.children}
|
||||
@@ -47,4 +46,4 @@ export default class MiniDialog extends React.Component<IProps, never> {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
position: absolute;
|
||||
transform: translate(0%, 0%);
|
||||
|
||||
display:flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
@@ -36,14 +36,14 @@
|
||||
filter: invert(75%) sepia(0%) saturate(100%) hue-rotate(0deg) brightness(100%) contrast(100%);
|
||||
}
|
||||
|
||||
@media(max-height: 580px) {
|
||||
.RightBar {
|
||||
height: calc(100vh - 180px);
|
||||
}
|
||||
@media (max-height: 580px) {
|
||||
.RightBar {
|
||||
height: calc(100vh - 180px);
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-height: 500px) {
|
||||
.RightBar {
|
||||
height: calc(100vh - 170px);
|
||||
}
|
||||
}
|
||||
@media (max-height: 500px) {
|
||||
.RightBar {
|
||||
height: calc(100vh - 170px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import React from 'react'
|
||||
|
||||
import Discord from '../../resources/icons/discord.svg'
|
||||
import Github from '../../resources/icons/github.svg'
|
||||
import Discord from '../../resources/icons/discord.svg'
|
||||
import Github from '../../resources/icons/github.svg'
|
||||
|
||||
import './RightBar.css'
|
||||
|
||||
@@ -28,4 +28,4 @@ export default class RightBar extends React.Component {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,12 @@
|
||||
width: 5%;
|
||||
}
|
||||
|
||||
.AkebiIcon,
|
||||
#ExtrasMenuButton {
|
||||
width: 5%;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.ExtrasIcon,
|
||||
.ServerIcon {
|
||||
height: 20px;
|
||||
filter: invert(28%) sepia(28%) saturate(1141%) hue-rotate(352deg) brightness(96%) contrast(88%);
|
||||
@@ -109,17 +114,17 @@
|
||||
}
|
||||
|
||||
@media (max-width: 1040px) {
|
||||
#playButton {
|
||||
right: 5%;
|
||||
}
|
||||
#playButton {
|
||||
right: 5%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 870px) {
|
||||
#playButton {
|
||||
min-width: 235px;
|
||||
}
|
||||
#playButton {
|
||||
min-width: 235px;
|
||||
}
|
||||
|
||||
#officialPlay {
|
||||
width: 40%;
|
||||
}
|
||||
#officialPlay {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,33 +8,39 @@ import { translate } from '../../utils/language'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
import Server from '../../resources/icons/server.svg'
|
||||
import Akebi from '../../resources/icons/akebi.svg'
|
||||
import Plus from '../../resources/icons/plus.svg'
|
||||
|
||||
import './ServerLaunchSection.css'
|
||||
import {dataDir} from '@tauri-apps/api/path'
|
||||
import { getGameExecutable } from '../../utils/game'
|
||||
import { patchGame, unpatchGame } from '../../utils/metadata'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { getGameExecutable, getGameVersion } from '../../utils/game'
|
||||
import { patchGame, unpatchGame } from '../../utils/rsa'
|
||||
|
||||
interface IState {
|
||||
grasscutterEnabled: boolean;
|
||||
buttonLabel: string;
|
||||
checkboxLabel: string;
|
||||
ip: string;
|
||||
port: string;
|
||||
|
||||
ipPlaceholder: string;
|
||||
portPlaceholder: string;
|
||||
|
||||
portHelpText: string;
|
||||
|
||||
httpsLabel: string;
|
||||
httpsEnabled: boolean;
|
||||
|
||||
swag: boolean;
|
||||
interface IProps {
|
||||
openExtras: (playGame: () => void) => void
|
||||
}
|
||||
|
||||
export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
constructor(props: {}) {
|
||||
interface IState {
|
||||
grasscutterEnabled: boolean
|
||||
buttonLabel: string
|
||||
checkboxLabel: string
|
||||
ip: string
|
||||
port: string
|
||||
|
||||
ipPlaceholder: string
|
||||
portPlaceholder: string
|
||||
|
||||
portHelpText: string
|
||||
|
||||
httpsLabel: string
|
||||
httpsEnabled: boolean
|
||||
|
||||
swag: boolean
|
||||
akebiSet: boolean
|
||||
migotoSet: boolean
|
||||
}
|
||||
|
||||
export default class ServerLaunchSection extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -48,12 +54,13 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
portHelpText: '',
|
||||
httpsLabel: '',
|
||||
httpsEnabled: false,
|
||||
swag: false
|
||||
swag: false,
|
||||
akebiSet: false,
|
||||
migotoSet: false,
|
||||
}
|
||||
|
||||
this.toggleGrasscutter = this.toggleGrasscutter.bind(this)
|
||||
this.playGame = this.playGame.bind(this)
|
||||
this.launchAkebi = this.launchAkebi.bind(this)
|
||||
this.setIp = this.setIp.bind(this)
|
||||
this.setPort = this.setPort.bind(this)
|
||||
this.toggleHttps = this.toggleHttps.bind(this)
|
||||
@@ -73,7 +80,9 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
portHelpText: await translate('help.port_help_text'),
|
||||
httpsLabel: await translate('main.https_enable'),
|
||||
httpsEnabled: config.https_enabled || false,
|
||||
swag: config.swag_mode || false
|
||||
swag: config.swag_mode || false,
|
||||
akebiSet: config.akebi_path !== '',
|
||||
migotoSet: config.migoto_path !== '',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -84,7 +93,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
|
||||
// Set state as well
|
||||
this.setState({
|
||||
grasscutterEnabled: config.toggle_grasscutter
|
||||
grasscutterEnabled: config.toggle_grasscutter,
|
||||
})
|
||||
|
||||
await saveConfig(config)
|
||||
@@ -93,18 +102,40 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
async playGame(exe?: string, proc_name?: string) {
|
||||
const config = await getConfig()
|
||||
|
||||
if(!await getGameExecutable()) {
|
||||
if (!(await getGameExecutable())) {
|
||||
alert('Game executable not set!')
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Connect to proxy
|
||||
if (config.toggle_grasscutter) {
|
||||
const patched = await patchGame()
|
||||
if (config.patch_rsa) {
|
||||
const gameVersion = await getGameVersion()
|
||||
console.log(gameVersion)
|
||||
|
||||
if (!patched) {
|
||||
alert('Could not patch game!')
|
||||
return
|
||||
if (gameVersion == null) {
|
||||
alert(
|
||||
'Game version could not be determined. Please make sure you have the game correctly selected and try again.'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (gameVersion?.major == 2 && gameVersion?.minor < 9) {
|
||||
alert('Game version is too old for RSA patching. Please disable RSA patching in the settings and try again.')
|
||||
return
|
||||
}
|
||||
|
||||
if (gameVersion?.major == 3 && gameVersion?.minor < 1) {
|
||||
alert('Game version is too old for RSA patching. Please disable RSA patching in the settings and try again.')
|
||||
return
|
||||
}
|
||||
|
||||
const patched = await patchGame()
|
||||
|
||||
if (!patched) {
|
||||
alert('Could not patch! Try launching again, or patching manually.')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const game_exe = await getGameExecutable()
|
||||
@@ -113,14 +144,18 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
await setConfigOption('last_ip', this.state.ip)
|
||||
await setConfigOption('last_port', this.state.port)
|
||||
|
||||
// Set IP
|
||||
await invoke('set_proxy_addr', { addr: (this.state.httpsEnabled ? 'https':'http') + '://' + this.state.ip + ':' + this.state.port })
|
||||
await invoke('enable_process_watcher', {
|
||||
process: proc_name || game_exe
|
||||
process: proc_name || game_exe,
|
||||
})
|
||||
|
||||
// Connect to proxy
|
||||
await invoke('connect', { port: 8365, certificatePath: await dataDir() + '\\cultivation\\ca' })
|
||||
if (config.use_internal_proxy) {
|
||||
// Set IP
|
||||
await invoke('set_proxy_addr', {
|
||||
addr: (this.state.httpsEnabled ? 'https' : 'http') + '://' + this.state.ip + ':' + this.state.port,
|
||||
})
|
||||
// Connect to proxy
|
||||
await invoke('connect', { port: 8365, certificatePath: (await dataDir()) + '\\cultivation\\ca' })
|
||||
}
|
||||
|
||||
// Open server as well if the options are set
|
||||
if (config.grasscutter_with_game) {
|
||||
@@ -132,24 +167,27 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
await invoke('run_jar', {
|
||||
path: config.grasscutter_path,
|
||||
executeIn: jarFolder,
|
||||
javaPath: config.java_path || ''
|
||||
javaPath: config.java_path || '',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const unpatched = await unpatchGame()
|
||||
|
||||
if (!unpatched) {
|
||||
alert(`Could not unpatch game, aborting launch! (You can find your metadata backup in ${await dataDir()}\\cultivation\\)`)
|
||||
return
|
||||
}
|
||||
await unpatchGame()
|
||||
}
|
||||
|
||||
|
||||
if (config.wipe_login) {
|
||||
// First wipe registry if we have to
|
||||
await invoke('wipe_registry', {
|
||||
// The exe is always PascalCase so we can get the dir using regex
|
||||
execName: (await getGameExecutable())?.split('.exe')[0].replace(/([a-z\d])([A-Z])/g, '$1 $2'),
|
||||
})
|
||||
}
|
||||
|
||||
// Launch the program
|
||||
const gameExists = await invoke('dir_exists', {
|
||||
path: exe || config.game_install_path
|
||||
path: exe || config.game_install_path,
|
||||
})
|
||||
|
||||
if (gameExists) await invoke('run_program', { path: exe || config.game_install_path })
|
||||
if (gameExists) await invoke('run_program_relative', { path: exe || config.game_install_path })
|
||||
else alert('Game not found! At: ' + (exe || config.game_install_path))
|
||||
}
|
||||
|
||||
@@ -170,29 +208,19 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
await invoke('run_jar', {
|
||||
path: config.grasscutter_path,
|
||||
executeIn: jarFolder,
|
||||
javaPath: config.java_path || ''
|
||||
javaPath: config.java_path || '',
|
||||
})
|
||||
}
|
||||
|
||||
async launchAkebi() {
|
||||
const config = await getConfig()
|
||||
|
||||
// Get game exe from game path, so we can watch it
|
||||
const pathArr = config.game_install_path.replace(/\\/g, '/').split('/')
|
||||
const gameExec = pathArr[pathArr.length - 1]
|
||||
|
||||
await this.playGame(config.akebi_path, gameExec)
|
||||
}
|
||||
|
||||
setIp(text: string) {
|
||||
this.setState({
|
||||
ip: text
|
||||
ip: text,
|
||||
})
|
||||
}
|
||||
|
||||
setPort(text: string) {
|
||||
this.setState({
|
||||
port: text
|
||||
port: text,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -203,7 +231,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
|
||||
// Set state as well
|
||||
this.setState({
|
||||
httpsEnabled: config.https_enabled
|
||||
httpsEnabled: config.https_enabled,
|
||||
})
|
||||
|
||||
await saveConfig(config)
|
||||
@@ -213,34 +241,54 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
return (
|
||||
<div id="playButton">
|
||||
<div id="serverControls">
|
||||
<Checkbox id="enableGC" label={this.state.checkboxLabel} onChange={this.toggleGrasscutter} checked={this.state.grasscutterEnabled}/>
|
||||
<Checkbox
|
||||
id="enableGC"
|
||||
label={this.state.checkboxLabel}
|
||||
onChange={this.toggleGrasscutter}
|
||||
checked={this.state.grasscutterEnabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
this.state.grasscutterEnabled && (
|
||||
<div>
|
||||
<div className="ServerConfig" id="serverConfigContainer">
|
||||
<TextInput id="ip" key="ip" placeholder={this.state.ipPlaceholder} onChange={this.setIp} initalValue={this.state.ip} />
|
||||
<TextInput style={{
|
||||
{this.state.grasscutterEnabled && (
|
||||
<div>
|
||||
<div className="ServerConfig" id="serverConfigContainer">
|
||||
<TextInput
|
||||
id="ip"
|
||||
key="ip"
|
||||
placeholder={this.state.ipPlaceholder}
|
||||
onChange={this.setIp}
|
||||
initalValue={this.state.ip}
|
||||
/>
|
||||
<TextInput
|
||||
style={{
|
||||
width: '10%',
|
||||
}} id="port" key="port" placeholder={this.state.portPlaceholder} onChange={this.setPort} initalValue={this.state.port} />
|
||||
<HelpButton contents={this.state.portHelpText} />
|
||||
<Checkbox id="httpsEnable" label={this.state.httpsLabel} onChange={this.toggleHttps} checked={this.state.httpsEnabled} />
|
||||
</div>
|
||||
}}
|
||||
id="port"
|
||||
key="port"
|
||||
placeholder={this.state.portPlaceholder}
|
||||
onChange={this.setPort}
|
||||
initalValue={this.state.port}
|
||||
/>
|
||||
<HelpButton contents={this.state.portHelpText} />
|
||||
<Checkbox
|
||||
id="httpsEnable"
|
||||
label={this.state.httpsLabel}
|
||||
onChange={this.toggleHttps}
|
||||
checked={this.state.httpsEnabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="ServerLaunchButtons" id="serverLaunchContainer">
|
||||
<BigButton onClick={this.playGame} id="officialPlay">{this.state.buttonLabel}</BigButton>
|
||||
{
|
||||
this.state.swag && (
|
||||
<BigButton onClick={this.launchAkebi} id="akebiLaunch">
|
||||
<img className="AkebiIcon" id="akebiIcon" src={Akebi} />
|
||||
</BigButton>
|
||||
)
|
||||
}
|
||||
<BigButton onClick={this.playGame} id="officialPlay">
|
||||
{this.state.buttonLabel}
|
||||
</BigButton>
|
||||
{this.state.swag && (
|
||||
<BigButton onClick={() => this.props.openExtras(this.playGame)} id="ExtrasMenuButton">
|
||||
<img className="ExtrasIcon" id="extrasIcon" src={Plus} />
|
||||
</BigButton>
|
||||
)}
|
||||
<BigButton onClick={this.launchServer} id="serverLaunch">
|
||||
<img className="ServerIcon" id="serverLaunchIcon" src={Server} />
|
||||
</BigButton>
|
||||
@@ -248,4 +296,4 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,4 +51,4 @@
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
import React from 'react'
|
||||
import { app } from '@tauri-apps/api'
|
||||
import { appWindow } from '@tauri-apps/api/window'
|
||||
import closeIcon from '../../resources/icons/close.svg'
|
||||
import minIcon from '../../resources/icons/min.svg'
|
||||
import cogBtn from '../../resources/icons/cog.svg'
|
||||
import downBtn from '../../resources/icons/download.svg'
|
||||
|
||||
import { getConfig, setConfigOption } from '../../utils/configuration'
|
||||
import Tr from '../../utils/language'
|
||||
|
||||
import './TopBar.css'
|
||||
import { getConfig, setConfigOption } from '../../utils/configuration'
|
||||
import closeIcon from '../../resources/icons/close.svg'
|
||||
import minIcon from '../../resources/icons/min.svg'
|
||||
|
||||
interface IProps {
|
||||
optFunc: () => void;
|
||||
downFunc: () => void;
|
||||
gameFunc: () => void;
|
||||
children?: React.ReactNode | React.ReactNode[]
|
||||
}
|
||||
|
||||
interface IState {
|
||||
version: string;
|
||||
clicks: number;
|
||||
intv: NodeJS.Timeout | null;
|
||||
version: string
|
||||
clicks: number
|
||||
intv: NodeJS.Timeout | null
|
||||
}
|
||||
|
||||
export default class TopBar extends React.Component<IProps, IState> {
|
||||
@@ -30,7 +25,7 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
this.state = {
|
||||
version: '0.0.0',
|
||||
clicks: 0,
|
||||
intv: null
|
||||
intv: null,
|
||||
}
|
||||
|
||||
this.activateClick = this.activateClick.bind(this)
|
||||
@@ -59,10 +54,10 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
setTimeout(() => {
|
||||
// Gotta clear it so it goes back to regular colors
|
||||
this.setState({
|
||||
clicks: 0
|
||||
clicks: 0,
|
||||
})
|
||||
}, 600)
|
||||
|
||||
|
||||
// Activate... SWAG MODE
|
||||
await setConfigOption('swag_mode', true)
|
||||
|
||||
@@ -75,7 +70,7 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
if (this.state.clicks < 3) {
|
||||
this.setState({
|
||||
clicks: this.state.clicks + 1,
|
||||
intv: setTimeout(() => this.setState({ clicks: 0 }), 1500)
|
||||
intv: setTimeout(() => this.setState({ clicks: 0 }), 1500),
|
||||
})
|
||||
|
||||
return
|
||||
@@ -89,36 +84,30 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
<span data-tauri-drag-region>
|
||||
<Tr text="main.title" />
|
||||
</span>
|
||||
<span data-tauri-drag-region id="version">{this.state?.version}</span>
|
||||
<span data-tauri-drag-region id="version">
|
||||
{this.state?.version}
|
||||
</span>
|
||||
</div>
|
||||
{/**
|
||||
* HEY YOU
|
||||
*
|
||||
* If you're looking at the source code to find the swag mode thing, that's okay! If you're not, move along...
|
||||
* Just do me a favor and don't go telling everyone about how you found it. If you are just helping someone who
|
||||
* for some reason needs it, that's fine, but not EVERYONE needs it, which is why it exists in the first place.
|
||||
*/}
|
||||
<div id="unassumingButton" className={this.state.clicks === 2 ? 'spin' : ''} onClick={this.activateClick}>
|
||||
?
|
||||
</div>
|
||||
{
|
||||
/**
|
||||
* HEY YOU
|
||||
*
|
||||
* If you're looking at the source code to find the swag mode thing, that's okay! If you're not, move along...
|
||||
* Just do me a favor and don't go telling everyone about how you found it. If you are just helping someone who
|
||||
* for some reason needs it, that's fine, but not EVERYONE needs it, which is why it exists in the first place.
|
||||
*/
|
||||
}
|
||||
<div id="unassumingButton" className={this.state.clicks === 2 ? 'spin' : ''} onClick={this.activateClick}>?</div>
|
||||
<div className="TopBtns" id="topBarButtonContainer">
|
||||
<div id="closeBtn" onClick={this.handleClose} className='TopButton'>
|
||||
<div id="closeBtn" onClick={this.handleClose} className="TopButton">
|
||||
<img src={closeIcon} alt="close" />
|
||||
</div>
|
||||
<div id="minBtn" onClick={this.handleMinimize} className='TopButton'>
|
||||
<div id="minBtn" onClick={this.handleMinimize} className="TopButton">
|
||||
<img src={minIcon} alt="minimize" />
|
||||
</div>
|
||||
<div id="settingsBtn" onClick={this.props.optFunc} className='TopButton'>
|
||||
<img src={cogBtn} alt="settings" />
|
||||
</div>
|
||||
<div id="downloadsBtn" className='TopButton' onClick={this.props.downFunc}>
|
||||
<img src={downBtn} alt="downloads" />
|
||||
</div>
|
||||
{/* <div id="gameBtn" className="TopButton" onClick={this.props.gameFunc}>
|
||||
<img src={gameBtn} alt="game" />
|
||||
</div> */}
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,4 +27,4 @@
|
||||
|
||||
.BigButton.disabled:hover {
|
||||
background: linear-gradient(#949494, #9c9c9c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ import React from 'react'
|
||||
import './BigButton.css'
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode;
|
||||
onClick: () => unknown;
|
||||
id: string;
|
||||
disabled?: boolean;
|
||||
children: React.ReactNode
|
||||
onClick: () => unknown
|
||||
id: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
interface IState {
|
||||
disabled?: boolean;
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export default class BigButton extends React.Component<IProps, IState> {
|
||||
@@ -17,7 +17,7 @@ export default class BigButton extends React.Component<IProps, IState> {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
disabled: this.props.disabled
|
||||
disabled: this.props.disabled,
|
||||
}
|
||||
|
||||
this.handleClick = this.handleClick.bind(this)
|
||||
@@ -25,7 +25,7 @@ export default class BigButton extends React.Component<IProps, IState> {
|
||||
|
||||
static getDerivedStateFromProps(props: IProps, _state: IState) {
|
||||
return {
|
||||
disabled: props.disabled
|
||||
disabled: props.disabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,9 +37,13 @@ export default class BigButton extends React.Component<IProps, IState> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={'BigButton ' + (this.state.disabled ? 'disabled' : '')} onClick={this.handleClick} id={this.props.id}>
|
||||
<div
|
||||
className={'BigButton ' + (this.state.disabled ? 'disabled' : '')}
|
||||
onClick={this.handleClick}
|
||||
id={this.props.id}
|
||||
>
|
||||
<div className="BigButtonText">{this.props.children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.Checkbox input[type="checkbox"] {
|
||||
.Checkbox input[type='checkbox'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
|
||||
.CheckboxDisplay img {
|
||||
height: 100%;
|
||||
filter: invert(99%) sepia(0%) saturate(1188%) hue-rotate(186deg) brightness(97%) contrast(67%)
|
||||
filter: invert(99%) sepia(0%) saturate(1188%) hue-rotate(186deg) brightness(97%) contrast(67%);
|
||||
}
|
||||
|
||||
.Checkbox label {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import checkmark from '../../../resources/icons/check.svg'
|
||||
import './Checkbox.css'
|
||||
|
||||
interface IProps {
|
||||
label?: string,
|
||||
checked: boolean,
|
||||
onChange: () => void,
|
||||
id: string
|
||||
label?: string
|
||||
checked: boolean
|
||||
onChange: () => void
|
||||
id?: string
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -19,14 +19,14 @@ export default class Checkbox extends React.Component<IProps, IState> {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
checked: props.checked
|
||||
checked: props.checked,
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: IProps, state: IState) {
|
||||
if (props.checked !== state.checked) {
|
||||
return {
|
||||
checked: props.checked
|
||||
checked: props.checked,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,14 +41,12 @@ export default class Checkbox extends React.Component<IProps, IState> {
|
||||
render() {
|
||||
return (
|
||||
<div className="Checkbox">
|
||||
<input type='checkbox' id={this.props.id} checked={this.state.checked} onChange={this.handleChange} />
|
||||
<input type="checkbox" id={this.props.id} checked={this.state.checked} onChange={this.handleChange} />
|
||||
<label htmlFor={this.props.id}>
|
||||
<div className="CheckboxDisplay">
|
||||
{this.state.checked ? <img src={checkmark} alt='Checkmark' /> : null}
|
||||
</div>
|
||||
<div className="CheckboxDisplay">{this.state.checked ? <img src={checkmark} alt="Checkmark" /> : null}</div>
|
||||
<span>{this.props.label || ''}</span>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
|
||||
.FileSelectIcon img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ interface IProps {
|
||||
readonly?: boolean
|
||||
placeholder?: string
|
||||
folder?: boolean
|
||||
customClearBehaviour?: () => void,
|
||||
customClearBehaviour?: () => void
|
||||
openFolder?: string
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export default class DirInput extends React.Component<IProps, IState> {
|
||||
this.state = {
|
||||
value: props.value || '',
|
||||
placeholder: this.props.placeholder || 'Select file or folder...',
|
||||
folder: this.props.folder || false
|
||||
folder: this.props.folder || false,
|
||||
}
|
||||
|
||||
this.handleIconClick = this.handleIconClick.bind(this)
|
||||
@@ -54,8 +54,8 @@ export default class DirInput extends React.Component<IProps, IState> {
|
||||
async componentDidMount() {
|
||||
if (!this.props.placeholder) {
|
||||
const translation = await translate('components.select_file')
|
||||
this.setState( {
|
||||
placeholder: translation
|
||||
this.setState({
|
||||
placeholder: translation,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -65,15 +65,12 @@ export default class DirInput extends React.Component<IProps, IState> {
|
||||
|
||||
if (this.state.folder) {
|
||||
path = await open({
|
||||
directory: true
|
||||
directory: true,
|
||||
})
|
||||
} else {
|
||||
console.log(this.props.openFolder)
|
||||
path = await open({
|
||||
filters: [
|
||||
{ name: 'Files', extensions: this.props.extensions || ['*'] }
|
||||
],
|
||||
defaultPath: this.props.openFolder
|
||||
filters: [{ name: 'Files', extensions: this.props.extensions || ['*'] }],
|
||||
defaultPath: this.props.openFolder,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -81,7 +78,7 @@ export default class DirInput extends React.Component<IProps, IState> {
|
||||
if (!path) return
|
||||
|
||||
this.setState({
|
||||
value: path
|
||||
value: path,
|
||||
})
|
||||
|
||||
if (this.props.onChange) this.props.onChange(path)
|
||||
@@ -89,12 +86,13 @@ export default class DirInput extends React.Component<IProps, IState> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='DirInput'>
|
||||
<div className="DirInput">
|
||||
<TextInput
|
||||
value={this.state.value}
|
||||
placeholder={this.state.placeholder}
|
||||
clearable={this.props.clearable !== undefined ? this.props.clearable : true}
|
||||
readOnly={this.props.readonly !== undefined ? this.props.readonly : true } onChange={(text: string) => {
|
||||
readOnly={this.props.readonly !== undefined ? this.props.readonly : true}
|
||||
onChange={(text: string) => {
|
||||
this.setState({ value: text })
|
||||
|
||||
if (this.props.onChange) this.props.onChange(text)
|
||||
@@ -108,4 +106,4 @@ export default class DirInput extends React.Component<IProps, IState> {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import DownloadSection from './DownloadSection'
|
||||
import './DownloadList.css'
|
||||
|
||||
interface IProps {
|
||||
downloadManager: DownloadHandler;
|
||||
downloadManager: DownloadHandler
|
||||
}
|
||||
|
||||
export default class DownloadList extends React.Component<IProps, never> {
|
||||
@@ -16,17 +16,14 @@ export default class DownloadList extends React.Component<IProps, never> {
|
||||
render() {
|
||||
const list = this.props.downloadManager.getDownloads().map((download) => {
|
||||
return (
|
||||
<DownloadSection key={download.path} downloadName={download.path} downloadManager={this.props.downloadManager} />
|
||||
<DownloadSection
|
||||
key={download.path}
|
||||
downloadName={download.path}
|
||||
downloadManager={this.props.downloadManager}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
return (
|
||||
<div className="DownloadList">
|
||||
{
|
||||
list.length > 0 ? list : 'No downloads present'
|
||||
}
|
||||
</div>
|
||||
)
|
||||
return <div className="DownloadList">{list.length > 0 ? list : 'No downloads present'}</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,4 +26,4 @@
|
||||
|
||||
.DownloadStatus {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import ProgressBar from './ProgressBar'
|
||||
import './DownloadSection.css'
|
||||
|
||||
interface IProps {
|
||||
downloadManager: DownloadHandler;
|
||||
downloadName: string;
|
||||
downloadManager: DownloadHandler
|
||||
downloadName: string
|
||||
}
|
||||
|
||||
export default class DownloadSection extends React.Component<IProps, never> {
|
||||
@@ -32,4 +32,4 @@ export default class DownloadSection extends React.Component<IProps, never> {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,4 +30,4 @@
|
||||
right: -450%;
|
||||
width: 200px;
|
||||
height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,53 +2,32 @@ import React from 'react'
|
||||
|
||||
import './HelpButton.css'
|
||||
import Help from '../../../resources/icons/help.svg'
|
||||
import MiniDialog from '../MiniDialog'
|
||||
import { translate } from '../../../utils/language'
|
||||
|
||||
interface IProps {
|
||||
children?: React.ReactNode[] | React.ReactNode;
|
||||
children?: React.ReactNode[] | React.ReactNode
|
||||
contents?: string
|
||||
id?: string
|
||||
}
|
||||
|
||||
interface IState {
|
||||
opened: boolean
|
||||
}
|
||||
|
||||
export default class HelpButton extends React.Component<IProps, IState> {
|
||||
export default class HelpButton extends React.Component<IProps, never> {
|
||||
constructor(props: IProps) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
opened: false
|
||||
}
|
||||
|
||||
this.setOpen = this.setOpen.bind(this)
|
||||
this.setClosed = this.setClosed.bind(this)
|
||||
this.showAlert = this.showAlert.bind(this)
|
||||
}
|
||||
|
||||
setOpen() {
|
||||
this.setState({ opened: true })
|
||||
}
|
||||
|
||||
setClosed() {
|
||||
this.setState({ opened: false })
|
||||
async showAlert() {
|
||||
if (this.props.contents) alert(await translate(this.props.contents))
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="HelpSection">
|
||||
<div className="HelpButton" onMouseEnter={this.setOpen} onMouseLeave={this.setClosed}>
|
||||
<div className="HelpButton" onClick={this.showAlert}>
|
||||
<img src={Help} />
|
||||
</div>
|
||||
|
||||
<div className="HelpContents" style={{
|
||||
display: this.state.opened ? 'block' : 'none'
|
||||
}}>
|
||||
<MiniDialog closeFn={this.setClosed}>
|
||||
{this.props.contents || this.props.children}
|
||||
</MiniDialog>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,16 @@ import Tr from '../../../utils/language'
|
||||
import './ProgressBar.css'
|
||||
|
||||
interface IProps {
|
||||
downloadManager: DownloadHandler,
|
||||
downloadManager: DownloadHandler
|
||||
withStats?: boolean
|
||||
}
|
||||
|
||||
interface IState {
|
||||
average: number,
|
||||
files: number,
|
||||
extracting: number,
|
||||
total: number,
|
||||
speed: string,
|
||||
average: number
|
||||
files: number
|
||||
extracting: number
|
||||
total: number
|
||||
speed: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,7 +30,7 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
files,
|
||||
extracting,
|
||||
total: totalSize,
|
||||
speed: '0 B/s'
|
||||
speed: '0 B/s',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,28 +52,33 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
return (
|
||||
<div className="MainProgressBarWrapper">
|
||||
<div className="ProgressBar">
|
||||
<div className="InnerProgress" style={{
|
||||
width: `${(() => {
|
||||
// Handles no files downloading
|
||||
if (this.state.files === 0) {
|
||||
return '100'
|
||||
}
|
||||
<div
|
||||
className="InnerProgress"
|
||||
style={{
|
||||
width: `${(() => {
|
||||
// Handles no files downloading
|
||||
if (this.state.files === 0 || this.state.average >= 100) {
|
||||
return '100'
|
||||
}
|
||||
|
||||
if (this.state.total <= 0) {
|
||||
return '0'
|
||||
}
|
||||
if (this.state.total <= 0) {
|
||||
return '0'
|
||||
}
|
||||
|
||||
return this.state.average
|
||||
})()}%`,
|
||||
}}></div>
|
||||
return this.state.average
|
||||
})()}%`,
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div className="MainProgressText">
|
||||
<Tr text="main.files_downloading" /> {this.state.files} ({this.state.speed})
|
||||
<br />
|
||||
<Tr text="main.files_extracting" /> {this.state.extracting}
|
||||
</div>
|
||||
{(this.props.withStats === undefined || this.props.withStats) && (
|
||||
<div className="MainProgressText">
|
||||
<Tr text="main.files_downloading" /> {this.state.files} ({this.state.speed})
|
||||
<br />
|
||||
<Tr text="main.files_extracting" /> {this.state.extracting}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.ProgressBar, .InnerProgress {
|
||||
.ProgressBar,
|
||||
.InnerProgress {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -91,4 +92,4 @@
|
||||
|
||||
.downloadStop:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import React from 'react'
|
||||
import { capitalize } from '../../../utils/string'
|
||||
|
||||
import Stop from '../../../resources/icons/close.svg'
|
||||
import Stop from '../../../resources/icons/close.svg'
|
||||
import './ProgressBar.css'
|
||||
import DownloadHandler from '../../../utils/download'
|
||||
import { translate } from '../../../utils/language'
|
||||
|
||||
interface IProps {
|
||||
path: string,
|
||||
downloadManager: DownloadHandler,
|
||||
path: string
|
||||
downloadManager: DownloadHandler
|
||||
}
|
||||
|
||||
interface IState {
|
||||
progress: number,
|
||||
status: string,
|
||||
total: number,
|
||||
progress: number
|
||||
status: string
|
||||
total: number
|
||||
}
|
||||
|
||||
export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
@@ -36,7 +36,7 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
const prog = this.props.downloadManager.getDownloadProgress(this.props.path)
|
||||
this.setState({
|
||||
progress: prog?.progress || 0,
|
||||
status: await translate(`download_status.${prog?.status || 'stopped'}`) || 'stopped',
|
||||
status: (await translate(`download_status.${prog?.status || 'stopped'}`)) || 'stopped',
|
||||
total: prog?.total || 0,
|
||||
})
|
||||
|
||||
@@ -54,24 +54,29 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ProgressBarWrapper">
|
||||
<div style={{
|
||||
width: '80%'
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
width: '80%',
|
||||
}}
|
||||
>
|
||||
<div className="ProgressBar">
|
||||
<div className="InnerProgress" style={{
|
||||
width: `${(() => {
|
||||
// Handles files with content-lengths of 0
|
||||
if (this.state.status === 'finished') {
|
||||
return '100'
|
||||
}
|
||||
<div
|
||||
className="InnerProgress"
|
||||
style={{
|
||||
width: `${(() => {
|
||||
// Handles files with content-lengths of 0
|
||||
if (this.state.status === 'finished') {
|
||||
return '100'
|
||||
}
|
||||
|
||||
if (this.state.total <= 0) {
|
||||
return '0'
|
||||
}
|
||||
if (this.state.total <= 0) {
|
||||
return '0'
|
||||
}
|
||||
|
||||
return this.state.progress / this.state.total * 100
|
||||
})()}%`,
|
||||
}}></div>
|
||||
return (this.state.progress / this.state.total) * 100
|
||||
})()}%`,
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div className="DownloadControls">
|
||||
<div onClick={this.stopDownload} className="downloadStop">
|
||||
@@ -80,10 +85,8 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ProgressText">
|
||||
{capitalize(this.state.status) || 'Waiting'}
|
||||
</div>
|
||||
<div className="ProgressText">{capitalize(this.state.status) || 'Waiting'}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
right: 16%;
|
||||
|
||||
|
||||
filter: invert(99%) sepia(0%) saturate(1188%) hue-rotate(186deg) brightness(97%) contrast(67%);
|
||||
}
|
||||
|
||||
@@ -28,4 +28,4 @@
|
||||
|
||||
.TextInputClear {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ import './TextInput.css'
|
||||
import Close from '../../../resources/icons/close.svg'
|
||||
|
||||
interface IProps {
|
||||
value?: string;
|
||||
initalValue?: string;
|
||||
placeholder?: string;
|
||||
onChange?: (value: string) => void;
|
||||
readOnly?: boolean;
|
||||
id?: string;
|
||||
clearable?: boolean;
|
||||
customClearBehaviour?: () => void;
|
||||
style?: React.CSSProperties;
|
||||
value?: string
|
||||
initalValue?: string
|
||||
placeholder?: string
|
||||
onChange?: (value: string) => void
|
||||
readOnly?: boolean
|
||||
id?: string
|
||||
clearable?: boolean
|
||||
customClearBehaviour?: () => void
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -24,14 +24,14 @@ export default class TextInput extends React.Component<IProps, IState> {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
value: props.value || ''
|
||||
value: props.value || '',
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
if (this.props.initalValue) {
|
||||
this.setState({
|
||||
value: this.props.initalValue
|
||||
value: this.props.initalValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -43,26 +43,35 @@ export default class TextInput extends React.Component<IProps, IState> {
|
||||
render() {
|
||||
return (
|
||||
<div className="TextInputWrapper" style={this.props.style || {}}>
|
||||
<input id={this.props?.id} readOnly={this.props.readOnly || false} placeholder={this.props.placeholder || ''} className="TextInput" value={this.state.value} onChange={(e) => {
|
||||
this.setState({ value: e.target.value })
|
||||
if (this.props.onChange) this.props.onChange(e.target.value)
|
||||
}} />
|
||||
{
|
||||
this.props.clearable ?
|
||||
<div className="TextClear" onClick={() => {
|
||||
<input
|
||||
id={this.props?.id}
|
||||
readOnly={this.props.readOnly || false}
|
||||
placeholder={this.props.placeholder || ''}
|
||||
className="TextInput"
|
||||
value={this.state.value}
|
||||
onChange={(e) => {
|
||||
this.setState({ value: e.target.value })
|
||||
if (this.props.onChange) this.props.onChange(e.target.value)
|
||||
}}
|
||||
/>
|
||||
{this.props.clearable ? (
|
||||
<div
|
||||
className="TextClear"
|
||||
onClick={() => {
|
||||
// Run custom behaviour first
|
||||
if (this.props.customClearBehaviour) return this.props.customClearBehaviour()
|
||||
|
||||
this.setState({ value: '' })
|
||||
|
||||
|
||||
if (this.props.onChange) this.props.onChange('')
|
||||
|
||||
this.forceUpdate()
|
||||
}}>
|
||||
<img src={Close} className="TextInputClear" />
|
||||
</div> : null
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img src={Close} className="TextInputClear" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
.Divider {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -13,4 +12,4 @@
|
||||
.DividerLine {
|
||||
width: 60%;
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,4 @@ export default class Divider extends React.Component {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,16 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.HeaderText {
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
margin-bottom: 10px;
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.DownloadValue .BigButton {
|
||||
height: 100%;
|
||||
min-height: 30px;
|
||||
@@ -28,4 +38,4 @@
|
||||
|
||||
.DownloadMenuSection .HelpButton img {
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,20 @@ import { invoke } from '@tauri-apps/api'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import HelpButton from '../common/HelpButton'
|
||||
|
||||
const FULL_BUILD_DOWNLOAD = 'https://cdn.discordapp.com/attachments/615655311960965130/1079203157294460958/GrasscutterCulti3.4.zip'
|
||||
const STABLE_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/stable.zip'
|
||||
const DEV_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/development.zip'
|
||||
const STABLE_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/stable/Grasscutter.zip'
|
||||
const DEV_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/development/Grasscutter.zip'
|
||||
const RESOURCES_DOWNLOAD = 'https://github.com/Koko-boya/Grasscutter_Resources/archive/refs/heads/main.zip'
|
||||
const RESOURCES_DOWNLOAD = 'https://gitlab.com/YuukiPS/GC-Resources/-/archive/3.4/GC-Resources-3.4.zip' // Use Yuuki res as grasscutter crepe res are broken
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void;
|
||||
downloadManager: DownloadHandler;
|
||||
closeFn: () => void
|
||||
downloadManager: DownloadHandler
|
||||
}
|
||||
|
||||
interface IState {
|
||||
fullbuild_downloading: boolean
|
||||
grasscutter_downloading: boolean
|
||||
resources_downloading: boolean
|
||||
repo_downloading: boolean
|
||||
@@ -37,14 +39,16 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
fullbuild_downloading: this.props.downloadManager.downloadingFullBuild(),
|
||||
grasscutter_downloading: this.props.downloadManager.downloadingJar(),
|
||||
resources_downloading: this.props.downloadManager.downloadingResources(),
|
||||
repo_downloading: this.props.downloadManager.downloadingRepo(),
|
||||
grasscutter_set: false,
|
||||
resources_exist: false
|
||||
resources_exist: false,
|
||||
}
|
||||
|
||||
this.getGrasscutterFolder = this.getGrasscutterFolder.bind(this)
|
||||
this.downloadGrasscutterFullBuild = this.downloadGrasscutterFullBuild.bind(this)
|
||||
this.downloadGrasscutterStableRepo = this.downloadGrasscutterStableRepo.bind(this)
|
||||
this.downloadGrasscutterDevRepo = this.downloadGrasscutterDevRepo.bind(this)
|
||||
this.downloadGrasscutterStable = this.downloadGrasscutterStable.bind(this)
|
||||
@@ -63,7 +67,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
if (!gc_path || gc_path === '') {
|
||||
this.setState({
|
||||
grasscutter_set: false,
|
||||
resources_exist: false
|
||||
resources_exist: false,
|
||||
})
|
||||
|
||||
return
|
||||
@@ -72,15 +76,17 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
const path = gc_path.substring(0, gc_path.lastIndexOf('\\'))
|
||||
|
||||
if (gc_path) {
|
||||
const resources_exist: boolean = await invoke('dir_exists', {
|
||||
path: path + '\\resources'
|
||||
}) as boolean && !(await invoke('dir_is_empty', {
|
||||
path: path + '\\resources'
|
||||
})) as boolean
|
||||
const resources_exist: boolean =
|
||||
((await invoke('dir_exists', {
|
||||
path: path + '\\resources',
|
||||
})) as boolean) &&
|
||||
(!(await invoke('dir_is_empty', {
|
||||
path: path + '\\resources',
|
||||
})) as boolean)
|
||||
|
||||
this.setState({
|
||||
grasscutter_set: gc_path !== '',
|
||||
resources_exist
|
||||
resources_exist,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -107,10 +113,21 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
return folderPath
|
||||
}
|
||||
|
||||
async downloadGrasscutterFullBuild() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(FULL_BUILD_DOWNLOAD, folder + '\\GrasscutterCulti3.4.zip', async () => {
|
||||
await unzip(folder + '\\GrasscutterCulti3.4.zip', folder + '\\', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
this.toggleButtons()
|
||||
}
|
||||
|
||||
async downloadGrasscutterStableRepo() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () =>{
|
||||
unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
|
||||
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
|
||||
await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
this.toggleButtons()
|
||||
@@ -118,8 +135,9 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterDevRepo() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () =>{
|
||||
unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
|
||||
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
|
||||
await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
this.toggleButtons()
|
||||
@@ -127,20 +145,22 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterStable() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(STABLE_DOWNLOAD, folder + '\\grasscutter.zip', () =>{
|
||||
unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
|
||||
this.props.downloadManager.addDownload(STABLE_DOWNLOAD, folder + '\\grasscutter.zip', async () => {
|
||||
await unzip(folder + '\\grasscutter.zip', folder + '\\', true)
|
||||
this.toggleButtons
|
||||
})
|
||||
|
||||
// Also add repo download
|
||||
this.downloadGrasscutterStableRepo()
|
||||
|
||||
this.toggleButtons()
|
||||
}
|
||||
}
|
||||
|
||||
async downloadGrasscutterLatest() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', () =>{
|
||||
unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
|
||||
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', async () => {
|
||||
await unzip(folder + '\\grasscutter.zip', folder + '\\', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
// Also add repo download
|
||||
@@ -152,24 +172,31 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
async downloadResources() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '\\resources.zip', async () => {
|
||||
// Delete the existing folder if it exists
|
||||
if (await invoke('dir_exists', {
|
||||
path: folder + '\\resources'
|
||||
})) {
|
||||
|
||||
// Tell the user this takes some time
|
||||
alert(
|
||||
'Extracting resources can take time! If your resources appear to be "stuck" extracting for less than 15-20 mins, they likely still are extracting.'
|
||||
)
|
||||
|
||||
// Delete the existing folder if it exists
|
||||
if (
|
||||
await invoke('dir_exists', {
|
||||
path: folder + '\\resources',
|
||||
})
|
||||
) {
|
||||
await invoke('dir_delete', {
|
||||
path: folder + '\\resources'
|
||||
path: folder + '\\resources',
|
||||
})
|
||||
}
|
||||
|
||||
await unzip(folder + '\\resources.zip', folder + '\\', () => {
|
||||
// Rename folder to resources
|
||||
invoke('rename', {
|
||||
path: folder + '\\Resources',
|
||||
newName: 'resources'
|
||||
})
|
||||
|
||||
this.toggleButtons()
|
||||
await unzip(folder + '\\resources.zip', folder + '\\', true)
|
||||
// Rename folder to resources
|
||||
invoke('rename', {
|
||||
path: folder + '\\Resources',
|
||||
newName: 'resources',
|
||||
})
|
||||
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
this.toggleButtons()
|
||||
@@ -180,6 +207,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
// Set states since we know we are downloading something if this is called
|
||||
this.setState({
|
||||
fullbuild_downloading: this.props.downloadManager.downloadingFullBuild(),
|
||||
grasscutter_downloading: this.props.downloadManager.downloadingJar(),
|
||||
resources_downloading: this.props.downloadManager.downloadingResources(),
|
||||
repo_downloading: this.props.downloadManager.downloadingRepo(),
|
||||
@@ -190,32 +218,25 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
render() {
|
||||
return (
|
||||
<Menu closeFn={this.props.closeFn} className="Downloads" heading="Downloads">
|
||||
<div className='DownloadMenuSection' id="downloadMenuContainerGCStable">
|
||||
<div className='DownloadLabel' id="downloadMenuLabelGCStable">
|
||||
<Tr text={
|
||||
this.state.grasscutter_set ? 'downloads.grasscutter_stable' : 'downloads.grasscutter_stable_update'
|
||||
} />
|
||||
<HelpButton>
|
||||
<Tr text="help.gc_stable_jar" />
|
||||
</HelpButton>
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonGCStable">
|
||||
<BigButton disabled={this.state.grasscutter_downloading} onClick={this.downloadGrasscutterStable} id="grasscutterStableBtn" >
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className="HeaderText" id="downloadMenuAIOHeader">
|
||||
<Tr text="downloads.aio_header" />
|
||||
</div>
|
||||
<div className='DownloadMenuSection' id="downloadMenuContainerGCDev">
|
||||
<div className='DownloadLabel' id="downloadMenuLabelGCDev">
|
||||
<Tr text={
|
||||
this.state.grasscutter_set ? 'downloads.grasscutter_latest' : 'downloads.grasscutter_latest_update'
|
||||
} />
|
||||
<HelpButton>
|
||||
<Tr text="help.gc_dev_jar" />
|
||||
</HelpButton>
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerGCFullBuild">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCFullBuild">
|
||||
<Tr
|
||||
text={'downloads.grasscutter_fullbuild'}
|
||||
/>
|
||||
<HelpButton contents="help.gc_fullbuild" />
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonGCDev">
|
||||
<BigButton disabled={this.state.grasscutter_downloading} onClick={this.downloadGrasscutterLatest} id="grasscutterLatestBtn" >
|
||||
<div className="DownloadValue" id="downloadMenuButtonGCFullBuild">
|
||||
<BigButton
|
||||
disabled={this.state.grasscutter_downloading}
|
||||
onClick={this.downloadGrasscutterFullBuild}
|
||||
id="grasscutterFullBuildBtn"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
@@ -223,48 +244,98 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className='DownloadMenuSection' id="downloadMenuContainerGCStableData">
|
||||
<div className='DownloadLabel' id="downloadMenuLabelGCStableData">
|
||||
<Tr text={
|
||||
this.state.grasscutter_set ? 'downloads.grasscutter_stable_data' : 'downloads.grasscutter_stable_data_update'
|
||||
} />
|
||||
<HelpButton>
|
||||
<Tr text="help.gc_stable_data" />
|
||||
</HelpButton>
|
||||
{/* <div className="DownloadMenuSection" id="downloadMenuContainerGCStable">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCStable">
|
||||
<Tr
|
||||
text={this.state.grasscutter_set ? 'downloads.grasscutter_stable' : 'downloads.grasscutter_stable_update'}
|
||||
/>
|
||||
<HelpButton contents="help.gc_stable_jar" />
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonGCStableData">
|
||||
<BigButton disabled={this.state.repo_downloading} onClick={this.downloadGrasscutterStableRepo} id="grasscutterStableRepo" >
|
||||
<div className="DownloadValue" id="downloadMenuButtonGCStable">
|
||||
<BigButton
|
||||
disabled={this.state.grasscutter_downloading}
|
||||
onClick={this.downloadGrasscutterStable}
|
||||
id="grasscutterStableBtn"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="HeaderText" id="downloadMenuIndividualHeader">
|
||||
<Tr text="downloads.individual_header" />
|
||||
</div>
|
||||
<div className='DownloadMenuSection' id="downloadMenuContainerGCDevData">
|
||||
<div className='DownloadLabel' id="downloadMenuLabelGCDevData">
|
||||
<Tr text={
|
||||
this.state.grasscutter_set ? 'downloads.grasscutter_latest_data' : 'downloads.grasscutter_latest_data_update'
|
||||
} />
|
||||
<HelpButton>
|
||||
<Tr text="help.gc_dev_data" />
|
||||
</HelpButton>
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerGCDev">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCDev">
|
||||
<Tr
|
||||
text={this.state.grasscutter_set ? 'downloads.grasscutter_latest' : 'downloads.grasscutter_latest_update'}
|
||||
/>
|
||||
<HelpButton contents="help.gc_dev_jar" />
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonGCDevData">
|
||||
<BigButton disabled={this.state.repo_downloading} onClick={this.downloadGrasscutterStableRepo} id="grasscutterDevRepo" >
|
||||
<div className="DownloadValue" id="downloadMenuButtonGCDev">
|
||||
<BigButton
|
||||
disabled={this.state.grasscutter_downloading}
|
||||
onClick={this.downloadGrasscutterLatest}
|
||||
id="grasscutterLatestBtn"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
{/* <div className="DownloadMenuSection" id="downloadMenuContainerGCStableData">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCStableData">
|
||||
<Tr
|
||||
text={
|
||||
this.state.grasscutter_set
|
||||
? 'downloads.grasscutter_stable_data'
|
||||
: 'downloads.grasscutter_stable_data_update'
|
||||
}
|
||||
/>
|
||||
<HelpButton contents="help.gc_stable_data" />
|
||||
</div>
|
||||
<div className="DownloadValue" id="downloadMenuButtonGCStableData">
|
||||
<BigButton
|
||||
disabled={this.state.repo_downloading}
|
||||
onClick={this.downloadGrasscutterStableRepo}
|
||||
id="grasscutterStableRepo"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerGCDevData">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCDevData">
|
||||
<Tr
|
||||
text={
|
||||
this.state.grasscutter_set
|
||||
? 'downloads.grasscutter_latest_data'
|
||||
: 'downloads.grasscutter_latest_data_update'
|
||||
}
|
||||
/>
|
||||
<HelpButton contents="help.gc_dev_data" />
|
||||
</div>
|
||||
<div className="DownloadValue" id="downloadMenuButtonGCDevData">
|
||||
<BigButton
|
||||
disabled={this.state.repo_downloading}
|
||||
onClick={this.downloadGrasscutterStableRepo}
|
||||
id="grasscutterDevRepo"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='DownloadMenuSection' id="downloadMenuContainerResources">
|
||||
<div className='DownloadLabel' id="downloadMenuLabelResources">
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerResources">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelResources">
|
||||
<Tr text="downloads.resources" />
|
||||
<HelpButton>
|
||||
<Tr text="help.resources" />
|
||||
</HelpButton>
|
||||
<HelpButton contents="help.resources" />
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonResources">
|
||||
<BigButton disabled={this.state.resources_downloading || !this.state.grasscutter_set || this.state.resources_exist} onClick={this.downloadResources} id="resourcesBtn" >
|
||||
<div className="DownloadValue" id="downloadMenuButtonResources">
|
||||
<BigButton
|
||||
disabled={this.state.resources_downloading || !this.state.grasscutter_set || this.state.resources_exist}
|
||||
onClick={this.downloadResources}
|
||||
id="resourcesBtn"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
@@ -272,4 +343,4 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
src/ui/components/menu/ExtrasMenu.css
Normal file
@@ -0,0 +1,32 @@
|
||||
.ExtrasMenu {
|
||||
width: 20%;
|
||||
height: 40%;
|
||||
}
|
||||
|
||||
.ExtrasMenu .MenuInner {
|
||||
justify-content: space-between;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
.ExtrasMenuContent {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ExtraItem {
|
||||
width: 80%;
|
||||
|
||||
padding: 6px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ExtraLaunch .BigButton {
|
||||
padding: 20px 50px;
|
||||
}
|
||||
179
src/ui/components/menu/ExtrasMenu.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
import React from 'react'
|
||||
import { getConfig, saveConfig } from '../../../utils/configuration'
|
||||
import Checkbox from '../common/Checkbox'
|
||||
import Menu from './Menu'
|
||||
|
||||
import './ExtrasMenu.css'
|
||||
import BigButton from '../common/BigButton'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import Tr from '../../../utils/language'
|
||||
import { getGameExecutable } from '../../../utils/game'
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode | React.ReactNode[]
|
||||
closeFn: () => void
|
||||
playGame: (exe?: string, proc_name?: string) => void
|
||||
}
|
||||
|
||||
interface IState {
|
||||
migoto?: string
|
||||
akebi?: string
|
||||
reshade?: string
|
||||
launch_migoto: boolean
|
||||
launch_akebi: boolean
|
||||
launch_reshade: boolean
|
||||
}
|
||||
|
||||
export class ExtrasMenu extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
launch_migoto: false,
|
||||
launch_akebi: false,
|
||||
launch_reshade: false,
|
||||
}
|
||||
|
||||
this.launchPreprograms = this.launchPreprograms.bind(this)
|
||||
this.toggleMigoto = this.toggleMigoto.bind(this)
|
||||
this.toggleAkebi = this.toggleAkebi.bind(this)
|
||||
this.toggleReshade = this.toggleReshade.bind(this)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const config = await getConfig()
|
||||
|
||||
this.setState({
|
||||
migoto: config.migoto_path,
|
||||
akebi: config.akebi_path,
|
||||
reshade: config.reshade_path,
|
||||
launch_akebi: config?.last_extras?.akebi ?? false,
|
||||
launch_migoto: config?.last_extras?.migoto ?? false,
|
||||
launch_reshade: config?.last_extras?.reshade ?? false,
|
||||
})
|
||||
}
|
||||
|
||||
async launchPreprograms() {
|
||||
const config = await getConfig()
|
||||
|
||||
config.last_extras = {
|
||||
migoto: this.state.launch_migoto,
|
||||
akebi: this.state.launch_akebi,
|
||||
reshade: this.state.launch_reshade,
|
||||
}
|
||||
|
||||
await saveConfig(config)
|
||||
|
||||
// Close menu
|
||||
this.props.closeFn()
|
||||
|
||||
// This injects independent of the game
|
||||
if (this.state.launch_migoto) {
|
||||
await this.launchMigoto()
|
||||
}
|
||||
|
||||
// This injects independent of the game
|
||||
if (this.state.launch_reshade) {
|
||||
await this.launchReshade()
|
||||
}
|
||||
|
||||
// This will launch the game
|
||||
if (this.state.launch_akebi) {
|
||||
await this.launchAkebi()
|
||||
|
||||
// This already launches the game
|
||||
return
|
||||
}
|
||||
|
||||
// Launch the game
|
||||
await this.props.playGame()
|
||||
}
|
||||
|
||||
async launchAkebi() {
|
||||
const config = await getConfig()
|
||||
|
||||
// Get game exe from game path, so we can watch it
|
||||
const pathArr = config.game_install_path.replace(/\\/g, '/').split('/')
|
||||
const gameExec = pathArr[pathArr.length - 1]
|
||||
|
||||
await this.props.playGame(config.akebi_path, gameExec)
|
||||
}
|
||||
|
||||
async launchMigoto() {
|
||||
const config = await getConfig()
|
||||
|
||||
if (!config.migoto_path) return alert('Migoto not installed or set!')
|
||||
|
||||
await invoke('run_program_relative', { path: config.migoto_path })
|
||||
}
|
||||
|
||||
async launchReshade() {
|
||||
const config = await getConfig()
|
||||
|
||||
if (!config.reshade_path) return alert('Reshade not installed or set!')
|
||||
|
||||
await invoke('run_command', {
|
||||
program: config.reshade_path,
|
||||
args: [await getGameExecutable()],
|
||||
relative: true,
|
||||
})
|
||||
}
|
||||
|
||||
toggleMigoto() {
|
||||
this.setState({
|
||||
launch_migoto: !this.state.launch_migoto,
|
||||
})
|
||||
}
|
||||
|
||||
toggleAkebi() {
|
||||
this.setState({
|
||||
launch_akebi: !this.state.launch_akebi,
|
||||
})
|
||||
}
|
||||
|
||||
toggleReshade() {
|
||||
this.setState({
|
||||
launch_reshade: !this.state.launch_reshade,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Menu closeFn={this.props.closeFn} heading="Extras" className="ExtrasMenu">
|
||||
<div className="ExtrasMenuContent">
|
||||
{this.state.migoto && (
|
||||
<div className="ExtraItem">
|
||||
<div className="ExtraItemLabel">
|
||||
<Tr text="swag.migoto_name" />
|
||||
</div>
|
||||
<Checkbox id="MigotoCheckbox" checked={this.state.launch_migoto} onChange={this.toggleMigoto} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{this.state.akebi && (
|
||||
<div className="ExtraItem">
|
||||
<div className="ExtraItemLabel">
|
||||
<Tr text="swag.akebi_name" />
|
||||
</div>
|
||||
<Checkbox id="AkebiCheckbox" checked={this.state.launch_akebi} onChange={this.toggleAkebi} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{this.state.reshade && (
|
||||
<div className="ExtraItem">
|
||||
<div className="ExtraItemLabel">
|
||||
<Tr text="swag.reshade_name" />
|
||||
</div>
|
||||
<Checkbox id="ReshadeCheckbox" checked={this.state.launch_reshade} onChange={this.toggleReshade} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="ExtraLaunch">
|
||||
<BigButton id="ExtraLaunch" onClick={this.launchPreprograms}>
|
||||
<Tr text="main.launch_button" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -34,4 +34,4 @@
|
||||
|
||||
.GameDownloadDir .DirInput .TextInputWrapper input {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import Menu from './Menu'
|
||||
import Tr, { translate } from '../../../utils/language'
|
||||
import { translate } from '../../../utils/language'
|
||||
import DownloadHandler from '../../../utils/download'
|
||||
|
||||
import './Game.css'
|
||||
@@ -12,14 +12,14 @@ import { unzip } from '../../../utils/zipUtils'
|
||||
const GAME_DOWNLOAD = ''
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void;
|
||||
downloadManager: DownloadHandler;
|
||||
closeFn: () => void
|
||||
downloadManager: DownloadHandler
|
||||
}
|
||||
|
||||
interface IState {
|
||||
gameDownloading: boolean;
|
||||
gameDownloadFolder: string;
|
||||
dirPlaceholder: string;
|
||||
gameDownloading: boolean
|
||||
gameDownloadFolder: string
|
||||
dirPlaceholder: string
|
||||
}
|
||||
|
||||
export default class Downloads extends React.Component<IProps, IState> {
|
||||
@@ -29,7 +29,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
this.state = {
|
||||
gameDownloading: false,
|
||||
gameDownloadFolder: '',
|
||||
dirPlaceholder: ''
|
||||
dirPlaceholder: '',
|
||||
}
|
||||
|
||||
this.downloadGame = this.downloadGame.bind(this)
|
||||
@@ -37,7 +37,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async componentDidMount() {
|
||||
this.setState({
|
||||
dirPlaceholder: await translate('components.select_folder')
|
||||
dirPlaceholder: await translate('components.select_folder'),
|
||||
})
|
||||
|
||||
console.log(this.state)
|
||||
@@ -45,39 +45,48 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGame() {
|
||||
const folder = this.state.gameDownloadFolder
|
||||
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', () =>{
|
||||
unzip(folder + '\\game.zip', folder + '\\', () => {
|
||||
this.setState({
|
||||
gameDownloading: false
|
||||
})
|
||||
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', async () => {
|
||||
await unzip(folder + '\\game.zip', folder + '\\', true)
|
||||
this.setState({
|
||||
gameDownloading: false,
|
||||
})
|
||||
})
|
||||
|
||||
this.setState({
|
||||
gameDownloading: true
|
||||
gameDownloading: true,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Menu heading='Download Game' closeFn={this.props.closeFn} className="GameDownloadMenu">
|
||||
<Menu heading="Download Game" closeFn={this.props.closeFn} className="GameDownloadMenu">
|
||||
<div className="GameDownload">
|
||||
{
|
||||
this.state.gameDownloadFolder !== '' && !this.state.gameDownloading ?
|
||||
<BigButton id="downloadGameBtn" onClick={this.downloadGame}>Download Game</BigButton>
|
||||
: <BigButton id="disabledGameBtn" onClick={() => null} disabled>Download Game</BigButton>
|
||||
}
|
||||
<HelpButton>
|
||||
<Tr text="main.game_help_text" />
|
||||
</HelpButton>
|
||||
{this.state.gameDownloadFolder !== '' && !this.state.gameDownloading ? (
|
||||
<BigButton id="downloadGameBtn" onClick={this.downloadGame}>
|
||||
Download Game
|
||||
</BigButton>
|
||||
) : (
|
||||
<BigButton id="disabledGameBtn" onClick={() => null} disabled>
|
||||
Download Game
|
||||
</BigButton>
|
||||
)}
|
||||
<HelpButton contents="main.game_help_text" />
|
||||
</div>
|
||||
|
||||
|
||||
<div className="GameDownloadDir">
|
||||
<DirInput folder placeholder={this.state.dirPlaceholder} clearable={false} readonly={true} onChange={(value: string) => this.setState({
|
||||
gameDownloadFolder: value
|
||||
})}/>
|
||||
<DirInput
|
||||
folder
|
||||
placeholder={this.state.dirPlaceholder}
|
||||
clearable={false}
|
||||
readonly={true}
|
||||
onChange={(value: string) =>
|
||||
this.setState({
|
||||
gameDownloadFolder: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
|
||||
|
||||
box-shadow: 0px 0px 5px 3px rgba(0, 0, 0, 0.2);
|
||||
|
||||
overflow-y: auto;
|
||||
@@ -52,4 +52,4 @@
|
||||
|
||||
.MenuExit img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||