Compare commits
142 Commits
v1.0.2-alp
...
mod_manage
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f8e37aff3 | ||
|
|
6257a2e68c | ||
|
|
203bd40e8f | ||
|
|
c64cdababa | ||
|
|
1ecd38ee9f | ||
|
|
26292984a2 | ||
|
|
cffbcdae96 | ||
|
|
2026e2f896 | ||
|
|
9566beaf29 | ||
|
|
c99080168c | ||
|
|
9fa3351747 | ||
|
|
95282a3f36 | ||
|
|
0331bb5faf | ||
|
|
0551f3e6a0 | ||
|
|
412acdd317 | ||
|
|
c42c708db5 | ||
|
|
552d612e7c | ||
|
|
5077c19fdc | ||
|
|
36c2302f1b | ||
|
|
975b04fd0e | ||
|
|
811f437238 | ||
|
|
a25645ef77 | ||
|
|
ac8fd3af45 | ||
|
|
4f3952aeb1 | ||
|
|
c0740417e3 | ||
|
|
8700a77ba0 | ||
|
|
95267720a4 | ||
|
|
d97e5c192f | ||
|
|
a9d9d361e1 | ||
|
|
b78d9c28c9 | ||
|
|
f946cedb4d | ||
|
|
c659979851 | ||
|
|
940943b106 | ||
|
|
7ad1c4649c | ||
|
|
011b15c8d9 | ||
|
|
d28af907ec | ||
|
|
1b076ccea9 | ||
|
|
818896c734 | ||
|
|
3b6225d5f0 | ||
|
|
041a6cb768 | ||
|
|
acb6de85ad | ||
|
|
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 | ||
|
|
d46a2bdc44 | ||
|
|
bb383c5c0a | ||
|
|
4b42d0f8b8 | ||
|
|
3d0f8a3ff6 | ||
|
|
56b0c78661 | ||
|
|
ffc37cc203 | ||
|
|
8faaba2849 | ||
|
|
9d86d9c9ff | ||
|
|
8ff1a47fff | ||
|
|
12da09596d | ||
|
|
bd54a78e4b | ||
|
|
bb74553bee | ||
|
|
d373f46615 | ||
|
|
a1b0fec871 | ||
|
|
bae193050f | ||
|
|
fc5ffae1e2 | ||
|
|
ab0e05ffe1 | ||
|
|
88a1740b91 | ||
|
|
f24f3af377 | ||
|
|
9bdb18d4d6 | ||
|
|
7cbb600a7f | ||
|
|
33c733ce97 | ||
|
|
abafc94379 | ||
|
|
411e11dd8d | ||
|
|
b2453e7c4d | ||
|
|
30476a86ad | ||
|
|
375e15e947 | ||
|
|
fd87adc1f6 | ||
|
|
b903c27a22 | ||
|
|
5bf7019482 | ||
|
|
be633eeea5 | ||
|
|
58e683c669 | ||
|
|
cd5c2985e5 | ||
|
|
e41a89b26c | ||
|
|
4d05063b61 | ||
|
|
53e2b0cbed | ||
|
|
51d00add22 | ||
|
|
4fc90ee333 | ||
|
|
0ec8782f48 | ||
|
|
33c67eef06 | ||
|
|
a703843eed | ||
|
|
ba2a8b7fec | ||
|
|
b3585927ca | ||
|
|
d4e284663e | ||
|
|
065043bbe9 | ||
|
|
2c3a23e841 | ||
|
|
f35b596eb2 | ||
|
|
3008f50e1f | ||
|
|
99293ad7cf | ||
|
|
99687f0550 | ||
|
|
6124d6949c | ||
|
|
dd56af8fcb | ||
|
|
487b36a37e | ||
|
|
850b282b70 | ||
|
|
e0272aa38a | ||
|
|
27d7c32a73 | ||
|
|
936c533ff8 |
@@ -2,20 +2,9 @@ root = true
|
|||||||
|
|
||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = crlf
|
end_of_line = lf
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
insert_final_newline = false
|
|
||||||
max_line_length = 120
|
max_line_length = 120
|
||||||
tab_width = 2
|
tab_width = 2
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.rs]
|
|
||||||
max_line_length = 100
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
[{*.ats,*.cts,*.mts,*.ts}]
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
[*.json]
|
|
||||||
indent_size = 2
|
|
||||||
@@ -1,38 +1,40 @@
|
|||||||
{
|
{
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"es2021": true,
|
"es2021": true,
|
||||||
"node": true
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", "prettier"],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true
|
||||||
},
|
},
|
||||||
"extends": [
|
"ecmaVersion": "latest",
|
||||||
"eslint:recommended",
|
"sourceType": "module"
|
||||||
"plugin:react/recommended",
|
},
|
||||||
"plugin:@typescript-eslint/recommended"
|
"plugins": ["react", "@typescript-eslint"],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/ban-types": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"extendDefaults": true,
|
||||||
|
"types": {
|
||||||
|
"{}": false
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"parser": "@typescript-eslint/parser",
|
"@typescript-eslint/no-unused-vars": [
|
||||||
"parserOptions": {
|
"warn",
|
||||||
"ecmaFeatures": {
|
{
|
||||||
"jsx": true
|
"argsIgnorePattern": "^_",
|
||||||
},
|
"varsIgnorePattern": "^_"
|
||||||
"ecmaVersion": "latest",
|
}
|
||||||
"sourceType": "module"
|
]
|
||||||
},
|
},
|
||||||
"plugins": [
|
"settings": {
|
||||||
"react",
|
"react": {
|
||||||
"@typescript-eslint"
|
"version": "detect"
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"indent": [
|
|
||||||
"error",
|
|
||||||
2
|
|
||||||
],
|
|
||||||
"quotes": [
|
|
||||||
"error",
|
|
||||||
"single"
|
|
||||||
],
|
|
||||||
"semi": [
|
|
||||||
"error",
|
|
||||||
"never"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
61
.github/workflows/backend-checks.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: Check backend
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/backend-checks.yml'
|
||||||
|
- 'src-tauri/**'
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/backend-checks.yml'
|
||||||
|
- 'src-tauri/**'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.ref }}-${{ github.workflow }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
rustfmt:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: --manifest-path ./src-tauri/Cargo.toml --all -- --check
|
||||||
|
|
||||||
|
clippy:
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform: [windows-latest, ubuntu-latest, macos-latest]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install Linux dependencies
|
||||||
|
if: matrix.platform == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libwebkit2gtk-4.0-dev \
|
||||||
|
build-essential \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
libssl-dev \
|
||||||
|
libgtk-3-dev \
|
||||||
|
libayatana-appindicator3-dev \
|
||||||
|
librsvg2-dev
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v1
|
||||||
|
with:
|
||||||
|
working-directory: src-tauri
|
||||||
|
|
||||||
|
- uses: actions-rs/clippy-check@v1
|
||||||
|
with:
|
||||||
|
name: clippy (${{ runner.os }})
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
args: --manifest-path ./src-tauri/Cargo.toml --no-default-features -- -D warnings
|
||||||
32
.github/workflows/frontend-checks.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Check frontend
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- '**.lock'
|
||||||
|
- '**.rs'
|
||||||
|
- '**.toml'
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- '**.lock'
|
||||||
|
- '**.rs'
|
||||||
|
- '**.toml'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.ref }}-${{ github.workflow }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
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
|
||||||
3
.gitignore
vendored
@@ -23,4 +23,5 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
# moved lang files
|
# moved lang files
|
||||||
/lang
|
/lang
|
||||||
|
package-lock.json
|
||||||
|
|||||||
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
yarn lint-staged
|
||||||
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
|
||||||
|
}
|
||||||
87
README.md
@@ -1,29 +1,28 @@
|
|||||||
# NOTICE
|
# Client Patching Notice
|
||||||
Yes! The Cultivation repository is **open**. This does **not** mean it has released.\
|
|
||||||
Cultivation will be releasing at some point after opening this repo.
|
|
||||||
|
|
||||||
**This also means you will not be provided explicit support for Cultivation.**\
|
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.
|
||||||
Consider Cultivation to be the bleeding-edge version of GrassClipper.
|
|
||||||
|
|
||||||
During this open-beta testing period, **helpful issues are appreciated**, while unhelpful ones will be closed.
|
|
||||||
|
|
||||||
## Fair Warning
|
|
||||||
Cultivation is **VERY MUCH IN BETA**.
|
|
||||||
There are **no official releases of Cultivation**. You are **required** to build the application from **scratch** unless you want to deal with the alpha state of the current builds.
|
|
||||||
Please do **NOT install, download, or use pre-compiled versions of Cultivation found elsewhere**. Only use releases from this GitHub repository.
|
|
||||||
|
|
||||||
# Cultivation
|
# Cultivation
|
||||||
|
|
||||||
A game launcher designed to easily proxy traffic from anime game to private servers.
|
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
|
# Table Of Contents
|
||||||
* [Download](#download)
|
|
||||||
* [Developer Quick-start](#developer-quickstart)
|
- [Download](#download)
|
||||||
* [Setup](#setup)
|
- [Developer Quick-start](#developer-quickstart)
|
||||||
* [Building](#building)
|
- [Setup](#setup)
|
||||||
* [Troubleshooting](#troubleshooting)
|
- [Building](#building)
|
||||||
* [Theming](#theming)
|
- [Code Formatting and Linting](#code-formatting-and-linting)
|
||||||
|
- [Generating Update Artifacts](#generating-update-artifacts)
|
||||||
|
- [Theming](#theming)
|
||||||
|
- [Screenshots](#screenshots)
|
||||||
|
- [Credits](#credits)
|
||||||
|
|
||||||
# Download
|
# Download
|
||||||
|
|
||||||
[Find release builds here!](https://github.com/Grasscutters/Cultivation/releases)
|
[Find release builds here!](https://github.com/Grasscutters/Cultivation/releases)
|
||||||
|
|
||||||
Once downloaded, extract somewhere and open as administrator.
|
Once downloaded, extract somewhere and open as administrator.
|
||||||
@@ -31,30 +30,47 @@ Once downloaded, extract somewhere and open as administrator.
|
|||||||
# Developer Quickstart
|
# Developer Quickstart
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
* Install [NodeJS >12](https://nodejs.org/en/)
|
|
||||||
* Install [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) & [Rust compiler](https://www.rust-lang.org/tools/install)
|
- Install [NodeJS >12](https://nodejs.org/en/)
|
||||||
* `npm install` or `yarn install`
|
- Install [yarn](https://classic.yarnpkg.com/lang/en/docs/install) (cry about it `npm` lovers)
|
||||||
* `npm run start:dev` or `yarn start:dev`
|
- Install [Rust](https://www.rust-lang.org/tools/install)
|
||||||
|
- `yarn install`
|
||||||
|
- `yarn start:dev`
|
||||||
|
|
||||||
### Building
|
### 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,
|
||||||
|
|
||||||
### Updating
|
- `yarn build`
|
||||||
* 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`
|
|
||||||
|
|
||||||
# Troubleshooting
|
For a debug build,
|
||||||
TODO. Collect common issues before updating.
|
|
||||||
|
- `yarn build --debug`
|
||||||
|
|
||||||
|
### Code Formatting and Linting
|
||||||
|
|
||||||
|
Formatting:
|
||||||
|
|
||||||
|
- `yarn format`
|
||||||
|
|
||||||
|
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
|
# Theming
|
||||||
|
|
||||||
A full theming reference can be found [here!](/THEMES.md)
|
A full theming reference can be found [here!](/THEMES.md)
|
||||||
|
|
||||||
# Screenshots
|
# Screenshots
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
@@ -62,6 +78,9 @@ A full theming reference can be found [here!](/THEMES.md)
|
|||||||

|

|
||||||
|
|
||||||
## Credits
|
## 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.
|
- [SpikeHD](https://github.com/SpikeHD): For originally creating **GrassClipper** and creating the amazing UI of Cultivation.
|
||||||
* [Tauri](https://tauri.app): For providing an amazing, efficient, and simple desktop application framework/library.
|
- [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)
|
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`)
|
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
|
# 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:
|
`index.json` is where you tell Cultivation which files and images to include. It supports the following properties:
|
||||||
|
|
||||||
| Property | Description |
|
| Property | Description |
|
||||||
| :--- | :--- |
|
| :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `name` | The name of the theme. |
|
| `name` | The name of the theme. |
|
||||||
| `version` | Not shown anywhere, the version of the theme. |
|
| `version` | Not shown anywhere, the version of the theme. |
|
||||||
| `description` | Not shown anywhere, the description of the theme. |
|
| `description` | Not shown anywhere, the description of the theme. |
|
||||||
| `includes` | The files and folders to include. |
|
| `includes` | The files and folders to include. |
|
||||||
| `includes.css` | Array of CSS files to include. Example: `css: ["index.css"]` |
|
| `includes.css` | Array of CSS files to include. Example: `css: ["index.css"]` |
|
||||||
| `includes.js` | Array of JS files to includes. Example `js: ["index.js"]` |
|
| `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"` |
|
| `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"` |
|
| `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:
|
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
|
```css
|
||||||
/* Change the font */
|
/* Change the font */
|
||||||
body {
|
body {
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif !important;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```css
|
```css
|
||||||
/* Remove the news section */
|
/* Remove the news section */
|
||||||
.NewsSection {
|
.NewsSection {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```css
|
```css
|
||||||
/* Change the right bar width */
|
/* Change the right bar width */
|
||||||
.RightBar {
|
.RightBar {
|
||||||
@@ -72,6 +74,7 @@ body {
|
|||||||
```
|
```
|
||||||
|
|
||||||
## How can I change XYZ element?
|
## 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.
|
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
|
## Writing your JS
|
||||||
@@ -83,24 +86,26 @@ Below are some examples of what you can do:
|
|||||||
```js
|
```js
|
||||||
/* Change the version number every 500ms */
|
/* Change the version number every 500ms */
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
document.getElementById("version").innerHTML = "v" + Math.floor(Math.random() * 100);
|
document.getElementById('version').innerHTML = 'v' + Math.floor(Math.random() * 100)
|
||||||
}, 500);
|
}, 500)
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
/* Load a custom font */
|
/* Load a custom font */
|
||||||
const head = document.head
|
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.href = 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap'
|
||||||
link.rel = "stylesheet"
|
link.rel = 'stylesheet'
|
||||||
link.type = "text/css"
|
link.type = 'text/css'
|
||||||
|
|
||||||
head.appendChild(link)
|
head.appendChild(link)
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
/* Create a new button that does nothing */
|
/* Create a new button that does nothing */
|
||||||
const newButton = document.createElement("button");
|
const newButton = document.createElement('button')
|
||||||
newButton.innerHTML = "New 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
|
# Documentation of Element ID's and Classes for custom theming
|
||||||
|
|
||||||
## IDs
|
## 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.
|
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 |
|
| #ID | Description |
|
||||||
|----------------------------------------|-----------------------------------------------------------------|
|
| ------------------------------------ | --------------------------------------------------------------- |
|
||||||
| `#miniDialogContainer` | Main container of MiniDialog |
|
| `#miniDialogContainer` | Main container of MiniDialog |
|
||||||
| `#miniDialogContainerTop` | Affects only top section of MiniDialog |
|
| `#miniDialogContainerTop` | Affects only top section of MiniDialog |
|
||||||
| `#miniDialogButtonClose` | Close button (SVG) of MiniDialog |
|
| `#miniDialogButtonClose` | Close button (SVG) of MiniDialog |
|
||||||
| `#miniDialogContent` | MiniDialog content |
|
| `#miniDialogContent` | MiniDialog content |
|
||||||
| `#rightBarContainer` | Main container of RightBar |
|
| `#rightBarContainer` | Main container of RightBar |
|
||||||
| `#rightBarContent` | RightBar content |
|
| `#rightBarContent` | RightBar content |
|
||||||
| `#rightBarButtonDiscord` | Discord button on the RightBar |
|
| `#rightBarButtonDiscord` | Discord button on the RightBar |
|
||||||
| `#rightBarButtonGithub` | Github button on the RightBar |
|
| `#rightBarButtonGithub` | Github button on the RightBar |
|
||||||
| `#playButton` | Main container for whole launch buttons section |
|
| `#playButton` | Main container for whole launch buttons section |
|
||||||
| `#serverControls` | Container of "play on grasscutter" checkbox |
|
| `#serverControls` | Container of "play on grasscutter" checkbox |
|
||||||
| `#enableGC` | "play on grasscutter" checkbox |
|
| `#enableGC` | "play on grasscutter" checkbox |
|
||||||
| `#ip` | Server ip input if play on grasscutter is enabled |
|
| `#ip` | Server ip input if play on grasscutter is enabled |
|
||||||
| `#port` | Server port 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 |
|
| `#httpsEnable` | "Enable https" checkbox if play on grasscutter is enabled |
|
||||||
| `#officialPlay` | Launch button |
|
| `#officialPlay` | Launch button |
|
||||||
| `#serverLaunch` | Launch server button |
|
| `#serverLaunch` | Launch server button |
|
||||||
| `#serverlaunchIcon` | Icon (SVG) of server launch button |
|
| `#serverlaunchIcon` | Icon (SVG) of server launch button |
|
||||||
| `#serverConfigContainer` | Main container of server configuration section |
|
| `#serverConfigContainer` | Main container of server configuration section |
|
||||||
| `#serverLaunchContainer` | Main container of launch buttons (includes launch server) |
|
| `#serverLaunchContainer` | Main container of launch buttons (includes launch server) |
|
||||||
| `#topBarContainer` | Main container of launcher TopBar (minimize, exit, settings...) |
|
| `#topBarContainer` | Main container of launcher TopBar (minimize, exit, settings...) |
|
||||||
| `#title` | Title of the TopBar |
|
| `#title` | Title of the TopBar |
|
||||||
| `#version` | Version of the launcher in TopBar |
|
| `#version` | Version of the launcher in TopBar |
|
||||||
| `#topBarButtonContainer` | Container of launcher TopBar buttons only |
|
| `#topBarButtonContainer` | Container of launcher TopBar buttons only |
|
||||||
| `#closeBtn` | Exit launcher button |
|
| `#closeBtn` | Exit launcher button |
|
||||||
| `#minBtn` | Minimize launcher button |
|
| `#minBtn` | Minimize launcher button |
|
||||||
| `#settingsBtn` | Settings button |
|
| `#settingsBtn` | Settings button |
|
||||||
| `#downloadsBtn` | Downloads button (grasscutter resources, grasscutter...) |
|
| `#downloadsBtn` | Downloads button (grasscutter resources, grasscutter...) |
|
||||||
| `#newsContainer` | Main container of the news section |
|
| `#newsContainer` | Main container of the news section |
|
||||||
| `#newsTabsContainer` | Container for news tabs |
|
| `#newsTabsContainer` | Container for news tabs |
|
||||||
| `#commits` | News tabs container commits button |
|
| `#commits` | News tabs container commits button |
|
||||||
| `#latest_version` | News tabs for latest version button |
|
| `#latest_version` | News tabs for latest version button |
|
||||||
| `#newsContent` | Content section of news container |
|
| `#newsContent` | Content section of news container |
|
||||||
| `#newsCommitsTable` | Commits table of news section |
|
| `#newsCommitsTable` | Commits table of news section |
|
||||||
| `#downloadMenuContainerGCStable` | Grasscutter stable update container |
|
| `#downloadMenuContainerGCStable` | Grasscutter stable update container |
|
||||||
| `#downloadMenuLabelGCStable` | Label for stable update button |
|
| `#downloadMenuLabelGCStable` | Label for stable update button |
|
||||||
| `#downloadMenuButtonGCStable` | Button container for stable update button |
|
| `#downloadMenuButtonGCStable` | Button container for stable update button |
|
||||||
| `#grasscutterStableBtn` | "Update grasscutter stable" button |
|
| `#grasscutterStableBtn` | "Update grasscutter stable" button |
|
||||||
| `#downloadMenuContainerGCDev` | Grasscutter development update container |
|
| `#downloadMenuContainerGCDev` | Grasscutter development update container |
|
||||||
| `#downloadMenuLabelGCDev` | Label for latest update button |
|
| `#downloadMenuLabelGCDev` | Label for latest update button |
|
||||||
| `#downloadMenuButtonGCDev` | Button container for latest update button |
|
| `#downloadMenuButtonGCDev` | Button container for latest update button |
|
||||||
| `grasscutterLatestBtn` | "Update grasscutter latest" button |
|
| `grasscutterLatestBtn` | "Update grasscutter latest" button |
|
||||||
| `#downloadMenuContainerGCStableData` | Grasscutter stable data update container |
|
| `#downloadMenuContainerGCStableData` | Grasscutter stable data update container |
|
||||||
| `#downloadMenuLabelGCStableData` | Label for stable data update |
|
| `#downloadMenuLabelGCStableData` | Label for stable data update |
|
||||||
| `#downloadMenuButtonGCStableData` | Button container for stable data update button |
|
| `#downloadMenuButtonGCStableData` | Button container for stable data update button |
|
||||||
| `#grasscutterStableRepo` | "Update grasscutter stable data" button |
|
| `#grasscutterStableRepo` | "Update grasscutter stable data" button |
|
||||||
| `#downloadMenuContainerGCDevData` | Grasscutter latest data update container |
|
| `#downloadMenuContainerGCDevData` | Grasscutter latest data update container |
|
||||||
| `#downloadMenuLabelGCDevData` | Label for latest data update |
|
| `#downloadMenuLabelGCDevData` | Label for latest data update |
|
||||||
| `#downloadMenuButtonGCDevData` | Button container for latest data update button |
|
| `#downloadMenuButtonGCDevData` | Button container for latest data update button |
|
||||||
| `#grasscutterDevRepo` | "Update grasscutter latest data" button |
|
| `#grasscutterDevRepo` | "Update grasscutter latest data" button |
|
||||||
| `#downloadMenuContainerResources` | Container for grasscutter resources download |
|
| `#downloadMenuContainerResources` | Container for grasscutter resources download |
|
||||||
| `#downloadMenuLabelResources` | label for resources download |
|
| `#downloadMenuLabelResources` | label for resources download |
|
||||||
| `#downloadMenuButtonResources` | Button container for resources download button |
|
| `#downloadMenuButtonResources` | Button container for resources download button |
|
||||||
| `#resourcesBtn` | "Download grasscutter resources" button |
|
| `#resourcesBtn` | "Download grasscutter resources" button |
|
||||||
| `#menuContainer` | Generic Popup modal like menu container |
|
| `#menuContainer` | Generic Popup modal like menu container |
|
||||||
| `#menuContainerTop` | Top section of menu container |
|
| `#menuContainerTop` | Top section of menu container |
|
||||||
| `#menuHeading` | Menu title |
|
| `#menuHeading` | Menu title |
|
||||||
| `#menuButtonCloseContainer` | Container for menu close button |
|
| `#menuButtonCloseContainer` | Container for menu close button |
|
||||||
| `#menuButtonCloseIcon` | Menu close icon (SVG) |
|
| `#menuButtonCloseIcon` | Menu close icon (SVG) |
|
||||||
| `#menuContent` | Content section of the menu |
|
| `#menuContent` | Content section of the menu |
|
||||||
| `#menuOptionsContainerGameExec` | Container for game executable option section |
|
| `#menuOptionsContainerGameExec` | Container for game executable option section |
|
||||||
| `#menuOptionsLabelGameExec` | Label for game executable option |
|
| `#menuOptionsLabelGameExec` | Label for game executable option |
|
||||||
| `#menuOptionsDirGameExec` | Set game executable file browser |
|
| `#menuOptionsDirGameExec` | Set game executable file browser |
|
||||||
| `#menuOptionsContainerGCJar` | Container for grasscutter jar option |
|
| `#menuOptionsContainerGCJar` | Container for grasscutter jar option |
|
||||||
| `#menuOptionsLabelGCJar` | Label for grasscutter jar option |
|
| `#menuOptionsLabelGCJar` | Label for grasscutter jar option |
|
||||||
| `#menuOptionsDirGCJar` | Set grasscutter jar file browser |
|
| `#menuOptionsDirGCJar` | Set grasscutter jar file browser |
|
||||||
| `#menuOptionsContainerToggleEnc` | Container for toggle encryption option |
|
| `#menuOptionsContainerToggleEnc` | Container for toggle encryption option |
|
||||||
| `#menuOptionsLabelToggleEnc` | Label for toggle encryption option |
|
| `#menuOptionsLabelToggleEnc` | Label for toggle encryption option |
|
||||||
| `#menuOptionsButtonToggleEnc` | Toggle encryption button container |
|
| `#menuOptionsButtonToggleEnc` | Toggle encryption button container |
|
||||||
| `#toggleEnc` | Toggle encryption button |
|
| `#toggleEnc` | Toggle encryption button |
|
||||||
| `#menuOptionsContainerGCWGame` | Container for "grasscutter with game" option |
|
| `#menuOptionsContainerGCWGame` | Container for "grasscutter with game" option |
|
||||||
| `#menuOptionsLabelGCWDame` | Label for "grasscutter with game" option |
|
| `#menuOptionsLabelGCWDame` | Label for "grasscutter with game" option |
|
||||||
| `#menuOptionsCheckboxGCWGame` | Container for "grasscutter with game" option checkbox |
|
| `#menuOptionsCheckboxGCWGame` | Container for "grasscutter with game" option checkbox |
|
||||||
| `#gcWithGame` | Grasscutter with game checkbox |
|
| `#gcWithGame` | Grasscutter with game checkbox |
|
||||||
| `#menuOptionsContainerThemes` | Container for themes section |
|
| `#menuOptionsContainerThemes` | Container for themes section |
|
||||||
| `#menuOptionsLabelThemes` | Label for set themes option |
|
| `#menuOptionsLabelThemes` | Label for set themes option |
|
||||||
| `#menuOptionsSelectThemes` | Container for themes select menu |
|
| `#menuOptionsSelectThemes` | Container for themes select menu |
|
||||||
| `#menuOptionsSelectMenuThemes` | Set theme select menu |
|
| `#menuOptionsSelectMenuThemes` | Set theme select menu |
|
||||||
| `#menuOptionsContainerJavaPath` | Container for Java Path option |
|
| `#menuOptionsContainerJavaPath` | Container for Java Path option |
|
||||||
| `#menuOptionsLabelJavaPath` | Label for Java path option |
|
| `#menuOptionsLabelJavaPath` | Label for Java path option |
|
||||||
| `#menuOptionsDirJavaPath` | Container for java path file browser |
|
| `#menuOptionsDirJavaPath` | Container for java path file browser |
|
||||||
| `#menuOptionsContainerBG` | Container for Background option |
|
| `#menuOptionsContainerBG` | Container for Background option |
|
||||||
| `#menuOptionsLabelBG` | Label for background option |
|
| `#menuOptionsLabelBG` | Label for background option |
|
||||||
| `#menuOptionsDirBG` | Container for background url/local path option |
|
| `#menuOptionsDirBG` | Container for background url/local path option |
|
||||||
| `#menuOptionsContainerLang` | Container for language change option |
|
| `#menuOptionsContainerLang` | Container for language change option |
|
||||||
| `#menuOptionsLabelLang` | Label for language change option |
|
| `#menuOptionsLabelLang` | Label for language change option |
|
||||||
| `#menuOptionsSelectLang` | Container for language change select menu |
|
| `#menuOptionsSelectLang` | Container for language change select menu |
|
||||||
| `#menuOptionsSelectMenuLang` | Language select menu |
|
| `#menuOptionsSelectMenuLang` | Language select menu |
|
||||||
| `#DownloadProgress` | Download progress container |
|
| `#DownloadProgress` | Download progress container |
|
||||||
| `#bottomSectionContainer` | Bottom section container |
|
| `#bottomSectionContainer` | Bottom section container |
|
||||||
| `#miniDownloadContainer` | Container for mini download |
|
| `#miniDownloadContainer` | Container for mini download |
|
||||||
|
|
||||||
## Classes
|
## 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.
|
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 |
|
| .Class | Description |
|
||||||
|-----------------------------|---------------------------------------------------------|
|
| ------------------------- | ------------------------------------------------------- |
|
||||||
| `.BigButton` | Class for all buttons |
|
| `.BigButton` | Class for all buttons |
|
||||||
| `.BigButtonText` | Text inside a button | |
|
| `.BigButtonText` | Text inside a button |
|
||||||
| `.Checkbox` | Checkbox container |
|
| `.Checkbox` | Checkbox container |
|
||||||
| `.CheckboxDisplay` | Content of checkbox |
|
| `.CheckboxDisplay` | Content of checkbox |
|
||||||
| `.DirInput` | Container for DirInput |
|
| `.DirInput` | Container for DirInput |
|
||||||
| `.FileSelectIcon` | Icon of DirInput |
|
| `.FileSelectIcon` | Icon of DirInput |
|
||||||
| `.DownloadList` | List of all downloads |
|
| `.DownloadList` | List of all downloads |
|
||||||
| `.DownloadSection` | Container for each download |
|
| `.DownloadSection` | Container for each download |
|
||||||
| `.DownloadTitle` | Contains file download path and current status |
|
| `.DownloadTitle` | Contains file download path and current status |
|
||||||
| `.DownloadPath` | Path of a download |
|
| `.DownloadPath` | Path of a download |
|
||||||
| `.DownloadStatus` | Status of a download |
|
| `.DownloadStatus` | Status of a download |
|
||||||
| `.DownloadSectionInner` | Contains progressbar of the download section |
|
| `.DownloadSectionInner` | Contains progressbar of the download section |
|
||||||
| `.HelpSection` | Container for help "?" circle button |
|
| `.HelpSection` | Container for help "?" circle button |
|
||||||
| `.HelpButton` | HelpButton itself |
|
| `.HelpButton` | HelpButton itself |
|
||||||
| `.HelpContents` | Content of help button once expanded |
|
| `.HelpContents` | Content of help button once expanded |
|
||||||
| `.MainProgressBarWrapper` | Container for MainProgressBar |
|
| `.MainProgressBarWrapper` | Container for MainProgressBar |
|
||||||
| `.ProgressBar` | ProgressBar (creativity left the brain) |
|
| `.ProgressBar` | ProgressBar (creativity left the brain) |
|
||||||
| `.InnerProgress` | ProgressBar percentage |
|
| `.InnerProgress` | ProgressBar percentage |
|
||||||
| `.MainProgressText` | Text for MainProgressBar |
|
| `.MainProgressText` | Text for MainProgressBar |
|
||||||
| `.ProgressBarWrapper` | Container for ProgressBar |
|
| `.ProgressBarWrapper` | Container for ProgressBar |
|
||||||
| `.DownloadControls` | DownloadControls of ProgressBar |
|
| `.DownloadControls` | DownloadControls of ProgressBar |
|
||||||
| `.downloadStop` | Container for download stop icon (SVG) |
|
| `.downloadStop` | Container for download stop icon (SVG) |
|
||||||
| `.ProgressText` | Text of the ProgressBar display current download status |
|
| `.ProgressText` | Text of the ProgressBar display current download status |
|
||||||
| `.TextInputWrapper` | Container for TextInput |
|
| `.TextInputWrapper` | Container for TextInput |
|
||||||
| `.TextClear` | Container for clear input content button |
|
| `.TextClear` | Container for clear input content button |
|
||||||
| `.TextInputClear` | TextInput clear button icon (SVG) |
|
| `.TextInputClear` | TextInput clear button icon (SVG) |
|
||||||
| `.Divider` | Container for line dividers |
|
| `.Divider` | Container for line dividers |
|
||||||
| `.DividerLine` | Divider line itself |
|
| `.DividerLine` | Divider line itself |
|
||||||
| `.CommitAuthor` | Author of a commit |
|
| `.CommitAuthor` | Author of a commit |
|
||||||
| `.CommitMessage` | Message of a commit |
|
| `.CommitMessage` | Message of a commit |
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
# Troubleshooting
|
# Troubleshooting
|
||||||
|
|
||||||
A guide dedicated for trying to troubleshoot Cultivation.
|
A guide dedicated for trying to troubleshoot Cultivation.
|
||||||
|
|
||||||
## The launcher doesn't appear to open.
|
## The launcher doesn't appear to open.
|
||||||
|
|
||||||
Try running the launcher with **administrative privileges**.\
|
Try running the launcher with **administrative privileges**.\
|
||||||
If this fixes your issue, you can force enable it in the **Compatability**\
|
If this fixes your issue, you can force enable it in the **Compatability**\
|
||||||
tab for the launcher's executable.
|
tab for the launcher's executable.
|
||||||
|
|
||||||
## Unable to play on `localhost`.
|
## Unable to play on `localhost`.
|
||||||
|
|
||||||
Make sure your server is running with **encryption disabled** and `useInRouting` to **false**.\
|
Make sure your server is running with **encryption disabled** and `useInRouting` to **false**.\
|
||||||
Additionally, make sure Cultivation **is set to not use HTTPS**.
|
Additionally, make sure Cultivation **is set to not use HTTPS**.
|
||||||
|
|
||||||
## "I can't do anything requiring the internet after closing Cultivation!"
|
## "I can't do anything requiring the internet after closing Cultivation!"
|
||||||
|
|
||||||
You probably didn't close Cultivation properly.\
|
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.
|
||||||
|
|||||||
18
package.json
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "cultivation",
|
"name": "cultivation",
|
||||||
"version": "1.0.2",
|
"version": "1.0.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^1.0.0-rc.5",
|
"@tauri-apps/api": "^1.0.0-rc.5",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^13.0.0",
|
"@testing-library/react": "^13.0.0",
|
||||||
"@testing-library/user-event": "^13.2.1",
|
"@testing-library/user-event": "^14.2.6",
|
||||||
"@types/jest": "^27.0.1",
|
"@types/jest": "^28.1.6",
|
||||||
"@types/node": "^16.7.13",
|
"@types/node": "^18.0.6",
|
||||||
"@types/react": "^18.0.0",
|
"@types/react": "^18.0.0",
|
||||||
"@types/react-dom": "^18.0.0",
|
"@types/react-dom": "^18.0.0",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
@@ -27,7 +27,11 @@
|
|||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"start:dev": "tauri dev"
|
"start:dev": "tauri dev",
|
||||||
|
"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": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@@ -53,7 +57,11 @@
|
|||||||
"@typescript-eslint/parser": "^5.22.0",
|
"@typescript-eslint/parser": "^5.22.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.15.0",
|
"eslint": "^8.15.0",
|
||||||
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-react": "^7.29.4",
|
"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"
|
"run-script-os": "^1.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,7 @@
|
|||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta name="description" content="Tauri-powered anime game launcher" />
|
||||||
name="description"
|
|
||||||
content="Tauri-powered anime game launcher"
|
|
||||||
/>
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<title>Cultivation</title>
|
<title>Cultivation</title>
|
||||||
|
|||||||
316
src-tauri/Cargo.lock
generated
@@ -75,7 +75,7 @@ dependencies = [
|
|||||||
"asn1-rs-impl",
|
"asn1-rs-impl",
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"nom",
|
"nom",
|
||||||
"num-traits",
|
"num-traits 0.2.15",
|
||||||
"rusticata-macros",
|
"rusticata-macros",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time 0.3.11",
|
"time 0.3.11",
|
||||||
@@ -506,9 +506,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "1.2.2"
|
version = "1.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
|
checksum = "83827793632c72fa4f73c2edb31e7a997527dd8ffe7077344621fc62c5478157"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cache-padded",
|
"cache-padded",
|
||||||
]
|
]
|
||||||
@@ -657,9 +657,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.4"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5999502d32b9c48d492abe66392408144895020ec4709e549e840799f3bb74c0"
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
"typenum",
|
"typenum",
|
||||||
@@ -712,14 +712,17 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
|
|||||||
name = "cultivation"
|
name = "cultivation"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cc",
|
||||||
"duct",
|
"duct",
|
||||||
|
"file_diff",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"hudsucker",
|
"hudsucker",
|
||||||
"is_elevated",
|
"is_elevated",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"open 2.1.3",
|
"open",
|
||||||
"rcgen",
|
"rcgen",
|
||||||
|
"regex",
|
||||||
"registry",
|
"registry",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile",
|
||||||
@@ -733,6 +736,7 @@ dependencies = [
|
|||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"unrar",
|
||||||
"zip 0.6.2",
|
"zip 0.6.2",
|
||||||
"zip-extract",
|
"zip-extract",
|
||||||
]
|
]
|
||||||
@@ -780,9 +784,9 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dbus"
|
name = "dbus"
|
||||||
version = "0.9.5"
|
version = "0.9.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "de0a745c25b32caa56b82a3950f5fec7893a960f4c10ca3b02060b0c38d8c2ce"
|
checksum = "6f8bcdd56d2e5c4ed26a529c5a9029f5db8290d433497506f958eae3be148eb6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"libdbus-sys",
|
"libdbus-sys",
|
||||||
@@ -817,8 +821,8 @@ dependencies = [
|
|||||||
"asn1-rs",
|
"asn1-rs",
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"nom",
|
"nom",
|
||||||
"num-bigint",
|
"num-bigint 0.4.3",
|
||||||
"num-traits",
|
"num-traits 0.2.15",
|
||||||
"rusticata-macros",
|
"rusticata-macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -945,6 +949,15 @@ dependencies = [
|
|||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum_primitive"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits 0.1.43",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "error-chain"
|
name = "error-chain"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
@@ -979,6 +992,12 @@ dependencies = [
|
|||||||
"rustc_version 0.3.3",
|
"rustc_version 0.3.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "file_diff"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "31a7a908b8f32538a2143e59a6e4e2508988832d5d4d6f7c156b3cbc762643a5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filetime"
|
name = "filetime"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -1032,6 +1051,12 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fuchsia-cprng"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futf"
|
name = "futf"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -1229,15 +1254,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generator"
|
name = "generator"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee"
|
checksum = "cc184cace1cea8335047a471cc1da80f18acf8a76f3bab2028d499e328948ec7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
"winapi",
|
"windows 0.32.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1453,9 +1478,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.2"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@@ -1690,8 +1715,8 @@ dependencies = [
|
|||||||
"byteorder",
|
"byteorder",
|
||||||
"color_quant",
|
"color_quant",
|
||||||
"num-iter",
|
"num-iter",
|
||||||
"num-rational",
|
"num-rational 0.4.1",
|
||||||
"num-traits",
|
"num-traits 0.2.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1928,9 +1953,9 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mac-notification-sys"
|
name = "mac-notification-sys"
|
||||||
version = "0.5.2"
|
version = "0.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "042f74a606175d72ca483e14e0873fe0f6c003f7af45865b17b16fdaface7203"
|
checksum = "fff231a88fe2e9985f9d159a2f02986fe46daa0f6af976a0d934be4870cc9d02"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
@@ -2162,6 +2187,32 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num"
|
||||||
|
version = "0.1.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint 0.1.44",
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-iter",
|
||||||
|
"num-rational 0.1.42",
|
||||||
|
"num-traits 0.2.15",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits 0.2.15",
|
||||||
|
"rand 0.4.6",
|
||||||
|
"rustc-serialize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -2170,7 +2221,17 @@ checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits 0.2.15",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.1.43"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits 0.2.15",
|
||||||
|
"rustc-serialize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2180,7 +2241,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"num-traits",
|
"num-traits 0.2.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2191,7 +2252,19 @@ checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits 0.2.15",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.1.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint 0.1.44",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits 0.2.15",
|
||||||
|
"rustc-serialize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2202,7 +2275,16 @@ checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits 0.2.15",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.1.43"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits 0.2.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2316,19 +2398,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "open"
|
name = "open"
|
||||||
version = "2.1.3"
|
version = "3.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2423ffbf445b82e58c3b1543655968923dd06f85432f10be2bb4f1b7122f98c"
|
checksum = "f23a407004a1033f53e93f9b45580d14de23928faad187384f891507c9b0c045"
|
||||||
dependencies = [
|
|
||||||
"pathdiff",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "open"
|
|
||||||
version = "3.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "360bcc8316bf6363aa3954c3ccc4de8add167b087e0259190a043c9514f910fe"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pathdiff",
|
"pathdiff",
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
@@ -2336,9 +2408,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.40"
|
version = "0.10.41"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e"
|
checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
@@ -2368,9 +2440,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.74"
|
version = "0.9.75"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1"
|
checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -2526,9 +2598,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem"
|
name = "pem"
|
||||||
version = "1.0.2"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e9a3b09a20e374558580a4914d3b7d89bd61b954a5a5e1dcbea98753addb1947"
|
checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
]
|
]
|
||||||
@@ -2832,6 +2904,19 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||||
|
dependencies = [
|
||||||
|
"fuchsia-cprng",
|
||||||
|
"libc",
|
||||||
|
"rand_core 0.3.1",
|
||||||
|
"rdrand",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
@@ -2877,6 +2962,21 @@ dependencies = [
|
|||||||
"rand_core 0.6.3",
|
"rand_core 0.6.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.4.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -2957,9 +3057,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rcgen"
|
name = "rcgen"
|
||||||
version = "0.9.2"
|
version = "0.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7fa2d386df8533b02184941c76ae2e0d0c1d053f5d43339169d80f21275fc5e"
|
checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pem",
|
"pem",
|
||||||
"ring",
|
"ring",
|
||||||
@@ -2968,6 +3068,15 @@ dependencies = [
|
|||||||
"yasna",
|
"yasna",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rdrand"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.3.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
@@ -3114,6 +3223,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-serialize"
|
||||||
|
version = "0.3.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -3164,9 +3279,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf"
|
checksum = "24c8ad4f0c00e1eb5bc7614d236a7f1300e3dbd76b68cac8e06fb00b015ad8d8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
@@ -3302,18 +3417,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.138"
|
version = "1.0.139"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47"
|
checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.138"
|
version = "1.0.139"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c"
|
checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -3769,9 +3884,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d61fc211e0bd2c04c0aecd202d2cd72dd797a89da02989a39e1b9691462386d6"
|
checksum = "827f61bd3dd40276694be5c7ffc40d65b94ab00d9f8c1a4a4db07f2cdc306c83"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"attohttpc",
|
"attohttpc",
|
||||||
@@ -3790,7 +3905,7 @@ dependencies = [
|
|||||||
"notify-rust",
|
"notify-rust",
|
||||||
"objc",
|
"objc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"open 3.0.1",
|
"open",
|
||||||
"os_info",
|
"os_info",
|
||||||
"os_pipe 1.0.1",
|
"os_pipe 1.0.1",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
@@ -3822,9 +3937,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-build"
|
name = "tauri-build"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2f2b32e551ec810ba4ab2ad735de5e3576e54bf0322ab0f4b7ce41244bc65ecf"
|
checksum = "acafb1c515c5d14234a294461bd43c723639a84891a45f6a250fd3441ad2e8ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
@@ -3838,9 +3953,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-codegen"
|
name = "tauri-codegen"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6f1f7928dd040fc03c94207adfad506c0cf5b152982fd1dc0a621f7fd777e22"
|
checksum = "16d62a3c8790d6cba686cea6e3f7f569d12c662c3274c2d165a4fd33e3871b72"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"brotli",
|
"brotli",
|
||||||
@@ -3864,9 +3979,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-macros"
|
name = "tauri-macros"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e50b9f52871c088857360319a37472d59f4644f1ed004489599d62831a1b6996"
|
checksum = "7296fa17996629f43081e1c66d554703900187ed900c5bf46f97f0bcfb069278"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.4.0",
|
"heck 0.4.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -4048,10 +4163,11 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.19.2"
|
version = "1.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
|
checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -4087,9 +4203,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-tungstenite"
|
name = "tokio-tungstenite"
|
||||||
version = "0.17.1"
|
version = "0.17.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06cda1232a49558c46f8a504d5b93101d42c0bf7f911f12a105ba48168f821ae"
|
checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"log",
|
"log",
|
||||||
@@ -4216,9 +4332,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tungstenite"
|
name = "tungstenite"
|
||||||
version = "0.17.2"
|
version = "0.17.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5"
|
checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
@@ -4264,9 +4380,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
@@ -4289,6 +4405,31 @@ version = "0.2.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
|
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unrar"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "433cea4f0b7bec88d47becb380887b8786a3cfb1c82e1ef9d32a682ba6801814"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"enum_primitive",
|
||||||
|
"lazy_static",
|
||||||
|
"num",
|
||||||
|
"regex",
|
||||||
|
"unrar_sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unrar_sys"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0009399408dc0bcc5c8910672544fceceeba18b91f741ff943916e917d982c60"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@@ -4670,6 +4811,19 @@ dependencies = [
|
|||||||
"windows_x86_64_msvc 0.24.0",
|
"windows_x86_64_msvc 0.24.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.32.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_msvc 0.32.0",
|
||||||
|
"windows_i686_gnu 0.32.0",
|
||||||
|
"windows_i686_msvc 0.32.0",
|
||||||
|
"windows_x86_64_gnu 0.32.0",
|
||||||
|
"windows_x86_64_msvc 0.32.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows"
|
name = "windows"
|
||||||
version = "0.37.0"
|
version = "0.37.0"
|
||||||
@@ -4729,6 +4883,12 @@ version = "0.37.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3263d25f1170419995b78ff10c06b949e8a986c35c208dc24333c64753a87169"
|
checksum = "3263d25f1170419995b78ff10c06b949e8a986c35c208dc24333c64753a87169"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.32.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.36.1"
|
version = "0.36.1"
|
||||||
@@ -4747,6 +4907,12 @@ version = "0.24.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c0866510a3eca9aed73a077490bbbf03e5eaac4e1fd70849d89539e5830501fd"
|
checksum = "c0866510a3eca9aed73a077490bbbf03e5eaac4e1fd70849d89539e5830501fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.32.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.36.1"
|
version = "0.36.1"
|
||||||
@@ -4765,6 +4931,12 @@ version = "0.24.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf0ffed56b7e9369a29078d2ab3aaeceea48eb58999d2cff3aa2494a275b95c6"
|
checksum = "bf0ffed56b7e9369a29078d2ab3aaeceea48eb58999d2cff3aa2494a275b95c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.32.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.36.1"
|
version = "0.36.1"
|
||||||
@@ -4783,6 +4955,12 @@ version = "0.24.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "384a173630588044205a2993b6864a2f56e5a8c1e7668c07b93ec18cf4888dc4"
|
checksum = "384a173630588044205a2993b6864a2f56e5a8c1e7668c07b93ec18cf4888dc4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.32.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.36.1"
|
version = "0.36.1"
|
||||||
@@ -4801,6 +4979,12 @@ version = "0.24.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9bd8f062d8ca5446358159d79a90be12c543b3a965c847c8f3eedf14b321d399"
|
checksum = "9bd8f062d8ca5446358159d79a90be12c543b3a965c847c8f3eedf14b321d399"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.32.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.36.1"
|
version = "0.36.1"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ rust-version = "1.57"
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "1.0.0-rc.8", features = [] }
|
tauri-build = { version = "1.0.0-rc.8", features = [] }
|
||||||
|
cc = "1.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
is_elevated = "0.1.2"
|
is_elevated = "0.1.2"
|
||||||
@@ -30,13 +31,14 @@ sysinfo = "0.24.6"
|
|||||||
|
|
||||||
# ZIP-archive library.
|
# ZIP-archive library.
|
||||||
zip-extract = "0.1.1"
|
zip-extract = "0.1.1"
|
||||||
|
unrar = "0.4.4"
|
||||||
zip = "0.6.2"
|
zip = "0.6.2"
|
||||||
|
|
||||||
# For creating a "global" downloads list.
|
# For creating a "global" downloads list.
|
||||||
once_cell = "1.13.0"
|
once_cell = "1.13.0"
|
||||||
|
|
||||||
# Program opener.
|
# Program opener.
|
||||||
open = "2.1.2"
|
open = "3.0.2"
|
||||||
duct = "0.13.5"
|
duct = "0.13.5"
|
||||||
|
|
||||||
# Serialization.
|
# Serialization.
|
||||||
@@ -54,6 +56,12 @@ reqwest = { version = "0.11.3", features = ["stream"] }
|
|||||||
futures-util = "0.3.14"
|
futures-util = "0.3.14"
|
||||||
rcgen = { version = "0.9", features = ["x509-parser"] }
|
rcgen = { version = "0.9", features = ["x509-parser"] }
|
||||||
|
|
||||||
|
# metadata stuff
|
||||||
|
regex = "1"
|
||||||
|
|
||||||
|
# other
|
||||||
|
file_diff = "1.0.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
fn main() {
|
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()
|
||||||
|
.include("mhycrypto")
|
||||||
|
.file("mhycrypto/aes.c")
|
||||||
|
.compile("mhycrypto-aes");
|
||||||
|
|
||||||
tauri_build::build()
|
tauri_build::build()
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src-tauri/icons/icon_resize.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
1
src-tauri/keys/dispatchKey.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<RSAKeyValue><Modulus>AMW28dptX3h8q0O4z/vJrQxf6cmC6yVilgHRL98GazrYzmc3ixj87JpHIJ3IKEYV+HU/tYrUjEfY/ZtPzsLB9lKBelN9i8QjkFkA9QDICGYwJCXibxU67Z/HzENe9NQpG2i01SI0TJU8PJDV7zQPwPVGraIg5ouExRupq8UymaSHEyJ7zxKZCtgO0LKdROLJBSvI5srMu7kYTGmB7T07Ab8T9M595YSgd1vh06qZ3nsF1h4wg3y+zW28vdY28+RCj2V1i7oVyL0dQruLYq7qK8FycZl2j9R0GaJ8rRAjVP1Dsz+hjS3atHhQxOG9OFo6d/euedRvfWIhT9p6h1SeTjE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
|
||||||
7
src-tauri/keys/passwordKey.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<RSAKeyValue>
|
||||||
|
<Exponent>AQAB</Exponent>
|
||||||
|
<Modulus>yytg/H9lz7Lm0XcA8LMqIyXPVNApYTcSepT4VDLB4qqqFC3s
|
||||||
|
/Huv8vN7zA/P4uoREIu8KMenADFk7uwrZSxoMWwJgn6A7sbAt1cqAaUXB
|
||||||
|
9J4NzhL0x3AFTiHEQbw86hRvm2VGkbA5sWnr0NZw8SGBBY+EODwNIt51G
|
||||||
|
dBA7eoUQU=</Modulus>
|
||||||
|
</RSAKeyValue>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"files_extracting": "文件解压中:"
|
"files_extracting": "文件解压中:"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"game_exec": "选择游戏可执行文件",
|
"game_executable": "选择游戏可执行文件",
|
||||||
"grasscutter_jar": "选择 Grasscutter JAR 文件",
|
"grasscutter_jar": "选择 Grasscutter JAR 文件",
|
||||||
"java_path": "设置自定义 Java 路径",
|
"java_path": "设置自定义 Java 路径",
|
||||||
"grasscutter_with_game": "随游戏自动启动 Grasscutter",
|
"grasscutter_with_game": "随游戏自动启动 Grasscutter",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"enabled": "已啟用",
|
"enabled": "已啟用",
|
||||||
"disabled": "未啟用",
|
"disabled": "未啟用",
|
||||||
"game_exec": "選擇遊戲執行檔",
|
"game_executable": "選擇遊戲執行檔",
|
||||||
"grasscutter_jar": "選擇伺服器JAR檔案",
|
"grasscutter_jar": "選擇伺服器JAR檔案",
|
||||||
"toggle_encryption": "設定加密",
|
"toggle_encryption": "設定加密",
|
||||||
"java_path": "設定自定義Java路徑",
|
"java_path": "設定自定義Java路徑",
|
||||||
|
|||||||
@@ -1,61 +1,61 @@
|
|||||||
{
|
{
|
||||||
"lang_name": "Deutsch",
|
"lang_name": "Deutsch",
|
||||||
"main": {
|
"main": {
|
||||||
"title": "Cultivation",
|
"title": "Cultivation",
|
||||||
"launch_button": "Starten",
|
"launch_button": "Starten",
|
||||||
"gc_enable": "Über Grasscutter verbinden",
|
"gc_enable": "Über Grasscutter verbinden",
|
||||||
"https_enable": "HTTPS nutzen",
|
"https_enable": "HTTPS nutzen",
|
||||||
"ip_placeholder": "Server Adresse...",
|
"ip_placeholder": "Server Adresse...",
|
||||||
"port_placeholder": "Port...",
|
"port_placeholder": "Port...",
|
||||||
"files_downloading": "Herunterladen von Dateien: ",
|
"files_downloading": "Herunterladen von Dateien: ",
|
||||||
"files_extracting": "Extrahieren von Dateien: "
|
"files_extracting": "Extrahieren von Dateien: "
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"enabled": "Aktiviert",
|
"enabled": "Aktiviert",
|
||||||
"disabled": "Deaktiviert",
|
"disabled": "Deaktiviert",
|
||||||
"game_exec": "Spiel Datei auswählen",
|
"game_executable": "Spiel Datei auswählen",
|
||||||
"grasscutter_jar": "Grasscuter JAR auswählen",
|
"grasscutter_jar": "Grasscuter JAR auswählen",
|
||||||
"toggle_encryption": "Verschlüsselung umschalten",
|
"toggle_encryption": "Verschlüsselung umschalten",
|
||||||
"java_path": "Benutzerdefinierten Java Pfad setzen",
|
"java_path": "Benutzerdefinierten Java Pfad setzen",
|
||||||
"grasscutter_with_game": "Grasscutter automatisch mit dem Spiel starten",
|
"grasscutter_with_game": "Grasscutter automatisch mit dem Spiel starten",
|
||||||
"language": "Sprache auswählen",
|
"language": "Sprache auswählen",
|
||||||
"background": "Benutzerdefinierten Hintergrund festlegen (link oder bild)",
|
"background": "Benutzerdefinierten Hintergrund festlegen (link oder bild)",
|
||||||
"theme": "Theme auswählen"
|
"theme": "Theme auswählen"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"grasscutter_stable_data": "Stabile Grasscutter Daten herunterladen",
|
"grasscutter_stable_data": "Stabile Grasscutter Daten herunterladen",
|
||||||
"grasscutter_latest_data": "Aktuellste Grasscutter Daten herunterladen",
|
"grasscutter_latest_data": "Aktuellste Grasscutter Daten herunterladen",
|
||||||
"grasscutter_stable_data_update": "Stabile Grasscutter Daten aktualisieren",
|
"grasscutter_stable_data_update": "Stabile Grasscutter Daten aktualisieren",
|
||||||
"grasscutter_latest_data_update": "Aktuellste Grasscutter Daten aktualisieren",
|
"grasscutter_latest_data_update": "Aktuellste Grasscutter Daten aktualisieren",
|
||||||
"grasscutter_stable": "Stabile Grasscutter Version herunterladen",
|
"grasscutter_stable": "Stabile Grasscutter Version herunterladen",
|
||||||
"grasscutter_latest": "Aktuellste Grasscutter Version herunterladen",
|
"grasscutter_latest": "Aktuellste Grasscutter Version herunterladen",
|
||||||
"grasscutter_stable_update": "Stabile Grasscutter Version aktualisieren",
|
"grasscutter_stable_update": "Stabile Grasscutter Version aktualisieren",
|
||||||
"grasscutter_latest_update": "Aktuellste Grasscutter Version aktualisieren",
|
"grasscutter_latest_update": "Aktuellste Grasscutter Version aktualisieren",
|
||||||
"resources": "Grasscutter Ressourcen herunterladen"
|
"resources": "Grasscutter Ressourcen herunterladen"
|
||||||
},
|
},
|
||||||
"download_status": {
|
"download_status": {
|
||||||
"downloading": "Lädt herunter",
|
"downloading": "Lädt herunter",
|
||||||
"extracting": "Extrahiert",
|
"extracting": "Extrahiert",
|
||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"finished": "Fertig",
|
"finished": "Fertig",
|
||||||
"stopped": "Gestoppt"
|
"stopped": "Gestoppt"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"select_file": "Datei oder Ordner auswählen...",
|
"select_file": "Datei oder Ordner auswählen...",
|
||||||
"select_folder": "Ordner auswählen...",
|
"select_folder": "Ordner auswählen...",
|
||||||
"download": "Herunterladen"
|
"download": "Herunterladen"
|
||||||
},
|
},
|
||||||
"news": {
|
"news": {
|
||||||
"latest_commits": "Letzte Commits",
|
"latest_commits": "Letzte Commits",
|
||||||
"latest_version": "Letzte Version"
|
"latest_version": "Letzte Version"
|
||||||
},
|
},
|
||||||
"help": {
|
"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'.",
|
"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.",
|
"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_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_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_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.",
|
"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"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,19 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"game_exec": "Set Game Executable",
|
"game_path": "Set Game Install Path",
|
||||||
|
"game_executable": "Set Game Executable",
|
||||||
|
"recover_metadata": "Emergency Metadata Recovery",
|
||||||
"grasscutter_jar": "Set Grasscutter JAR",
|
"grasscutter_jar": "Set Grasscutter JAR",
|
||||||
"toggle_encryption": "Toggle Encryption",
|
"toggle_encryption": "Toggle Encryption",
|
||||||
|
"install_certificate": "Install Proxy Certificate",
|
||||||
"java_path": "Set Custom Java Path",
|
"java_path": "Set Custom Java Path",
|
||||||
"grasscutter_with_game": "Automatically launch Grasscutter with game",
|
"grasscutter_with_game": "Automatically launch Grasscutter with game",
|
||||||
"language": "Select Language",
|
"language": "Select Language",
|
||||||
"background": "Set Custom Background (link or image file)",
|
"background": "Set Custom Background (link or image file)",
|
||||||
"theme": "Set Theme"
|
"theme": "Set Theme",
|
||||||
|
"patch_metadata": "Automatically Patch Metadata",
|
||||||
|
"use_proxy": "Use Internal Proxy"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"grasscutter_stable_data": "Download Grasscutter Stable Data",
|
"grasscutter_stable_data": "Download Grasscutter Stable Data",
|
||||||
@@ -31,7 +36,8 @@
|
|||||||
"grasscutter_latest": "Download Grasscutter Latest",
|
"grasscutter_latest": "Download Grasscutter Latest",
|
||||||
"grasscutter_stable_update": "Update Grasscutter Stable",
|
"grasscutter_stable_update": "Update Grasscutter Stable",
|
||||||
"grasscutter_latest_update": "Update Grasscutter Latest",
|
"grasscutter_latest_update": "Update Grasscutter Latest",
|
||||||
"resources": "Download Grasscutter Resources"
|
"resources": "Download Grasscutter Resources",
|
||||||
|
"game": "Download Game"
|
||||||
},
|
},
|
||||||
"download_status": {
|
"download_status": {
|
||||||
"downloading": "Downloading",
|
"downloading": "Downloading",
|
||||||
@@ -43,7 +49,8 @@
|
|||||||
"components": {
|
"components": {
|
||||||
"select_file": "Select file or folder...",
|
"select_file": "Select file or folder...",
|
||||||
"select_folder": "Select folder...",
|
"select_folder": "Select folder...",
|
||||||
"download": "Download"
|
"download": "Download",
|
||||||
|
"install": "Install"
|
||||||
},
|
},
|
||||||
"news": {
|
"news": {
|
||||||
"latest_commits": "Recent Commits",
|
"latest_commits": "Recent Commits",
|
||||||
@@ -57,5 +64,13 @@
|
|||||||
"gc_stable_data": "Download the current stable Grasscutter data files, which does not come with a jar file. This is useful for updating.",
|
"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.",
|
"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"
|
"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"
|
||||||
|
},
|
||||||
|
"swag": {
|
||||||
|
"akebi_name": "Akebi",
|
||||||
|
"migoto_name": "Migoto",
|
||||||
|
"reshade_name": "Reshade",
|
||||||
|
"akebi": "Set Akebi Executable",
|
||||||
|
"migoto": "Set 3DMigoto Executable",
|
||||||
|
"reshade": "Set Reshade Injector"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
69
src-tauri/lang/es.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"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_executable": "Establecer ejecutable del juego",
|
||||||
|
"recover_metadata": "Recuperación de Metadatos 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)",
|
||||||
|
"theme": "Establecer Tema"
|
||||||
|
},
|
||||||
|
"downloads": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
"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_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,7 +13,7 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"enabled": "active",
|
"enabled": "active",
|
||||||
"disabled": "desactiver",
|
"disabled": "desactiver",
|
||||||
"game_exec": "definir l'executable du jeu",
|
"game_executable": "definir l'executable du jeu",
|
||||||
"grasscutter_jar": "definir le Jar Grasscutter",
|
"grasscutter_jar": "definir le Jar Grasscutter",
|
||||||
"toggle_encryption": "activer l'encryption",
|
"toggle_encryption": "activer l'encryption",
|
||||||
"java_path": "definir un chemin java personnalise",
|
"java_path": "definir un chemin java personnalise",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"files_extracting": "MengExtract File: "
|
"files_extracting": "MengExtract File: "
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"game_exec": "Set Game Executable",
|
"game_executable": "Set Game Executable",
|
||||||
"grasscutter_jar": "Path ke Grasscutter JAR",
|
"grasscutter_jar": "Path ke Grasscutter JAR",
|
||||||
"java_path": "Atur kustom Java path",
|
"java_path": "Atur kustom Java path",
|
||||||
"grasscutter_with_game": "Otomatis Menjalankan Grasscutter Dengan Game",
|
"grasscutter_with_game": "Otomatis Menjalankan Grasscutter Dengan Game",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"background": "Atur Kustom Latar Belakang (link atau gambar file)",
|
"background": "Atur Kustom Latar Belakang (link atau gambar file)",
|
||||||
"theme": "Atur Tema"
|
"theme": "Atur Tema"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"grasscutter_stable_data": "Sedang Mendownload Grasscutter Versi Stabil",
|
"grasscutter_stable_data": "Sedang Mendownload Grasscutter Versi Stabil",
|
||||||
"grasscutter_latest_data": "Sedang Mendownload Grasscutter Data Terbaru",
|
"grasscutter_latest_data": "Sedang Mendownload Grasscutter Data Terbaru",
|
||||||
"grasscutter_stable_data_update": "Memperbaharui Grasscutter Data Stabil",
|
"grasscutter_stable_data_update": "Memperbaharui Grasscutter Data Stabil",
|
||||||
@@ -54,4 +54,4 @@
|
|||||||
"gc_dev_data": "Unduh file data Grasscutter Development 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"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"enabled": "Iespējots",
|
"enabled": "Iespējots",
|
||||||
"disabled": "Atspējots",
|
"disabled": "Atspējots",
|
||||||
"game_exec": "Iestatīt spēles izpildāmu",
|
"game_executable": "Iestatīt spēles izpildāmu",
|
||||||
"grasscutter_jar": "Iestatiet Grasscutter JAR",
|
"grasscutter_jar": "Iestatiet Grasscutter JAR",
|
||||||
"toggle_encryption": "Pārslēgt Šifrēšanu",
|
"toggle_encryption": "Pārslēgt Šifrēšanu",
|
||||||
"java_path": "Iestatiet pielāgotu Java ceļu",
|
"java_path": "Iestatiet pielāgotu Java ceļu",
|
||||||
|
|||||||
@@ -1,61 +1,61 @@
|
|||||||
{
|
{
|
||||||
"lang_name": "Русский",
|
"lang_name": "Русский",
|
||||||
"main": {
|
"main": {
|
||||||
"title": "Cultivation",
|
"title": "Cultivation",
|
||||||
"launch_button": "Запустить",
|
"launch_button": "Запустить",
|
||||||
"gc_enable": "Подключиться с Grasscutter",
|
"gc_enable": "Подключиться с Grasscutter",
|
||||||
"https_enable": "Исп. HTTPS",
|
"https_enable": "Исп. HTTPS",
|
||||||
"ip_placeholder": "Айпи адрес...",
|
"ip_placeholder": "Айпи адрес...",
|
||||||
"port_placeholder": "Порт...",
|
"port_placeholder": "Порт...",
|
||||||
"files_downloading": "Файлов скачано: ",
|
"files_downloading": "Файлов скачано: ",
|
||||||
"files_extracting": "Извлечено файлов: "
|
"files_extracting": "Извлечено файлов: "
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"enabled": "Включено",
|
"enabled": "Включено",
|
||||||
"disabled": "Выключено",
|
"disabled": "Выключено",
|
||||||
"game_exec": "Установить исполняемый файл игры",
|
"game_executable": "Установить исполняемый файл игры",
|
||||||
"grasscutter_jar": "Установить Grasscutter JAR",
|
"grasscutter_jar": "Установить Grasscutter JAR",
|
||||||
"toggle_encryption": "Переключить шифрование",
|
"toggle_encryption": "Переключить шифрование",
|
||||||
"java_path": "Установить пользовательский путь Java",
|
"java_path": "Установить пользовательский путь Java",
|
||||||
"grasscutter_with_game": "Автоматически запускать Grasscutter вместе с игрой",
|
"grasscutter_with_game": "Автоматически запускать Grasscutter вместе с игрой",
|
||||||
"language": "Установить язык",
|
"language": "Установить язык",
|
||||||
"background": "Установить свой фон (ссылка или файл)",
|
"background": "Установить свой фон (ссылка или файл)",
|
||||||
"theme": "Установить тему"
|
"theme": "Установить тему"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"grasscutter_stable_data": "Скачать стабильные данные Grasscutter",
|
"grasscutter_stable_data": "Скачать стабильные данные Grasscutter",
|
||||||
"grasscutter_latest_data": "Скачать последние данные Grasscutter",
|
"grasscutter_latest_data": "Скачать последние данные Grasscutter",
|
||||||
"grasscutter_stable_data_update": "Обновить стабильные данные Grasscutter",
|
"grasscutter_stable_data_update": "Обновить стабильные данные Grasscutter",
|
||||||
"grasscutter_latest_data_update": "Обновить последние данные Grasscutter",
|
"grasscutter_latest_data_update": "Обновить последние данные Grasscutter",
|
||||||
"grasscutter_stable": "Скачать стабильную версию Grasscutter",
|
"grasscutter_stable": "Скачать стабильную версию Grasscutter",
|
||||||
"grasscutter_latest": "Скачать последнюю версию Grasscutter",
|
"grasscutter_latest": "Скачать последнюю версию Grasscutter",
|
||||||
"grasscutter_stable_update": "Обновить стабильную версию Grasscutter",
|
"grasscutter_stable_update": "Обновить стабильную версию Grasscutter",
|
||||||
"grasscutter_latest_update": "Обновить последнюю версию Grasscutter",
|
"grasscutter_latest_update": "Обновить последнюю версию Grasscutter",
|
||||||
"resources": "Скачать ресурсы Grasscutter"
|
"resources": "Скачать ресурсы Grasscutter"
|
||||||
},
|
},
|
||||||
"download_status": {
|
"download_status": {
|
||||||
"downloading": "Скачивание",
|
"downloading": "Скачивание",
|
||||||
"extracting": "Извлечение",
|
"extracting": "Извлечение",
|
||||||
"error": "Ошибка",
|
"error": "Ошибка",
|
||||||
"finished": "Закончено",
|
"finished": "Закончено",
|
||||||
"stopped": "Остановлено"
|
"stopped": "Остановлено"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"select_file": "Выберите файл или папку...",
|
"select_file": "Выберите файл или папку...",
|
||||||
"select_folder": "Выберите папку...",
|
"select_folder": "Выберите папку...",
|
||||||
"download": "Скачать"
|
"download": "Скачать"
|
||||||
},
|
},
|
||||||
"news": {
|
"news": {
|
||||||
"latest_commits": "Последние коммиты",
|
"latest_commits": "Последние коммиты",
|
||||||
"latest_version": "Последняя версия"
|
"latest_version": "Последняя версия"
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"port_help_text": "Убедитесь, что это порт Dispatch-сервера, не порт игрового сервера. Обычно это '443'.",
|
"port_help_text": "Убедитесь, что это порт Dispatch-сервера, не порт игрового сервера. Обычно это '443'.",
|
||||||
"game_help_text": "Вам не нужно устанавливать еще одну копию, что бы играть с Grascutter. Это нужно или для версии 2.6, или если у Вас не установлена игра.",
|
"game_help_text": "Вам не нужно устанавливать еще одну копию, что бы играть с Grascutter. Это нужно или для версии 2.6, или если у Вас не установлена игра.",
|
||||||
"gc_stable_jar": "Скачать последнюю стабильную версию Grasscutter, которая содержит jar файл и данные.",
|
"gc_stable_jar": "Скачать последнюю стабильную версию Grasscutter, которая содержит jar файл и данные.",
|
||||||
"gc_dev_jar": "Скачать последнюю версию для разработки Grasscutter, которая содержит jar файл и данные.",
|
"gc_dev_jar": "Скачать последнюю версию для разработки Grasscutter, которая содержит jar файл и данные.",
|
||||||
"gc_stable_data": "Скачать стабильные данные Grasscutter, в которой нету jar файла. Это полезно для обновления.",
|
"gc_stable_data": "Скачать стабильные данные Grasscutter, в которой нету jar файла. Это полезно для обновления.",
|
||||||
"gc_dev_data": "Скачать последнюю версию для разработки Grasscutter, в которой нету jar файла. Это полезно для обновления.",
|
"gc_dev_data": "Скачать последнюю версию для разработки Grasscutter, в которой нету jar файла. Это полезно для обновления.",
|
||||||
"resources": "Это необходимо для запуска сервера Grasscutter. Эта кнопка будет серой, если у Вас уже есть не пустая папка с ресурсами."
|
"resources": "Это необходимо для запуска сервера Grasscutter. Эта кнопка будет серой, если у Вас уже есть не пустая папка с ресурсами."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,59 +3,70 @@
|
|||||||
"main": {
|
"main": {
|
||||||
"title": "Cultivation",
|
"title": "Cultivation",
|
||||||
"launch_button": "Khởi Chạy",
|
"launch_button": "Khởi Chạy",
|
||||||
"gc_enable": "Kết nối đến Grasscutter",
|
"gc_enable": "Kết nối qua Grasscutter",
|
||||||
"https_enable": "Sử dụng HTTPS",
|
"https_enable": "Dùng HTTPS",
|
||||||
"ip_placeholder": "Địa chỉ máy chủ...",
|
"ip_placeholder": "Địa chỉ máy chủ...",
|
||||||
"port_placeholder": "Cổng...",
|
"port_placeholder": "Cổng...",
|
||||||
"files_downloading": "Đang tải file: ",
|
"files_downloading": "Đang tải tập tin: ",
|
||||||
"files_extracting": "Đang giải nén tệp tin: "
|
"files_extracting": "Đang giải nén tập tin: "
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"enabled": "Bật",
|
"enabled": "Bật",
|
||||||
"disabled": "Tắt",
|
"disabled": "Tắt",
|
||||||
"game_exec": "Đường dẫn đến GenshinImpact.exe",
|
"game_path": "Đường dẫn cài game",
|
||||||
"grasscutter_jar": "Đường dẫn đến Grasscutter.jar",
|
"game_executable": "Tập tin thực thi game",
|
||||||
"toggle_encryption": "Bật/Tắt mã hoá",
|
"recover_metadata": "Khôi phục Metadata khẩn cấp",
|
||||||
"java_path": "Đường dẫn Java tuỳ chỉnh",
|
"grasscutter_jar": "Tập tin JAR Grasscutter",
|
||||||
"grasscutter_with_game": "Tự động khởi chạy Grasscutter cùng game",
|
"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": "Chọn ngôn ngữ",
|
"language": "Chọn ngôn ngữ",
|
||||||
"background": "Ảnh nền tuỳ chỉnh (đường dẫn hoặc tệp tin ảnh)",
|
"background": "Hình nền tùy chỉnh (liên kết hoặc tập tin hình ảnh)",
|
||||||
"theme": "Chọn giao diện"
|
"theme": "Giao diện",
|
||||||
|
"patch_metadata": "Tự động sửa Metadata",
|
||||||
|
"use_proxy": "Sử dụng proxy nội bộ"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"grasscutter_stable_data": "Tải xuống dữ liệu Grasscutter bản ổn định",
|
"grasscutter_stable_data": "Tải 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_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_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_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_stable": "Tải Grasscutter bản ổn định",
|
||||||
"grasscutter_latest": "Tải xuống Grasscutter phiển bản mới nhất",
|
"grasscutter_latest": "Tải Grasscutter bản mới nhất",
|
||||||
"grasscutter_stable_update": "Cập nhật Grasscutter ổn định",
|
"grasscutter_stable_update": "Cập nhật Grasscutter bản ổn định",
|
||||||
"grasscutter_latest_update": "Cập nhật Grasscutter mới nhất",
|
"grasscutter_latest_update": "Cập nhật Grasscutter bản mới nhất",
|
||||||
"resources": "Tải xuống tài nguyên cho Grasscutter"
|
"resources": "Tải tài nguyên Grasscutter",
|
||||||
|
"game": "Tải game"
|
||||||
},
|
},
|
||||||
"download_status": {
|
"download_status": {
|
||||||
"downloading": "Đang tải",
|
"downloading": "Đang tải xuống",
|
||||||
"extracting": "Đang giải nén",
|
"extracting": "Đang giải nén",
|
||||||
"error": "Lỗi",
|
"error": "Lỗi",
|
||||||
"finished": "Đã xong",
|
"finished": "Hoàn thành",
|
||||||
"stopped": "Đã dừng"
|
"stopped": "Đã dừng"
|
||||||
},
|
},
|
||||||
"components": {
|
"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...",
|
"select_folder": "Chọn thư mục...",
|
||||||
"download": "Tải xuống"
|
"download": "Tải xuống",
|
||||||
|
"install": "Cài đặt"
|
||||||
},
|
},
|
||||||
"news": {
|
"news": {
|
||||||
"latest_commits": "Cập nhật gần đây",
|
"latest_commits": "Thay Đổi Gần Đây",
|
||||||
"latest_version": "Phiên bản mới nhất"
|
"latest_version": "Phiên Bản Mới Nhất"
|
||||||
},
|
},
|
||||||
"help": {
|
"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'.",
|
"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 đặt trò chơi.",
|
"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, bảo gồm file jar và các file dữ liệu.",
|
"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_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_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 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_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 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.",
|
"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.",
|
||||||
"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"
|
"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ó sẵn một thư mục tài nguyên có nội dung bên trong"
|
||||||
|
},
|
||||||
|
"swag": {
|
||||||
|
"akebi": "Tập tin thực thi Akebi",
|
||||||
|
"migoto": "Tập tin thực thi 3dMigoto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
387
src-tauri/mhycrypto/aes.c
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
// Simple, thoroughly commented implementation of 128-bit AES / Rijndael using C
|
||||||
|
// Chris Hulbert - chris.hulbert@gmail.com - http://splinter.com.au/blog
|
||||||
|
// References:
|
||||||
|
// http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
|
||||||
|
// http://en.wikipedia.org/wiki/Rijndael_key_schedule
|
||||||
|
// http://en.wikipedia.org/wiki/Rijndael_mix_columns
|
||||||
|
// http://en.wikipedia.org/wiki/Rijndael_S-box
|
||||||
|
|
||||||
|
// This code is public domain, or any OSI-approved license, your choice. No warranty.
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "aes.h"
|
||||||
|
|
||||||
|
typedef unsigned char byte;
|
||||||
|
|
||||||
|
// Here are all the lookup tables for the row shifts, rcon, s-boxes, and galois field multiplications
|
||||||
|
static const byte shift_rows_table[] = {0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11};
|
||||||
|
static const byte shift_rows_table_inv[] = {0, 13, 10, 7, 4, 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3};
|
||||||
|
static const byte lookup_rcon[] = {
|
||||||
|
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a};
|
||||||
|
static const byte lookup_sbox[] = {
|
||||||
|
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
|
||||||
|
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
|
||||||
|
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
|
||||||
|
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
|
||||||
|
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
|
||||||
|
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
|
||||||
|
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
|
||||||
|
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
|
||||||
|
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
|
||||||
|
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
|
||||||
|
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
|
||||||
|
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
|
||||||
|
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
|
||||||
|
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
|
||||||
|
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
||||||
|
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16};
|
||||||
|
static const byte lookup_sbox_inv[] = {
|
||||||
|
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
|
||||||
|
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
|
||||||
|
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
|
||||||
|
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
|
||||||
|
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
|
||||||
|
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
|
||||||
|
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
|
||||||
|
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
|
||||||
|
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
|
||||||
|
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
|
||||||
|
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
|
||||||
|
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
|
||||||
|
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
|
||||||
|
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
|
||||||
|
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
|
||||||
|
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d};
|
||||||
|
static const byte lookup_g2[] = {
|
||||||
|
0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
|
||||||
|
0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e,
|
||||||
|
0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e,
|
||||||
|
0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e,
|
||||||
|
0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e,
|
||||||
|
0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe,
|
||||||
|
0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde,
|
||||||
|
0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe,
|
||||||
|
0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05,
|
||||||
|
0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25,
|
||||||
|
0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45,
|
||||||
|
0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65,
|
||||||
|
0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85,
|
||||||
|
0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5,
|
||||||
|
0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5,
|
||||||
|
0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5};
|
||||||
|
static const byte lookup_g3[] = {
|
||||||
|
0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11,
|
||||||
|
0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21,
|
||||||
|
0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71,
|
||||||
|
0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41,
|
||||||
|
0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1,
|
||||||
|
0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1,
|
||||||
|
0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1,
|
||||||
|
0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81,
|
||||||
|
0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a,
|
||||||
|
0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba,
|
||||||
|
0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea,
|
||||||
|
0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda,
|
||||||
|
0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a,
|
||||||
|
0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a,
|
||||||
|
0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a,
|
||||||
|
0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a};
|
||||||
|
static const byte lookup_g9[] = {
|
||||||
|
0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
|
||||||
|
0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7,
|
||||||
|
0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,
|
||||||
|
0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc,
|
||||||
|
0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01,
|
||||||
|
0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91,
|
||||||
|
0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a,
|
||||||
|
0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa,
|
||||||
|
0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b,
|
||||||
|
0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b,
|
||||||
|
0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0,
|
||||||
|
0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30,
|
||||||
|
0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed,
|
||||||
|
0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d,
|
||||||
|
0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6,
|
||||||
|
0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46};
|
||||||
|
static const byte lookup_g11[] = {
|
||||||
|
0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69,
|
||||||
|
0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9,
|
||||||
|
0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12,
|
||||||
|
0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2,
|
||||||
|
0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f,
|
||||||
|
0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f,
|
||||||
|
0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4,
|
||||||
|
0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54,
|
||||||
|
0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e,
|
||||||
|
0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e,
|
||||||
|
0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5,
|
||||||
|
0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55,
|
||||||
|
0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68,
|
||||||
|
0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8,
|
||||||
|
0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13,
|
||||||
|
0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3};
|
||||||
|
static const byte lookup_g13[] = {
|
||||||
|
0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b,
|
||||||
|
0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b,
|
||||||
|
0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0,
|
||||||
|
0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20,
|
||||||
|
0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26,
|
||||||
|
0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6,
|
||||||
|
0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d,
|
||||||
|
0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d,
|
||||||
|
0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91,
|
||||||
|
0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41,
|
||||||
|
0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a,
|
||||||
|
0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa,
|
||||||
|
0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc,
|
||||||
|
0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c,
|
||||||
|
0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47,
|
||||||
|
0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97};
|
||||||
|
static const byte lookup_g14[] = {
|
||||||
|
0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a,
|
||||||
|
0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba,
|
||||||
|
0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81,
|
||||||
|
0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61,
|
||||||
|
0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7,
|
||||||
|
0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17,
|
||||||
|
0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c,
|
||||||
|
0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc,
|
||||||
|
0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b,
|
||||||
|
0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb,
|
||||||
|
0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0,
|
||||||
|
0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20,
|
||||||
|
0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6,
|
||||||
|
0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56,
|
||||||
|
0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d,
|
||||||
|
0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d};
|
||||||
|
|
||||||
|
// Xor's all elements in a n byte array a by b
|
||||||
|
static void xor_s(byte * a, const byte *b, int n) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
a[i] ^= b[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Xor the current cipher state by a specific round key
|
||||||
|
static void xor_round_key(byte *state, const byte *keys, int round) {
|
||||||
|
xor_s(state, keys + round * 16, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the rijndael s-box to all elements in an array
|
||||||
|
// http://en.wikipedia.org/wiki/Rijndael_S-box
|
||||||
|
static void sub_bytes(byte *a, int n) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
a[i] = lookup_sbox[a[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void sub_bytes_inv(byte *a, int n) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
a[i] = lookup_sbox_inv[a[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the core key schedule transform on 4 bytes, as part of the key expansion process
|
||||||
|
// http://en.wikipedia.org/wiki/Rijndael_key_schedule#Key_schedule_core
|
||||||
|
static void key_schedule_core(byte *a, int i) {
|
||||||
|
byte temp = a[0]; // Rotate the output eight bits to the left
|
||||||
|
a[0] = a[1];
|
||||||
|
a[1] = a[2];
|
||||||
|
a[2] = a[3];
|
||||||
|
a[3] = temp;
|
||||||
|
sub_bytes(a, 4); // Apply Rijndael's S-box on all four individual bytes in the output word
|
||||||
|
a[0] ^= lookup_rcon[i]; // On just the first (leftmost) byte of the output word, perform the rcon operation with i
|
||||||
|
// as the input, and exclusive or the rcon output with the first byte of the output word
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand the 16-byte key to 11 round keys (176 bytes)
|
||||||
|
// http://en.wikipedia.org/wiki/Rijndael_key_schedule#The_key_schedule
|
||||||
|
void oqs_aes128_load_schedule_c(const uint8_t *key, void **_schedule) {
|
||||||
|
*_schedule = malloc(16 * 11);
|
||||||
|
assert(*_schedule != NULL);
|
||||||
|
uint8_t *schedule = (uint8_t *) *_schedule;
|
||||||
|
int bytes = 16; // The count of how many bytes we've created so far
|
||||||
|
int i = 1; // The rcon iteration value i is set to 1
|
||||||
|
int j; // For repeating the second stage 3 times
|
||||||
|
byte t[4]; // Temporary working area known as 't' in the Wiki article
|
||||||
|
memcpy(schedule, key, 16); // The first 16 bytes of the expanded key are simply the encryption key
|
||||||
|
|
||||||
|
while (bytes < 176) { // Until we have 176 bytes of expanded key, we do the following:
|
||||||
|
memcpy(t, schedule + bytes - 4, 4); // We assign the value of the previous four bytes in the expanded key to t
|
||||||
|
key_schedule_core(t, i); // We perform the key schedule core on t, with i as the rcon iteration value
|
||||||
|
i++; // We increment i by 1
|
||||||
|
xor_s(t, schedule + bytes - 16, 4); // We exclusive-or t with the four-byte block 16 bytes before the new expanded key.
|
||||||
|
memcpy(schedule + bytes, t, 4); // This becomes the next 4 bytes in the expanded key
|
||||||
|
bytes += 4; // Keep track of how many expanded key bytes we've added
|
||||||
|
|
||||||
|
// We then do the following three times to create the next twelve bytes
|
||||||
|
for (j = 0; j < 3; j++) {
|
||||||
|
memcpy(t, schedule + bytes - 4, 4); // We assign the value of the previous 4 bytes in the expanded key to t
|
||||||
|
xor_s(t, schedule + bytes - 16, 4); // We exclusive-or t with the four-byte block n bytes before
|
||||||
|
memcpy(schedule + bytes, t, 4); // This becomes the next 4 bytes in the expanded key
|
||||||
|
bytes += 4; // Keep track of how many expanded key bytes we've added
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void oqs_aes128_free_schedule_c(void *schedule) {
|
||||||
|
if (schedule != NULL) {
|
||||||
|
free(schedule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the shift rows step on the 16 byte cipher state
|
||||||
|
// http://en.wikipedia.org/wiki/Advanced_Encryption_Standard#The_ShiftRows_step
|
||||||
|
static void shift_rows(byte *state) {
|
||||||
|
int i;
|
||||||
|
byte temp[16];
|
||||||
|
memcpy(temp, state, 16);
|
||||||
|
for (i = 0; i < 16; i++) {
|
||||||
|
state[i] = temp[shift_rows_table[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void shift_rows_inv(byte *state) {
|
||||||
|
int i;
|
||||||
|
byte temp[16];
|
||||||
|
memcpy(temp, state, 16);
|
||||||
|
for (i = 0; i < 16; i++) {
|
||||||
|
state[i] = temp[shift_rows_table_inv[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the mix columns matrix on one column of 4 bytes
|
||||||
|
// http://en.wikipedia.org/wiki/Rijndael_mix_columns
|
||||||
|
static void mix_col(byte *state) {
|
||||||
|
byte a0 = state[0];
|
||||||
|
byte a1 = state[1];
|
||||||
|
byte a2 = state[2];
|
||||||
|
byte a3 = state[3];
|
||||||
|
state[0] = lookup_g2[a0] ^ lookup_g3[a1] ^ a2 ^ a3;
|
||||||
|
state[1] = lookup_g2[a1] ^ lookup_g3[a2] ^ a3 ^ a0;
|
||||||
|
state[2] = lookup_g2[a2] ^ lookup_g3[a3] ^ a0 ^ a1;
|
||||||
|
state[3] = lookup_g2[a3] ^ lookup_g3[a0] ^ a1 ^ a2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the mix columns matrix on each column of the 16 bytes
|
||||||
|
static void mix_cols(byte *state) {
|
||||||
|
mix_col(state);
|
||||||
|
mix_col(state + 4);
|
||||||
|
mix_col(state + 8);
|
||||||
|
mix_col(state + 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the inverse mix columns matrix on one column of 4 bytes
|
||||||
|
// http://en.wikipedia.org/wiki/Rijndael_mix_columns
|
||||||
|
static void mix_col_inv(byte *state) {
|
||||||
|
byte a0 = state[0];
|
||||||
|
byte a1 = state[1];
|
||||||
|
byte a2 = state[2];
|
||||||
|
byte a3 = state[3];
|
||||||
|
state[0] = lookup_g14[a0] ^ lookup_g9[a3] ^ lookup_g13[a2] ^ lookup_g11[a1];
|
||||||
|
state[1] = lookup_g14[a1] ^ lookup_g9[a0] ^ lookup_g13[a3] ^ lookup_g11[a2];
|
||||||
|
state[2] = lookup_g14[a2] ^ lookup_g9[a1] ^ lookup_g13[a0] ^ lookup_g11[a3];
|
||||||
|
state[3] = lookup_g14[a3] ^ lookup_g9[a2] ^ lookup_g13[a1] ^ lookup_g11[a0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the inverse mix columns matrix on each column of the 16 bytes
|
||||||
|
static void mix_cols_inv(byte *state) {
|
||||||
|
mix_col_inv(state);
|
||||||
|
mix_col_inv(state + 4);
|
||||||
|
mix_col_inv(state + 8);
|
||||||
|
mix_col_inv(state + 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
void oqs_aes128_enc_c(const uint8_t *plaintext, const void *_schedule, uint8_t *ciphertext) {
|
||||||
|
const uint8_t *schedule = (const uint8_t *) _schedule;
|
||||||
|
int i; // To count the rounds
|
||||||
|
|
||||||
|
// First Round
|
||||||
|
memcpy(ciphertext, plaintext, 16);
|
||||||
|
xor_round_key(ciphertext, schedule, 0);
|
||||||
|
|
||||||
|
// Middle rounds
|
||||||
|
for (i = 0; i < 9; i++) {
|
||||||
|
sub_bytes(ciphertext, 16);
|
||||||
|
shift_rows(ciphertext);
|
||||||
|
mix_cols(ciphertext);
|
||||||
|
xor_round_key(ciphertext, schedule, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final Round
|
||||||
|
sub_bytes(ciphertext, 16);
|
||||||
|
shift_rows(ciphertext);
|
||||||
|
xor_round_key(ciphertext, schedule, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void oqs_aes128_dec_c(const uint8_t *ciphertext, const void *_schedule, uint8_t *plaintext) {
|
||||||
|
const uint8_t *schedule = (const uint8_t *) _schedule;
|
||||||
|
int i; // To count the rounds
|
||||||
|
|
||||||
|
// Reverse the final Round
|
||||||
|
memcpy(plaintext, ciphertext, 16);
|
||||||
|
xor_round_key(plaintext, schedule, 10);
|
||||||
|
shift_rows_inv(plaintext);
|
||||||
|
sub_bytes_inv(plaintext, 16);
|
||||||
|
|
||||||
|
// Reverse the middle rounds
|
||||||
|
for (i = 0; i < 9; i++) {
|
||||||
|
xor_round_key(plaintext, schedule, 9 - i);
|
||||||
|
mix_cols_inv(plaintext);
|
||||||
|
shift_rows_inv(plaintext);
|
||||||
|
sub_bytes_inv(plaintext, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse the first Round
|
||||||
|
xor_round_key(plaintext, schedule, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's not enc nor dec, it's something in between
|
||||||
|
void oqs_mhy128_enc_c(const uint8_t *plaintext, const void *_schedule, uint8_t *ciphertext) {
|
||||||
|
const uint8_t *schedule = (const uint8_t *) _schedule;
|
||||||
|
int i; // To count the rounds
|
||||||
|
|
||||||
|
// First Round
|
||||||
|
memcpy(ciphertext, plaintext, 16);
|
||||||
|
xor_round_key(ciphertext, schedule, 0);
|
||||||
|
|
||||||
|
// Middle rounds
|
||||||
|
for (i = 0; i < 9; i++) {
|
||||||
|
sub_bytes_inv(ciphertext, 16);
|
||||||
|
shift_rows_inv(ciphertext);
|
||||||
|
mix_cols_inv(ciphertext);
|
||||||
|
xor_round_key(ciphertext, schedule, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final Round
|
||||||
|
sub_bytes_inv(ciphertext, 16);
|
||||||
|
shift_rows_inv(ciphertext);
|
||||||
|
xor_round_key(ciphertext, schedule, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
void oqs_mhy128_dec_c(const uint8_t *ciphertext, const void *_schedule, uint8_t *plaintext) {
|
||||||
|
const uint8_t *schedule = (const uint8_t *) _schedule;
|
||||||
|
int i; // To count the rounds
|
||||||
|
|
||||||
|
// Reverse the final Round
|
||||||
|
memcpy(plaintext, ciphertext, 16);
|
||||||
|
xor_round_key(plaintext, schedule, 10);
|
||||||
|
shift_rows(plaintext);
|
||||||
|
sub_bytes(plaintext, 16);
|
||||||
|
|
||||||
|
// Reverse the middle rounds
|
||||||
|
for (i = 0; i < 9; i++) {
|
||||||
|
xor_round_key(plaintext, schedule, 9 - i);
|
||||||
|
mix_cols(plaintext);
|
||||||
|
shift_rows(plaintext);
|
||||||
|
sub_bytes(plaintext, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse the first Round
|
||||||
|
xor_round_key(plaintext, schedule, 0);
|
||||||
|
}
|
||||||
66
src-tauri/mhycrypto/aes.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* \file aes.h
|
||||||
|
* \brief Header defining the API for OQS AES
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __OQS_AES_H
|
||||||
|
#define __OQS_AES_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to fill a key schedule given an initial key.
|
||||||
|
*
|
||||||
|
* @param key Initial Key.
|
||||||
|
* @param schedule Abstract data structure for a key schedule.
|
||||||
|
* @param forEncryption 1 if key schedule is for encryption, 0 if for decryption.
|
||||||
|
*/
|
||||||
|
void OQS_AES128_load_schedule(const uint8_t *key, void **schedule, int for_encryption);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to free a key schedule.
|
||||||
|
*
|
||||||
|
* @param schedule Schedule generated with OQS_AES128_load_schedule().
|
||||||
|
*/
|
||||||
|
void OQS_AES128_free_schedule(void *schedule);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to encrypt blocks of plaintext using ECB mode.
|
||||||
|
* A schedule based on the key is generated and used internally.
|
||||||
|
*
|
||||||
|
* @param plaintext Plaintext to be encrypted.
|
||||||
|
* @param plaintext_len Length on the plaintext in bytes. Must be a multiple of 16.
|
||||||
|
* @param key Key to be used for encryption.
|
||||||
|
* @param ciphertext Pointer to a block of memory which >= in size to the plaintext block. The result will be written here.
|
||||||
|
*/
|
||||||
|
void OQS_AES128_ECB_enc(const uint8_t *plaintext, const size_t plaintext_len, const uint8_t *key, uint8_t *ciphertext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to decrypt blocks of plaintext using ECB mode.
|
||||||
|
* A schedule based on the key is generated and used internally.
|
||||||
|
*
|
||||||
|
* @param ciphertext Ciphertext to be decrypted.
|
||||||
|
* @param ciphertext_len Length on the ciphertext in bytes. Must be a multiple of 16.
|
||||||
|
* @param key Key to be used for encryption.
|
||||||
|
* @param ciphertext Pointer to a block of memory which >= in size to the ciphertext block. The result will be written here.
|
||||||
|
*/
|
||||||
|
void OQS_AES128_ECB_dec(const uint8_t *ciphertext, const size_t ciphertext_len, const uint8_t *key, uint8_t *plaintext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as OQS_AES128_ECB_enc() except a schedule generated by
|
||||||
|
* OQS_AES128_load_schedule() is passed rather then a key. This is faster
|
||||||
|
* if the same schedule is used for multiple encryptions since it does
|
||||||
|
* not have to be regenerated from the key.
|
||||||
|
*/
|
||||||
|
void OQS_AES128_ECB_enc_sch(const uint8_t *plaintext, const size_t plaintext_len, const void *schedule, uint8_t *ciphertext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as OQS_AES128_ECB_dec() except a schedule generated by
|
||||||
|
* OQS_AES128_load_schedule() is passed rather then a key. This is faster
|
||||||
|
* if the same schedule is used for multiple encryptions since it does
|
||||||
|
* not have to be regenerated from the key.
|
||||||
|
*/
|
||||||
|
void OQS_AES128_ECB_dec_sch(const uint8_t *ciphertext, const size_t ciphertext_len, const void *schedule, uint8_t *plaintext);
|
||||||
|
|
||||||
|
#endif
|
||||||
31
src-tauri/mhycrypto/memecrypto.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#include "memecrypto.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
extern "C" void oqs_mhy128_enc_c(const uint8_t *plaintext, const void *_schedule, uint8_t *ciphertext);
|
||||||
|
extern "C" void oqs_mhy128_dec_c(const uint8_t *ciphertext, const void *_schedule, uint8_t *plaintext);
|
||||||
|
|
||||||
|
static uint8_t dexor16(const uint8_t *c) {
|
||||||
|
uint8_t ret = 0;
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
ret ^= c[i];
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void memecrypto_prepare_key(const uint8_t *in, uint8_t *out) {
|
||||||
|
for (int i = 0; i < 0xB0; i++)
|
||||||
|
out[i] = dexor16(&in[0x10 * i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void memecrypto_decrypt(const uint8_t *key, uint8_t *data) {
|
||||||
|
uint8_t plaintext[16];
|
||||||
|
oqs_mhy128_enc_c(data, key, plaintext);
|
||||||
|
memcpy(data, plaintext, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
void memecrypto_encrypt(const uint8_t *key, uint8_t *data) {
|
||||||
|
uint8_t ciphertext[16];
|
||||||
|
oqs_mhy128_dec_c(data, key, ciphertext);
|
||||||
|
memcpy(data, ciphertext, 16);
|
||||||
|
}
|
||||||
12
src-tauri/mhycrypto/memecrypto.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef MEMECRYPTO_H
|
||||||
|
#define MEMECRYPTO_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
void memecrypto_prepare_key(const uint8_t *in, uint8_t *out);
|
||||||
|
|
||||||
|
void memecrypto_decrypt(const uint8_t *key, uint8_t *data);
|
||||||
|
|
||||||
|
void memecrypto_encrypt(const uint8_t *key, uint8_t *data);
|
||||||
|
|
||||||
|
#endif //MEMECRYPTO_H
|
||||||
146
src-tauri/mhycrypto/metadata.cpp
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
#include "metadata.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <random>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "memecrypto.h"
|
||||||
|
#include "metadatastringdec.h"
|
||||||
|
|
||||||
|
unsigned char initial_prev_xor[] = { 0xad, 0x2f, 0x42, 0x30, 0x67, 0x04, 0xb0, 0x9c, 0x9d, 0x2a, 0xc0, 0xba, 0x0e, 0xbf, 0xa5, 0x68 };
|
||||||
|
|
||||||
|
bool get_global_metadata_keys(uint8_t *src, size_t srcn, uint8_t *longkey, uint8_t *shortkey) {
|
||||||
|
if (srcn != 0x4000)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (*(uint16_t *) (src + 0xc8) != 0xfc2e || *(uint16_t *) (src + 0xca) != 0x2cfe)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto offB00 = *(uint16_t *) (src + 0xd2);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 16; i++)
|
||||||
|
shortkey[i] = src[offB00 + i] ^ src[0x3000 + i];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 0xb00; i++)
|
||||||
|
longkey[i] = src[offB00 + 0x10 + i] ^ src[0x3000 + 0x10 + i] ^ shortkey[i % 16];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gen_global_metadata_key(uint8_t* src, size_t srcn) {
|
||||||
|
if (srcn != 0x4000)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
std::vector<uint8_t> read_file(const char* n);
|
||||||
|
auto data = read_file("xorpad.bin");
|
||||||
|
memcpy(src, data.data(), 0x4000);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::mt19937_64 rand (0xDEADBEEF);
|
||||||
|
|
||||||
|
uint64_t* key = (uint64_t*)src;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < srcn / sizeof(uint64_t); i++)
|
||||||
|
key[i] = rand();
|
||||||
|
|
||||||
|
*(uint16_t *) (src + 0xc8) = 0xfc2e; // Magic
|
||||||
|
*(uint16_t *) (src + 0xca) = 0x2cfe; // Magic
|
||||||
|
*(uint16_t *) (src + 0xd2) = rand() & 0x1FFFu; // Just some random value
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void decrypt_global_metadata_inner(uint8_t *data, size_t size) {
|
||||||
|
uint8_t longkey[0xB00];
|
||||||
|
uint8_t longkeyp[0xB0];
|
||||||
|
uint8_t shortkey[16];
|
||||||
|
get_global_metadata_keys(data + size - 0x4000, 0x4000, longkey, shortkey);
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
shortkey[i] ^= initial_prev_xor[i];
|
||||||
|
memecrypto_prepare_key(longkey, longkeyp);
|
||||||
|
|
||||||
|
auto perentry = (uint32_t) (size / 0x100 / 0x40);
|
||||||
|
for (int i = 0; i < 0x100; i++) {
|
||||||
|
auto off = (0x40u * perentry) * i;
|
||||||
|
|
||||||
|
uint8_t prev[16];
|
||||||
|
memcpy(prev, shortkey, 16);
|
||||||
|
for (int j = 0; j < 4; j++) {
|
||||||
|
uint8_t curr[16];
|
||||||
|
memcpy(curr, &data[off + j * 0x10], 16);
|
||||||
|
|
||||||
|
memecrypto_decrypt(longkeyp, curr);
|
||||||
|
|
||||||
|
for (int k = 0; k < 16; k++)
|
||||||
|
curr[k] ^= prev[k];
|
||||||
|
|
||||||
|
memcpy(prev, &data[off + j * 0x10], 16);
|
||||||
|
memcpy(&data[off + j * 0x10], curr, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t literal_dec_key[0x5000];
|
||||||
|
recrypt_global_metadata_header_string_fields(data, size, literal_dec_key);
|
||||||
|
recrypt_global_metadata_header_string_literals(data, size, literal_dec_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
generate_key_for_global_metadata_header_string(data, size, literal_dec_key);
|
||||||
|
|
||||||
|
recrypt_global_metadata_header_string_literals(data, size, literal_dec_key);
|
||||||
|
recrypt_global_metadata_header_string_fields(data, size, literal_dec_key);
|
||||||
|
|
||||||
|
uint8_t longkey[0xB00];
|
||||||
|
uint8_t longkeyp[0xB0];
|
||||||
|
uint8_t shortkey[16];
|
||||||
|
|
||||||
|
get_global_metadata_keys(data + size - 0x4000, 0x4000, longkey, shortkey);
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
shortkey[i] ^= initial_prev_xor[i];
|
||||||
|
memecrypto_prepare_key(longkey, longkeyp);
|
||||||
|
|
||||||
|
auto perentry = (uint32_t) (size / 0x100 / 0x40);
|
||||||
|
for (int i = 0; i < 0x100; i++) {
|
||||||
|
auto off = (0x40u * perentry) * i;
|
||||||
|
|
||||||
|
uint8_t prev[16];
|
||||||
|
memcpy(prev, shortkey, 16);
|
||||||
|
for (int j = 0; j < 4; j++) {
|
||||||
|
uint8_t curr[16];
|
||||||
|
memcpy(curr, &data[off + j * 0x10], 16);
|
||||||
|
|
||||||
|
for (int k = 0; k < 16; k++)
|
||||||
|
curr[k] ^= prev[k];
|
||||||
|
|
||||||
|
memecrypto_encrypt(longkeyp, curr);
|
||||||
|
|
||||||
|
memcpy(prev, curr, 16);
|
||||||
|
memcpy(&data[off + j * 0x10], curr, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" int encrypt_global_metadata(uint8_t* data, size_t size) {
|
||||||
|
try {
|
||||||
|
encrypt_global_metadata_inner(data, size);
|
||||||
|
return 0;
|
||||||
|
} catch (...) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src-tauri/mhycrypto/metadata.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef METADATA_H
|
||||||
|
#define METADATA_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
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
|
||||||
121
src-tauri/mhycrypto/metadatastringdec.cpp
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#include "metadatastringdec.h"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <random>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
struct m_header_fields {
|
||||||
|
char filler1[0x18];
|
||||||
|
uint32_t stringLiteralDataOffset; // 18
|
||||||
|
uint32_t stringLiteralDataCount; // 1c
|
||||||
|
uint32_t stringLiteralOffset; // 20
|
||||||
|
uint32_t stringLiteralCount; // 24
|
||||||
|
char filler2[0xd8 - 0x28];
|
||||||
|
uint32_t stringOffset, stringCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct m_literal {
|
||||||
|
uint32_t offset, length;
|
||||||
|
};
|
||||||
|
|
||||||
|
void generate_key_for_global_metadata_header_string(uint8_t* data, size_t len, uint8_t* literal_dec_key) {
|
||||||
|
if (len < sizeof(m_header_fields))
|
||||||
|
throw std::out_of_range("data not big enough for global metadata header");
|
||||||
|
|
||||||
|
uint32_t values[0x12] = {
|
||||||
|
*(uint32_t *) (data + 0x60),
|
||||||
|
*(uint32_t *) (data + 0x64),
|
||||||
|
*(uint32_t *) (data + 0x68),
|
||||||
|
*(uint32_t *) (data + 0x6c),
|
||||||
|
*(uint32_t *) (data + 0x140),
|
||||||
|
*(uint32_t *) (data + 0x144),
|
||||||
|
*(uint32_t *) (data + 0x148),
|
||||||
|
*(uint32_t *) (data + 0x14c),
|
||||||
|
*(uint32_t *) (data + 0x100),
|
||||||
|
*(uint32_t *) (data + 0x104),
|
||||||
|
*(uint32_t *) (data + 0x108),
|
||||||
|
*(uint32_t *) (data + 0x10c),
|
||||||
|
*(uint32_t *) (data + 0xf0),
|
||||||
|
*(uint32_t *) (data + 0xf4),
|
||||||
|
*(uint32_t *) (data + 8),
|
||||||
|
*(uint32_t *) (data + 0xc),
|
||||||
|
*(uint32_t *) (data + 0x10),
|
||||||
|
*(uint32_t *) (data + 0x14)
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t seed = ((uint64_t) values[values[0] & 0xfu] << 0x20u) | values[(values[0x11] & 0xf) + 2];
|
||||||
|
|
||||||
|
std::mt19937_64 rand (seed);
|
||||||
|
|
||||||
|
for (int i = 0; i < 6; i++) // Skip
|
||||||
|
rand();
|
||||||
|
|
||||||
|
auto key64 = (uint64_t *) literal_dec_key;
|
||||||
|
for (int i = 0; i < 0xa00; i++)
|
||||||
|
key64[i] = rand();
|
||||||
|
}
|
||||||
|
|
||||||
|
void recrypt_global_metadata_header_string_fields(uint8_t *data, size_t len, uint8_t *literal_dec_key) {
|
||||||
|
if (len < sizeof(m_header_fields))
|
||||||
|
throw std::out_of_range("data not big enough for global metadata header");
|
||||||
|
|
||||||
|
uint32_t values[0x12] = {
|
||||||
|
*(uint32_t *) (data + 0x60),
|
||||||
|
*(uint32_t *) (data + 0x64),
|
||||||
|
*(uint32_t *) (data + 0x68),
|
||||||
|
*(uint32_t *) (data + 0x6c),
|
||||||
|
*(uint32_t *) (data + 0x140),
|
||||||
|
*(uint32_t *) (data + 0x144),
|
||||||
|
*(uint32_t *) (data + 0x148),
|
||||||
|
*(uint32_t *) (data + 0x14c),
|
||||||
|
*(uint32_t *) (data + 0x100),
|
||||||
|
*(uint32_t *) (data + 0x104),
|
||||||
|
*(uint32_t *) (data + 0x108),
|
||||||
|
*(uint32_t *) (data + 0x10c),
|
||||||
|
*(uint32_t *) (data + 0xf0),
|
||||||
|
*(uint32_t *) (data + 0xf4),
|
||||||
|
*(uint32_t *) (data + 8),
|
||||||
|
*(uint32_t *) (data + 0xc),
|
||||||
|
*(uint32_t *) (data + 0x10),
|
||||||
|
*(uint32_t *) (data + 0x14)
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t seed = ((uint64_t) values[values[0] & 0xfu] << 0x20u) | values[(values[0x11] & 0xf) + 2];
|
||||||
|
|
||||||
|
std::mt19937_64 rand (seed);
|
||||||
|
|
||||||
|
auto header = (m_header_fields *) data;
|
||||||
|
header->stringCount ^= (uint32_t) rand();
|
||||||
|
header->stringOffset ^= (uint32_t) rand();
|
||||||
|
rand();
|
||||||
|
header->stringLiteralOffset ^= (uint32_t) rand();
|
||||||
|
header->stringLiteralDataCount ^= (uint32_t) rand();
|
||||||
|
header->stringLiteralDataOffset ^= (uint32_t) rand();
|
||||||
|
|
||||||
|
auto key64 = (uint64_t *) literal_dec_key;
|
||||||
|
for (int i = 0; i < 0xa00; i++)
|
||||||
|
key64[i] = rand();
|
||||||
|
}
|
||||||
|
|
||||||
|
void recrypt_global_metadata_header_string_literals(uint8_t *data, size_t len, uint8_t *literal_dec_key) {
|
||||||
|
if (len < sizeof(m_header_fields))
|
||||||
|
throw std::out_of_range("data not big enough for global metadata header");
|
||||||
|
|
||||||
|
auto header = (m_header_fields *) data;
|
||||||
|
if ((size_t) header->stringLiteralCount + header->stringLiteralOffset > len)
|
||||||
|
throw std::out_of_range("file trimmed or string literal offset/count field invalid");
|
||||||
|
|
||||||
|
auto literals = (m_literal *) (data + header->stringLiteralOffset);
|
||||||
|
auto count = header->stringLiteralCount / sizeof(m_literal);
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
auto slen = literals[i].length;
|
||||||
|
uint8_t *str = data + header->stringLiteralDataOffset + literals[i].offset;
|
||||||
|
uint8_t *okey = literal_dec_key + (i % 0x2800);
|
||||||
|
|
||||||
|
if ((size_t) header->stringLiteralDataOffset + literals[i].offset + slen > len)
|
||||||
|
throw std::out_of_range("file trimmed or contains invalid string entry");
|
||||||
|
|
||||||
|
for (size_t j = 0; j < slen; j++)
|
||||||
|
str[j] ^= literal_dec_key[(j + 0x1400u) % 0x5000u] ^ (okey[j % 0x2800u] + (uint8_t) j);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src-tauri/mhycrypto/metadatastringdec.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef METADATASTRINGDEC_H
|
||||||
|
#define METADATASTRINGDEC_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
void recrypt_global_metadata_header_string_fields(uint8_t *data, size_t len, uint8_t *literal_dec_key);
|
||||||
|
|
||||||
|
void recrypt_global_metadata_header_string_literals(uint8_t *data, size_t len, uint8_t *literal_dec_key);
|
||||||
|
|
||||||
|
void generate_key_for_global_metadata_header_string(uint8_t* data, size_t len, uint8_t* literal_dec_key);
|
||||||
|
|
||||||
|
#endif //METADATASTRINGDEC_H
|
||||||
4
src-tauri/rustfmt.toml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
newline_style = "Unix"
|
||||||
|
tab_spaces = 2
|
||||||
|
use_field_init_shorthand = true
|
||||||
|
use_try_shorthand = true
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
|
|
||||||
@@ -15,17 +15,14 @@ static DOWNLOADS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new())
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Result<(), String> {
|
pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Result<(), String> {
|
||||||
// Reqwest setup
|
// Reqwest setup
|
||||||
let res = match reqwest::get(url)
|
let res = match reqwest::get(url).await {
|
||||||
.await {
|
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
emit_download_err(window, format!("Failed to request {}", url), path);
|
emit_download_err(window, format!("Failed to request {}", url), path);
|
||||||
return Err(format!("Failed to request {}", url));
|
return Err(format!("Failed to request {}", url));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let total_size = res
|
let total_size = res.content_length().unwrap_or(0);
|
||||||
.content_length()
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
// Create file path
|
// Create file path
|
||||||
let mut file = match File::create(path) {
|
let mut file = match File::create(path) {
|
||||||
@@ -77,25 +74,10 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu
|
|||||||
|
|
||||||
let mut res_hash = std::collections::HashMap::new();
|
let mut res_hash = std::collections::HashMap::new();
|
||||||
|
|
||||||
res_hash.insert(
|
res_hash.insert("downloaded".to_string(), downloaded.to_string());
|
||||||
"downloaded".to_string(),
|
res_hash.insert("total".to_string(), total_size.to_string());
|
||||||
downloaded.to_string(),
|
res_hash.insert("path".to_string(), path.to_string());
|
||||||
);
|
res_hash.insert("total_downloaded".to_string(), total_downloaded.to_string());
|
||||||
|
|
||||||
res_hash.insert(
|
|
||||||
"total".to_string(),
|
|
||||||
total_size.to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
res_hash.insert(
|
|
||||||
"path".to_string(),
|
|
||||||
path.to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
res_hash.insert(
|
|
||||||
"total_downloaded".to_string(),
|
|
||||||
total_downloaded.to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create event to send to frontend
|
// Create event to send to frontend
|
||||||
window.emit("download_progress", &res_hash).unwrap();
|
window.emit("download_progress", &res_hash).unwrap();
|
||||||
@@ -111,15 +93,8 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu
|
|||||||
pub fn emit_download_err(window: tauri::Window, msg: String, path: &str) {
|
pub fn emit_download_err(window: tauri::Window, msg: String, path: &str) {
|
||||||
let mut res_hash = std::collections::HashMap::new();
|
let mut res_hash = std::collections::HashMap::new();
|
||||||
|
|
||||||
res_hash.insert(
|
res_hash.insert("error".to_string(), msg);
|
||||||
"error".to_string(),
|
res_hash.insert("path".to_string(), path.to_string());
|
||||||
msg,
|
|
||||||
);
|
|
||||||
|
|
||||||
res_hash.insert(
|
|
||||||
"path".to_string(),
|
|
||||||
path.to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
window.emit("download_error", &res_hash).unwrap();
|
window.emit("download_error", &res_hash).unwrap();
|
||||||
}
|
}
|
||||||
@@ -139,4 +114,4 @@ pub fn stop_download(path: String) {
|
|||||||
if let Err(_e) = std::fs::remove_file(&path) {
|
if let Err(_e) = std::fs::remove_file(&path) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
use file_diff::diff;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn rename(path: String, new_name: String) {
|
pub fn rename(path: String, new_name: String) {
|
||||||
@@ -19,25 +21,39 @@ pub fn rename(path: String, new_name: String) {
|
|||||||
fs::rename(path, &path_replaced).unwrap();
|
fs::rename(path, &path_replaced).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn dir_create(path: String) {
|
||||||
|
fs::create_dir_all(path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn dir_exists(path: &str) -> bool {
|
pub fn dir_exists(path: &str) -> bool {
|
||||||
fs::metadata(&path).is_ok()
|
let path_buf = std::path::PathBuf::from(path);
|
||||||
|
fs::metadata(path_buf).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn dir_is_empty(path: &str) -> bool {
|
pub fn dir_is_empty(path: &str) -> bool {
|
||||||
fs::read_dir(&path).unwrap().count() == 0
|
let path_buf = std::path::PathBuf::from(path);
|
||||||
|
fs::read_dir(path_buf).unwrap().count() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn dir_delete(path: &str) {
|
pub fn dir_delete(path: &str) {
|
||||||
fs::remove_dir_all(path).unwrap();
|
let path_buf = std::path::PathBuf::from(path);
|
||||||
|
fs::remove_dir_all(path_buf).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn are_files_identical(path1: &str, path2: &str) -> bool {
|
||||||
|
diff(path1, path2)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn copy_file(path: String, new_path: String) -> bool {
|
pub fn copy_file(path: String, new_path: String) -> bool {
|
||||||
let filename = &path.split("/").last().unwrap();
|
let filename = &path.split('/').last().unwrap();
|
||||||
let mut new_path_buf = std::path::PathBuf::from(&new_path);
|
let mut new_path_buf = std::path::PathBuf::from(&new_path);
|
||||||
|
let path_buf = std::path::PathBuf::from(&path);
|
||||||
|
|
||||||
// If the new path doesn't exist, create it.
|
// If the new path doesn't exist, create it.
|
||||||
if !dir_exists(new_path_buf.pop().to_string().as_str()) {
|
if !dir_exists(new_path_buf.pop().to_string().as_str()) {
|
||||||
@@ -45,11 +61,96 @@ pub fn copy_file(path: String, new_path: String) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy old to new
|
// 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,
|
Ok(_) => true,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Failed to copy file: {}", e);
|
println!("Failed to copy file: {}", e);
|
||||||
|
println!("Path: {}", path);
|
||||||
|
println!("New Path: {}", new_path);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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 path_buf = std::path::PathBuf::from(&path);
|
||||||
|
|
||||||
|
// If the new path doesn't exist, create it.
|
||||||
|
if !dir_exists(new_path_buf.pop().to_string().as_str()) {
|
||||||
|
match std::fs::create_dir_all(&new_path) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to create directory: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy old to new
|
||||||
|
match std::fs::copy(&path_buf, format!("{}/{}", new_path, new_name)) {
|
||||||
|
Ok(_) => true,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to copy file: {}", e);
|
||||||
|
println!("Path: {}", path);
|
||||||
|
println!("New Path: {}", new_path);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn delete_file(path: String) -> bool {
|
||||||
|
let path_buf = std::path::PathBuf::from(&path);
|
||||||
|
|
||||||
|
match std::fs::remove_file(path_buf) {
|
||||||
|
Ok(_) => true,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to delete file: {}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn read_file(path: String) -> String {
|
||||||
|
let path_buf = std::path::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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut contents = String::new();
|
||||||
|
file.read_to_string(&mut contents).unwrap();
|
||||||
|
|
||||||
|
contents
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn write_file(path: String, contents: String) {
|
||||||
|
let path_buf = std::path::PathBuf::from(&path);
|
||||||
|
|
||||||
|
// Create file if it exists, otherwise just open and rewrite
|
||||||
|
let mut file = match fs::File::create(&path_buf) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to open file: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write contents to file
|
||||||
|
match file.write_all(contents.as_bytes()) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to write to file: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
88
src-tauri/src/gamebanana.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
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 {
|
||||||
|
let res = web::query(format!("{}/apiv9/Mod/{}/DownloadPage", SITE_URL, mod_id).as_str()).await;
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn list_submissions(mode: String) -> String {
|
||||||
|
let res = web::query(
|
||||||
|
format!(
|
||||||
|
"{}/apiv9/Util/Game/Submissions?_idGameRow=8552&_nPage=1&_nPerpage=50&_sMode={}",
|
||||||
|
SITE_URL, mode
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
use std::path::{Path, PathBuf};
|
|
||||||
use crate::system_helpers::*;
|
use crate::system_helpers::*;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_lang(window: tauri::Window, lang: String) -> String {
|
pub async fn get_lang(window: tauri::Window, lang: String) -> String {
|
||||||
let lang = lang.to_lowercase();
|
let lang = lang.to_lowercase();
|
||||||
|
|
||||||
// Send contents of language file back
|
// Send contents of language file back
|
||||||
let lang_path: PathBuf = [&install_location(), "lang", &format!("{}.json", lang)].iter().collect();
|
let lang_path: PathBuf = [&install_location(), "lang", &format!("{}.json", lang)]
|
||||||
|
.iter()
|
||||||
|
.collect();
|
||||||
match std::fs::read_to_string(&lang_path) {
|
match std::fs::read_to_string(&lang_path) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -45,10 +47,7 @@ pub async fn get_languages() -> std::collections::HashMap<String, String> {
|
|||||||
pub fn emit_lang_err(window: tauri::Window, msg: String) {
|
pub fn emit_lang_err(window: tauri::Window, msg: String) {
|
||||||
let mut res_hash = std::collections::HashMap::new();
|
let mut res_hash = std::collections::HashMap::new();
|
||||||
|
|
||||||
res_hash.insert(
|
res_hash.insert("error".to_string(), msg);
|
||||||
"error".to_string(),
|
|
||||||
msg,
|
|
||||||
);
|
|
||||||
|
|
||||||
window.emit("lang_error", &res_hash).unwrap();
|
window.emit("lang_error", &res_hash).unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
#![cfg_attr(
|
#![cfg_attr(
|
||||||
all(not(debug_assertions), target_os = "windows"),
|
all(not(debug_assertions), target_os = "windows"),
|
||||||
windows_subsystem = "windows"
|
windows_subsystem = "windows"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::{sync::Mutex, collections::HashMap};
|
use std::{collections::HashMap, sync::Mutex};
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use structs::APIQuery;
|
||||||
use sysinfo::{System, SystemExt};
|
use sysinfo::{System, SystemExt};
|
||||||
use structs::{APIQuery};
|
|
||||||
|
|
||||||
|
mod downloader;
|
||||||
|
mod file_helpers;
|
||||||
|
mod gamebanana;
|
||||||
|
mod lang;
|
||||||
|
mod metadata_patcher;
|
||||||
|
mod proxy;
|
||||||
mod structs;
|
mod structs;
|
||||||
mod system_helpers;
|
mod system_helpers;
|
||||||
mod file_helpers;
|
|
||||||
mod unzip;
|
mod unzip;
|
||||||
mod downloader;
|
|
||||||
mod lang;
|
|
||||||
mod proxy;
|
|
||||||
mod web;
|
mod web;
|
||||||
|
|
||||||
static WATCH_GAME_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
|
static WATCH_GAME_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Start the game process watcher.
|
|
||||||
process_watcher();
|
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
enable_process_watcher,
|
enable_process_watcher,
|
||||||
@@ -37,6 +35,7 @@ fn main() {
|
|||||||
get_theme_list,
|
get_theme_list,
|
||||||
system_helpers::run_command,
|
system_helpers::run_command,
|
||||||
system_helpers::run_program,
|
system_helpers::run_program,
|
||||||
|
system_helpers::run_program_relative,
|
||||||
system_helpers::run_jar,
|
system_helpers::run_jar,
|
||||||
system_helpers::open_in_browser,
|
system_helpers::open_in_browser,
|
||||||
system_helpers::install_location,
|
system_helpers::install_location,
|
||||||
@@ -45,30 +44,55 @@ fn main() {
|
|||||||
proxy::generate_ca_files,
|
proxy::generate_ca_files,
|
||||||
unzip::unzip,
|
unzip::unzip,
|
||||||
file_helpers::rename,
|
file_helpers::rename,
|
||||||
|
file_helpers::dir_create,
|
||||||
file_helpers::dir_exists,
|
file_helpers::dir_exists,
|
||||||
file_helpers::dir_is_empty,
|
file_helpers::dir_is_empty,
|
||||||
file_helpers::dir_delete,
|
file_helpers::dir_delete,
|
||||||
file_helpers::copy_file,
|
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::download_file,
|
||||||
downloader::stop_download,
|
downloader::stop_download,
|
||||||
lang::get_lang,
|
lang::get_lang,
|
||||||
lang::get_languages,
|
lang::get_languages,
|
||||||
web::valid_url
|
web::valid_url,
|
||||||
|
web::web_get,
|
||||||
|
gamebanana::get_download_links,
|
||||||
|
gamebanana::list_submissions,
|
||||||
|
gamebanana::list_mods,
|
||||||
|
metadata_patcher::patch_metadata
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_watcher() {
|
#[tauri::command]
|
||||||
// Every 5 seconds, see if the game process is still running.
|
fn is_game_running() -> bool {
|
||||||
// If it is not, then we assume the game has closed and disable the proxy
|
// Grab the game process name
|
||||||
// to prevent any requests from being sent to the game.
|
let proc = WATCH_GAME_PROCESS.lock().unwrap().to_string();
|
||||||
|
|
||||||
// Start a thread so as to not block the main thread.
|
!proc.is_empty()
|
||||||
thread::spawn(|| {
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn enable_process_watcher(window: tauri::Window, process: String) {
|
||||||
|
*WATCH_GAME_PROCESS.lock().unwrap() = process;
|
||||||
|
|
||||||
|
window.listen("disable_process_watcher", |_e| {
|
||||||
|
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("Starting process watcher...");
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
let mut system = System::new_all();
|
let mut system = System::new_all();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
thread::sleep(std::time::Duration::from_secs(5));
|
||||||
|
|
||||||
// Refresh system info
|
// Refresh system info
|
||||||
system.refresh_all();
|
system.refresh_all();
|
||||||
|
|
||||||
@@ -81,28 +105,19 @@ fn process_watcher() {
|
|||||||
|
|
||||||
// If the game process closes, disable the proxy.
|
// If the game process closes, disable the proxy.
|
||||||
if !exists {
|
if !exists {
|
||||||
|
println!("Game closed");
|
||||||
|
|
||||||
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
|
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
|
||||||
disconnect();
|
disconnect();
|
||||||
|
|
||||||
|
window.emit("game_closed", &()).unwrap();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
thread::sleep(std::time::Duration::from_secs(5));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn is_game_running() -> bool {
|
|
||||||
// Grab the game process name
|
|
||||||
let proc = WATCH_GAME_PROCESS.lock().unwrap().to_string();
|
|
||||||
|
|
||||||
!proc.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn enable_process_watcher(process: String) {
|
|
||||||
*WATCH_GAME_PROCESS.lock().unwrap() = process;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn connect(port: u16, certificate_path: String) {
|
async fn connect(port: u16, certificate_path: String) {
|
||||||
// Log message to console.
|
// Log message to console.
|
||||||
@@ -156,7 +171,7 @@ async fn get_theme_list(data_dir: String) -> Vec<HashMap<String, String>> {
|
|||||||
|
|
||||||
map.insert("json".to_string(), theme_json);
|
map.insert("json".to_string(), theme_json);
|
||||||
map.insert("path".to_string(), path.to_str().unwrap().to_string());
|
map.insert("path".to_string(), path.to_str().unwrap().to_string());
|
||||||
|
|
||||||
// Push key-value pair containing "json" and "path"
|
// Push key-value pair containing "json" and "path"
|
||||||
themes.push(map);
|
themes.push(map);
|
||||||
}
|
}
|
||||||
|
|||||||
145
src-tauri/src/metadata_patcher.rs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
use regex::Regex;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
// For these two functions, a non-zero return value indicates failure.
|
||||||
|
extern "C" {
|
||||||
|
fn decrypt_global_metadata(data: *mut u8, size: usize) -> i32;
|
||||||
|
fn encrypt_global_metadata(data: *mut u8, size: usize) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
let success = unsafe { decrypt_global_metadata(data.as_mut_ptr(), data.len()) } == 0;
|
||||||
|
if success {
|
||||||
|
println!("Successfully decrypted global-metadata");
|
||||||
|
data
|
||||||
|
} else {
|
||||||
|
println!("Failed to decrypt global-metadata");
|
||||||
|
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();
|
||||||
|
let success = unsafe { encrypt_global_metadata(data.as_mut_ptr(), data.len()) } == 0;
|
||||||
|
if success {
|
||||||
|
println!("Successfully encrypted global-metadata");
|
||||||
|
data
|
||||||
|
} else {
|
||||||
|
println!("Failed to encrypt global-metadata");
|
||||||
|
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()
|
||||||
|
}
|
||||||
@@ -4,15 +4,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::{sync::Mutex, str::FromStr};
|
use std::{str::FromStr, sync::Mutex};
|
||||||
|
|
||||||
use rcgen::*;
|
|
||||||
use hudsucker::{
|
use hudsucker::{
|
||||||
async_trait::async_trait,
|
async_trait::async_trait,
|
||||||
certificate_authority::RcgenAuthority,
|
certificate_authority::RcgenAuthority,
|
||||||
hyper::{Body, Request, Response},
|
hyper::{Body, Request, Response},
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
|
use rcgen::*;
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
@@ -22,10 +22,11 @@ use rustls_pemfile as pemfile;
|
|||||||
use tauri::http::Uri;
|
use tauri::http::Uri;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use registry::{Hive, Data, Security};
|
use registry::{Data, Hive, Security};
|
||||||
|
|
||||||
async fn shutdown_signal() {
|
async fn shutdown_signal() {
|
||||||
tokio::signal::ctrl_c().await
|
tokio::signal::ctrl_c()
|
||||||
|
.await
|
||||||
.expect("Failed to install CTRL+C signal handler");
|
.expect("Failed to install CTRL+C signal handler");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,16 +43,18 @@ pub fn set_proxy_addr(addr: String) {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl HttpHandler for ProxyHandler {
|
impl HttpHandler for ProxyHandler {
|
||||||
async fn handle_request(&mut self,
|
async fn handle_request(
|
||||||
_context: &HttpContext,
|
&mut self,
|
||||||
mut request: Request<Body>,
|
_context: &HttpContext,
|
||||||
|
mut request: Request<Body>,
|
||||||
) -> RequestOrResponse {
|
) -> RequestOrResponse {
|
||||||
let uri = request.uri().to_string();
|
let uri = request.uri().to_string();
|
||||||
let uri_path = request.uri().path();
|
let uri_path = request.uri().path();
|
||||||
|
|
||||||
if uri.contains("hoyoverse.com") || uri.contains("mihoyo.com") || uri.contains("yuanshen.com") {
|
if uri.contains("hoyoverse.com") || uri.contains("mihoyo.com") || uri.contains("yuanshen.com") {
|
||||||
// Create new URI.
|
// Create new URI.
|
||||||
let new_uri = Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path).as_str()).unwrap();
|
let new_uri =
|
||||||
|
Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path).as_str()).unwrap();
|
||||||
// Set request URI to the new one.
|
// Set request URI to the new one.
|
||||||
*request.uri_mut() = new_uri;
|
*request.uri_mut() = new_uri;
|
||||||
}
|
}
|
||||||
@@ -59,10 +62,13 @@ impl HttpHandler for ProxyHandler {
|
|||||||
RequestOrResponse::Request(request)
|
RequestOrResponse::Request(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_response(&mut self,
|
async fn handle_response(
|
||||||
_context: &HttpContext,
|
&mut self,
|
||||||
response: Response<Body>,
|
_context: &HttpContext,
|
||||||
) -> Response<Body> { response }
|
response: Response<Body>,
|
||||||
|
) -> Response<Body> {
|
||||||
|
response
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,20 +76,22 @@ impl HttpHandler for ProxyHandler {
|
|||||||
*/
|
*/
|
||||||
pub async fn create_proxy(proxy_port: u16, certificate_path: String) {
|
pub async fn create_proxy(proxy_port: u16, certificate_path: String) {
|
||||||
// Get the certificate and private key.
|
// 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 private_key_bytes: &[u8] =
|
||||||
let mut ca_cert_bytes: &[u8] = &fs::read(format!("{}\\cert.crt", certificate_path)).expect("Could not read certificate");
|
&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");
|
||||||
|
|
||||||
// Parse the private key and certificate.
|
// Parse the private key and certificate.
|
||||||
let private_key = rustls::PrivateKey(
|
let private_key = rustls::PrivateKey(
|
||||||
pemfile::pkcs8_private_keys(&mut private_key_bytes)
|
pemfile::pkcs8_private_keys(&mut private_key_bytes)
|
||||||
.expect("Failed to parse private key")
|
.expect("Failed to parse private key")
|
||||||
.remove(0)
|
.remove(0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let ca_cert = rustls::Certificate(
|
let ca_cert = rustls::Certificate(
|
||||||
pemfile::certs(&mut ca_cert_bytes)
|
pemfile::certs(&mut ca_cert_bytes)
|
||||||
.expect("Failed to parse CA certificate")
|
.expect("Failed to parse CA certificate")
|
||||||
.remove(0)
|
.remove(0),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create the certificate authority.
|
// Create the certificate authority.
|
||||||
@@ -108,13 +116,23 @@ pub async fn create_proxy(proxy_port: u16, certificate_path: String) {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn connect_to_proxy(proxy_port: u16) {
|
pub fn connect_to_proxy(proxy_port: u16) {
|
||||||
// Create 'ProxyServer' string.
|
// Create 'ProxyServer' string.
|
||||||
let server_string: String = format!("http=127.0.0.1:{};https=127.0.0.1:{}", proxy_port, proxy_port);
|
let server_string: String = format!(
|
||||||
|
"http=127.0.0.1:{};https=127.0.0.1:{}",
|
||||||
|
proxy_port, proxy_port
|
||||||
|
);
|
||||||
|
|
||||||
// Fetch the 'Internet Settings' registry key.
|
// Fetch the 'Internet Settings' registry key.
|
||||||
let settings = Hive::CurrentUser.open(r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", Security::Write).unwrap();
|
let settings = Hive::CurrentUser
|
||||||
|
.open(
|
||||||
|
r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
|
||||||
|
Security::Write,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Set registry values.
|
// Set registry values.
|
||||||
settings.set_value("ProxyServer", &Data::String(server_string.parse().unwrap())).unwrap();
|
settings
|
||||||
|
.set_value("ProxyServer", &Data::String(server_string.parse().unwrap()))
|
||||||
|
.unwrap();
|
||||||
settings.set_value("ProxyEnable", &Data::U32(1)).unwrap();
|
settings.set_value("ProxyEnable", &Data::U32(1)).unwrap();
|
||||||
|
|
||||||
println!("Connected to the proxy.");
|
println!("Connected to the proxy.");
|
||||||
@@ -131,7 +149,12 @@ pub fn connect_to_proxy(_proxy_port: u16) {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn disconnect_from_proxy() {
|
pub fn disconnect_from_proxy() {
|
||||||
// Fetch the 'Internet Settings' registry key.
|
// Fetch the 'Internet Settings' registry key.
|
||||||
let settings = Hive::CurrentUser.open(r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", Security::Write).unwrap();
|
let settings = Hive::CurrentUser
|
||||||
|
.open(
|
||||||
|
r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
|
||||||
|
Security::Write,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Set registry values.
|
// Set registry values.
|
||||||
settings.set_value("ProxyEnable", &Data::U32(0)).unwrap();
|
settings.set_value("ProxyEnable", &Data::U32(0)).unwrap();
|
||||||
@@ -157,7 +180,7 @@ pub fn generate_ca_files(path: &Path) {
|
|||||||
details.push(DnType::OrganizationName, "Grasscutters");
|
details.push(DnType::OrganizationName, "Grasscutters");
|
||||||
details.push(DnType::CountryName, "CN");
|
details.push(DnType::CountryName, "CN");
|
||||||
details.push(DnType::LocalityName, "CN");
|
details.push(DnType::LocalityName, "CN");
|
||||||
|
|
||||||
// Set details in the parameter.
|
// Set details in the parameter.
|
||||||
params.distinguished_name = details;
|
params.distinguished_name = details;
|
||||||
// Set other properties.
|
// Set other properties.
|
||||||
@@ -165,9 +188,9 @@ pub fn generate_ca_files(path: &Path) {
|
|||||||
params.key_usages = vec![
|
params.key_usages = vec![
|
||||||
KeyUsagePurpose::DigitalSignature,
|
KeyUsagePurpose::DigitalSignature,
|
||||||
KeyUsagePurpose::KeyCertSign,
|
KeyUsagePurpose::KeyCertSign,
|
||||||
KeyUsagePurpose::CrlSign
|
KeyUsagePurpose::CrlSign,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Create certificate.
|
// Create certificate.
|
||||||
let cert = Certificate::from_params(params).unwrap();
|
let cert = Certificate::from_params(params).unwrap();
|
||||||
let cert_crt = cert.serialize_pem().unwrap();
|
let cert_crt = cert.serialize_pem().unwrap();
|
||||||
@@ -176,26 +199,37 @@ pub fn generate_ca_files(path: &Path) {
|
|||||||
// Make certificate directory.
|
// Make certificate directory.
|
||||||
let cert_dir = path.join("ca");
|
let cert_dir = path.join("ca");
|
||||||
match fs::create_dir(&cert_dir) {
|
match fs::create_dir(&cert_dir) {
|
||||||
Ok(_) => {},
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("{}", e);
|
println!("{}", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Write the certificate to a file.
|
// Write the certificate to a file.
|
||||||
let cert_path = cert_dir.join("cert.crt");
|
let cert_path = cert_dir.join("cert.crt");
|
||||||
match fs::write(&cert_path, cert_crt) {
|
match fs::write(&cert_path, cert_crt) {
|
||||||
Ok(_) => println!("Wrote certificate to {}", cert_path.to_str().unwrap()),
|
Ok(_) => println!("Wrote certificate to {}", cert_path.to_str().unwrap()),
|
||||||
Err(e) => println!("Error writing certificate to {}: {}", cert_path.to_str().unwrap(), e),
|
Err(e) => println!(
|
||||||
|
"Error writing certificate to {}: {}",
|
||||||
|
cert_path.to_str().unwrap(),
|
||||||
|
e
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the private key to a file.
|
// Write the private key to a file.
|
||||||
let private_key_path = cert_dir.join("private.key");
|
let private_key_path = cert_dir.join("private.key");
|
||||||
match fs::write(&private_key_path, private_key) {
|
match fs::write(&private_key_path, private_key) {
|
||||||
Ok(_) => println!("Wrote private key to {}", private_key_path.to_str().unwrap()),
|
Ok(_) => println!(
|
||||||
Err(e) => println!("Error writing private key to {}: {}", private_key_path.to_str().unwrap(), e),
|
"Wrote private key to {}",
|
||||||
|
private_key_path.to_str().unwrap()
|
||||||
|
),
|
||||||
|
Err(e) => println!(
|
||||||
|
"Error writing private key to {}: {}",
|
||||||
|
private_key_path.to_str().unwrap(),
|
||||||
|
e
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install certificate into the system's Root CA store.
|
// Install certificate into the system's Root CA store.
|
||||||
install_ca_files(&cert_path);
|
install_ca_files(&cert_path);
|
||||||
}
|
}
|
||||||
@@ -205,13 +239,27 @@ pub fn generate_ca_files(path: &Path) {
|
|||||||
*/
|
*/
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn install_ca_files(cert_path: &Path) {
|
pub fn install_ca_files(cert_path: &Path) {
|
||||||
crate::system_helpers::run_command("certutil", vec!["-user", "-addstore", "Root", cert_path.to_str().unwrap()]);
|
crate::system_helpers::run_command(
|
||||||
|
"certutil",
|
||||||
|
vec!["-user", "-addstore", "Root", cert_path.to_str().unwrap()],
|
||||||
|
);
|
||||||
println!("Installed certificate.");
|
println!("Installed certificate.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn install_ca_files(cert_path: &Path) {
|
pub fn install_ca_files(cert_path: &Path) {
|
||||||
crate::system_helpers::run_command("security", vec!["add-trusted-cert", "-d", "-r", "trustRoot", "-k", "/Library/Keychains/System.keychain", cert_path.to_str().unwrap()]);
|
crate::system_helpers::run_command(
|
||||||
|
"security",
|
||||||
|
vec![
|
||||||
|
"add-trusted-cert",
|
||||||
|
"-d",
|
||||||
|
"-r",
|
||||||
|
"trustRoot",
|
||||||
|
"-k",
|
||||||
|
"/Library/Keychains/System.keychain",
|
||||||
|
cert_path.to_str().unwrap(),
|
||||||
|
],
|
||||||
|
);
|
||||||
println!("Installed certificate.");
|
println!("Installed certificate.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ use serde::Deserialize;
|
|||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub(crate) struct APIQuery {
|
pub(crate) struct APIQuery {
|
||||||
pub bg_file: String,
|
pub bg_file: String,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,41 @@
|
|||||||
use duct::cmd;
|
use duct::cmd;
|
||||||
|
|
||||||
use crate::file_helpers;
|
#[tauri::command]
|
||||||
|
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("".into()))).unwrap_or(());
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn run_program(path: String) {
|
pub fn run_program_relative(path: String, args: Option<String>) {
|
||||||
// Open the program from the specified path.
|
// Save the current working directory
|
||||||
open::that(&path).unwrap();
|
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();
|
||||||
|
|
||||||
|
println!("Opening {} {}", &path, args.clone().unwrap_or("".into()));
|
||||||
|
|
||||||
|
// Without unwrap_or, this can crash when UAC prompt is denied
|
||||||
|
open::that(format!("{} {}", &path, args.unwrap_or("".into()))).unwrap_or(());
|
||||||
|
|
||||||
|
// Restore the original working directory
|
||||||
|
std::env::set_current_dir(&cwd).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn run_command(program: &str, args: Vec<&str>) {
|
pub fn run_command(program: &str, args: Vec<&str>) {
|
||||||
cmd(program, args).run()
|
let prog = program.to_string();
|
||||||
.expect("Failed to run command");
|
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 || {
|
||||||
|
cmd(prog, args).run().unwrap();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -23,7 +47,10 @@ pub fn run_jar(path: String, execute_in: String, java_path: String) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Open the program from the specified path.
|
// Open the program from the specified path.
|
||||||
match open::with(format!("/k cd /D \"{}\" & {}", &execute_in, &command), "C:\\Windows\\System32\\cmd.exe") {
|
match open::with(
|
||||||
|
format!("/k cd /D \"{}\" & {}", &execute_in, &command),
|
||||||
|
"C:\\Windows\\System32\\cmd.exe",
|
||||||
|
) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => println!("Failed to open jar ({} from {}): {}", &path, &execute_in, e),
|
Err(e) => println!("Failed to open jar ({} from {}): {}", &path, &execute_in, e),
|
||||||
};
|
};
|
||||||
@@ -38,7 +65,6 @@ pub fn open_in_browser(url: String) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn install_location() -> String {
|
pub fn install_location() -> String {
|
||||||
let mut exe_path = std::env::current_exe().unwrap();
|
let mut exe_path = std::env::current_exe().unwrap();
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
use std::fs::File;
|
use std::fs::{read_dir, File};
|
||||||
use std::path;
|
use std::path;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use unrar::archive::{Archive, OpenArchive};
|
||||||
|
|
||||||
#[tauri::command]
|
#[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
|
// Read file TODO: replace test file
|
||||||
let f = match File::open(&zipfile) {
|
let f = match File::open(&zipfile) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
@@ -15,42 +22,81 @@ pub fn unzip(window: tauri::Window, zipfile: String, destpath: String) {
|
|||||||
|
|
||||||
let write_path = path::PathBuf::from(&destpath);
|
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
|
// Run extraction in seperate thread
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let full_path = write_path;
|
let mut full_path = write_path.clone();
|
||||||
|
|
||||||
window.emit("extract_start", &zipfile).unwrap();
|
window.emit("extract_start", &zipfile).unwrap();
|
||||||
|
|
||||||
match zip_extract::extract(&f, &full_path, true) {
|
if folder_if_loose.unwrap_or(false) {
|
||||||
Ok(_) => {
|
// Create a new folder with the same name as the zip file
|
||||||
println!("Extracted zip file to: {}", full_path.to_str().unwrap_or("Error"));
|
let mut file_name = path::Path::new(&zipfile)
|
||||||
}
|
.file_name()
|
||||||
Err(e) => {
|
.unwrap()
|
||||||
println!("Failed to extract zip file: {}", e);
|
.to_str()
|
||||||
let mut res_hash = std::collections::HashMap::new();
|
.unwrap();
|
||||||
|
|
||||||
res_hash.insert(
|
// remove ".zip" from the end of the file name
|
||||||
"error".to_string(),
|
file_name = &file_name[..file_name.len() - 4];
|
||||||
e.to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
res_hash.insert(
|
let new_path = full_path.join(file_name);
|
||||||
"path".to_string(),
|
match std::fs::create_dir_all(&new_path) {
|
||||||
zipfile.to_string(),
|
Ok(_) => {}
|
||||||
);
|
Err(e) => {
|
||||||
|
println!("Failed to create directory: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
window.emit("download_error", &res_hash).unwrap();
|
full_path = new_path.clone();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Get the name of the inenr file in the zip file
|
println!("Is rar file? {}", zipfile.ends_with(".rar"));
|
||||||
let mut zip = zip::ZipArchive::new(&f).unwrap();
|
|
||||||
let file = zip.by_index(0).unwrap();
|
let mut name = "".into();
|
||||||
let name = file.name();
|
|
||||||
|
// If file ends in zip, OR is unknown, extract as zip, otherwise extract as rar
|
||||||
|
if zipfile.ends_with(".rar") {
|
||||||
|
extract_rar(
|
||||||
|
&window,
|
||||||
|
&zipfile,
|
||||||
|
&f,
|
||||||
|
&full_path,
|
||||||
|
top_level.unwrap_or(false),
|
||||||
|
);
|
||||||
|
|
||||||
|
let archive = Archive::new(zipfile.clone());
|
||||||
|
name = archive.list().unwrap().next().unwrap().unwrap().filename;
|
||||||
|
} else {
|
||||||
|
extract_zip(
|
||||||
|
&window,
|
||||||
|
&zipfile,
|
||||||
|
&f,
|
||||||
|
&full_path,
|
||||||
|
top_level.unwrap_or(false),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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().clone();
|
||||||
|
}
|
||||||
|
|
||||||
// If the contents is a jar file, emit that we have extracted a new jar file
|
// If the contents is a jar file, emit that we have extracted a new jar file
|
||||||
if name.ends_with(".jar") {
|
if name.ends_with(".jar") {
|
||||||
window.emit("jar_extracted", destpath.to_string() + name).unwrap();
|
window
|
||||||
|
.emit("jar_extracted", destpath.to_string() + name.as_str())
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete zip file
|
// Delete zip file
|
||||||
@@ -63,6 +109,80 @@ pub fn unzip(window: tauri::Window, zipfile: String, destpath: String) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.emit("extract_end", &zipfile).unwrap();
|
// 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() {
|
||||||
|
if !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.to_string());
|
||||||
|
|
||||||
|
window.emit("extract_end", &res_hash).unwrap();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_rar(
|
||||||
|
window: &tauri::Window,
|
||||||
|
rarfile: &String,
|
||||||
|
f: &File,
|
||||||
|
full_path: &path::PathBuf,
|
||||||
|
top_level: bool,
|
||||||
|
) {
|
||||||
|
let archive = Archive::new(rarfile.clone());
|
||||||
|
|
||||||
|
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")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to extract rar file: {}", e);
|
||||||
|
let mut res_hash = std::collections::HashMap::new();
|
||||||
|
|
||||||
|
res_hash.insert("error".to_string(), e.to_string());
|
||||||
|
res_hash.insert("path".to_string(), rarfile.to_string());
|
||||||
|
|
||||||
|
window.emit("download_error", &res_hash).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_zip(
|
||||||
|
window: &tauri::Window,
|
||||||
|
zipfile: &String,
|
||||||
|
f: &File,
|
||||||
|
full_path: &path::PathBuf,
|
||||||
|
top_level: bool,
|
||||||
|
) {
|
||||||
|
match zip_extract::extract(f, full_path, top_level) {
|
||||||
|
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();
|
||||||
|
|
||||||
|
res_hash.insert("error".to_string(), e.to_string());
|
||||||
|
res_hash.insert("path".to_string(), zipfile.to_string());
|
||||||
|
|
||||||
|
window.emit("download_error", &res_hash).unwrap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
use reqwest::header::USER_AGENT;
|
use reqwest::header::{CONTENT_TYPE, USER_AGENT};
|
||||||
|
|
||||||
pub(crate) async fn query(site: &str) -> String {
|
pub(crate) async fn query(site: &str) -> String {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
let response = client.get(site).header(USER_AGENT, "cultivation").send().await.unwrap();
|
let response = client
|
||||||
|
.get(site)
|
||||||
|
.header(USER_AGENT, "cultivation")
|
||||||
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
response.text().await.unwrap()
|
response.text().await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,7 +18,18 @@ pub(crate) async fn valid_url(url: String) -> bool {
|
|||||||
// Check if we get a 200 response
|
// Check if we get a 200 response
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
let response = client.get(url).header(USER_AGENT, "cultivation").send().await.unwrap();
|
let response = client
|
||||||
|
.get(url)
|
||||||
|
.header(USER_AGENT, "cultivation")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
response.status().as_str() == "200"
|
response.status().as_str() == "200"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn web_get(url: String) -> String {
|
||||||
|
// Send a GET request to the specified URL and send the response body back to the client.
|
||||||
|
query(&url).await
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,25 +7,17 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "Cultivation",
|
"productName": "Cultivation",
|
||||||
"version": "1.0.2"
|
"version": "1.0.4"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
"fs": {
|
"fs": {
|
||||||
"scope": [
|
"scope": ["$DATA", "$DATA/cultivation", "$DATA/cultivation/*"]
|
||||||
"$DATA",
|
|
||||||
"$DATA/cultivation",
|
|
||||||
"$DATA/cultivation/*"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"protocol": {
|
"protocol": {
|
||||||
"all": true,
|
"all": true,
|
||||||
"asset": true,
|
"asset": true,
|
||||||
"assetScope": [
|
"assetScope": ["$DATA", "$DATA/cultivation", "$DATA/cultivation/*"]
|
||||||
"$DATA",
|
|
||||||
"$DATA/cultivation",
|
|
||||||
"$DATA/cultivation/*"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"all": true
|
"all": true
|
||||||
},
|
},
|
||||||
@@ -37,13 +29,7 @@
|
|||||||
"depends": []
|
"depends": []
|
||||||
},
|
},
|
||||||
"externalBin": [],
|
"externalBin": [],
|
||||||
"icon": [
|
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
|
||||||
"icons/32x32.png",
|
|
||||||
"icons/128x128.png",
|
|
||||||
"icons/128x128@2x.png",
|
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico"
|
|
||||||
],
|
|
||||||
"identifier": "io.grasscutter",
|
"identifier": "io.grasscutter",
|
||||||
"shortDescription": "A game launcher.",
|
"shortDescription": "A game launcher.",
|
||||||
"longDescription": "A launcher for a certain anime game that proxies all related game traffic to external servers.",
|
"longDescription": "A launcher for a certain anime game that proxies all related game traffic to external servers.",
|
||||||
@@ -54,9 +40,7 @@
|
|||||||
"providerShortName": null,
|
"providerShortName": null,
|
||||||
"signingIdentity": null
|
"signingIdentity": null
|
||||||
},
|
},
|
||||||
"resources": [
|
"resources": ["lang/*.json", "keys/*"],
|
||||||
"lang/*.json"
|
|
||||||
],
|
|
||||||
"targets": "all",
|
"targets": "all",
|
||||||
"windows": {
|
"windows": {
|
||||||
"allowDowngrades": false,
|
"allowDowngrades": false,
|
||||||
@@ -91,4 +75,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: 'MiHoYo_SDK_Web', 'Helvetica Neue', BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
font-family: 'MiHoYo_SDK_Web', 'Helvetica Neue', BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
|
'Cantarell', 'Fira Sans', 'Droid Sans', sans-serif;
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||||
monospace;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,23 +7,15 @@ import Debug from './ui/Debug'
|
|||||||
|
|
||||||
import { getConfigOption } from './utils/configuration'
|
import { getConfigOption } from './utils/configuration'
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
||||||
document.getElementById('root') as HTMLElement
|
|
||||||
)
|
|
||||||
|
|
||||||
let isDebug = false;
|
let isDebug = false
|
||||||
|
|
||||||
(async() => {
|
;async () => {
|
||||||
isDebug = await getConfigOption('debug_enabled')
|
isDebug = await getConfigOption('debug_enabled')
|
||||||
})
|
}
|
||||||
|
|
||||||
root.render(
|
root.render(<React.StrictMode>{isDebug ? <Debug /> : <App />}</React.StrictMode>)
|
||||||
<React.StrictMode>
|
|
||||||
{
|
|
||||||
isDebug ? <Debug /> : <App />
|
|
||||||
}
|
|
||||||
</React.StrictMode>
|
|
||||||
)
|
|
||||||
|
|
||||||
import reportWebVitals from './utils/reportWebVitals'
|
import reportWebVitals from './utils/reportWebVitals'
|
||||||
isDebug && reportWebVitals(console.log)
|
isDebug && reportWebVitals(console.log)
|
||||||
|
|||||||
67
src/resources/icons/akebi.svg
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M7045 10204 c-301 -50 -596 -277 -731 -564 -96 -204 -111 -463 -40
|
||||||
|
-692 16 -52 14 -58 -26 -58 -62 0 -220 -85 -331 -178 -24 -20 -105 -91 -178
|
||||||
|
-157 -74 -66 -177 -156 -229 -200 -208 -175 -629 -573 -1001 -944 -152 -152
|
||||||
|
-286 -284 -297 -294 -18 -17 -27 -17 -75 -8 -96 21 -249 29 -361 20 -321 -26
|
||||||
|
-613 -162 -836 -389 -341 -346 -457 -846 -303 -1301 l36 -106 -66 -100 c-102
|
||||||
|
-155 -212 -343 -303 -518 -353 -683 -556 -1455 -531 -2025 6 -136 29 -341 47
|
||||||
|
-415 36 -156 58 -234 92 -335 47 -137 52 -147 134 -315 90 -182 113 -220 223
|
||||||
|
-375 376 -528 945 -931 1571 -1113 261 -76 439 -94 503 -52 27 17 30 17 105
|
||||||
|
-4 125 -35 209 -50 345 -62 107 -9 152 -8 279 6 118 13 180 15 277 8 214 -14
|
||||||
|
475 -9 566 11 144 31 312 80 395 114 14 6 68 27 120 48 91 36 302 135 365 172
|
||||||
|
180 104 373 265 507 422 346 407 518 977 520 1725 0 287 -10 448 -48 755 -43
|
||||||
|
357 -95 631 -208 1100 -24 102 -47 199 -51 217 -5 26 -1 41 25 78 48 70 108
|
||||||
|
204 132 297 32 118 31 366 0 482 -75 272 -258 503 -507 639 l-77 42 -68 202
|
||||||
|
c-37 112 -70 213 -73 225 -3 13 -14 57 -26 98 -12 41 -33 134 -47 205 -23 115
|
||||||
|
-26 154 -27 345 0 223 1 232 62 580 44 245 54 349 47 490 -10 234 -52 334
|
||||||
|
-206 495 -98 103 -128 150 -167 261 -24 70 -27 94 -28 209 0 135 10 185 58
|
||||||
|
280 49 99 182 235 282 288 179 96 373 112 464 39 62 -49 101 -117 101 -174 0
|
||||||
|
-81 -49 -152 -117 -172 -13 -3 -32 -8 -42 -11 -13 -4 -34 7 -65 34 -26 22 -63
|
||||||
|
45 -84 52 -107 32 -219 -73 -198 -185 7 -37 89 -141 129 -163 137 -76 279 -77
|
||||||
|
428 -3 102 52 190 149 236 263 36 89 42 239 14 326 -36 108 -112 215 -206 286
|
||||||
|
-80 62 -158 92 -265 105 -105 11 -142 11 -245 -6z m-574 -1640 c138 -41 189
|
||||||
|
-186 160 -459 -13 -115 -45 -328 -62 -405 -19 -86 -49 -386 -49 -493 0 -163
|
||||||
|
17 -299 71 -552 7 -36 35 -132 102 -350 l24 -80 -31 -2 c-114 -9 -233 -28
|
||||||
|
-305 -50 -193 -55 -383 -185 -509 -347 -12 -16 -24 -27 -27 -24 -6 5 -22 125
|
||||||
|
-30 223 -3 39 -10 113 -15 165 -10 105 -26 365 -35 595 -5 120 -11 158 -26
|
||||||
|
192 -34 70 -110 104 -187 84 -44 -12 -77 -47 -217 -230 -38 -51 -90 -118 -115
|
||||||
|
-150 -25 -32 -70 -91 -100 -131 -30 -40 -64 -84 -76 -98 l-21 -25 -37 67 c-89
|
||||||
|
159 -229 312 -386 419 l-81 55 213 214 c371 372 775 751 1063 997 94 79 193
|
||||||
|
166 220 192 78 75 185 157 239 184 60 30 136 33 217 9z m-2317 -1783 c351 -93
|
||||||
|
611 -365 682 -712 22 -109 15 -291 -16 -403 -107 -396 -472 -688 -900 -722
|
||||||
|
l-85 -6 70 50 c180 127 282 307 293 513 16 326 -203 611 -533 695 -90 22 -256
|
||||||
|
23 -347 1 -136 -33 -284 -119 -366 -212 -22 -25 -42 -45 -44 -45 -9 0 11 102
|
||||||
|
33 175 104 343 411 610 782 681 104 20 329 12 431 -15z m1310 -406 c21 -268
|
||||||
|
29 -351 51 -545 31 -262 72 -520 120 -765 26 -130 66 -320 75 -360 5 -22 28
|
||||||
|
-128 50 -235 23 -107 45 -213 50 -235 41 -178 113 -614 145 -880 48 -401 57
|
||||||
|
-943 20 -1215 -55 -407 -186 -801 -368 -1102 -107 -178 -168 -262 -258 -355
|
||||||
|
-146 -150 -290 -212 -494 -213 -252 0 -520 107 -800 319 -274 207 -539 609
|
||||||
|
-674 1021 -112 339 -154 603 -154 960 0 278 18 425 82 671 92 348 301 725 615
|
||||||
|
1106 l50 61 87 16 c472 86 871 430 1017 876 42 128 55 211 58 383 l4 168 120
|
||||||
|
157 c169 220 194 252 196 249 1 -1 5 -38 8 -82z m1043 -511 c-144 -112 -216
|
||||||
|
-335 -168 -519 27 -101 69 -171 148 -245 74 -69 141 -104 246 -127 67 -14 90
|
||||||
|
-15 159 -5 145 21 277 102 353 218 60 90 86 178 86 287 0 51 -3 102 -6 112
|
||||||
|
-13 40 26 -17 61 -89 95 -196 84 -447 -28 -633 -183 -303 -569 -423 -917 -284
|
||||||
|
-171 69 -330 226 -398 394 -152 373 46 791 432 913 73 23 82 17 32 -22z
|
||||||
|
m-3393 -1044 c104 -70 239 -133 356 -166 52 -15 97 -28 98 -30 4 -2 -15 -29
|
||||||
|
-83 -124 -60 -83 -171 -254 -217 -335 -185 -323 -305 -691 -349 -1070 -19
|
||||||
|
-158 -16 -519 5 -690 34 -284 98 -554 186 -790 44 -120 179 -400 231 -480 171
|
||||||
|
-266 299 -420 509 -610 50 -45 61 -59 47 -63 -47 -10 -352 117 -535 224 -700
|
||||||
|
409 -1152 1060 -1253 1804 -19 136 -16 466 5 625 78 591 314 1222 678 1813
|
||||||
|
l70 113 87 -84 c48 -46 122 -107 165 -137z m3105 -469 c175 -88 288 -116 481
|
||||||
|
-116 190 0 316 30 470 111 30 16 58 30 61 32 15 7 131 -523 183 -833 64 -382
|
||||||
|
88 -661 88 -1015 -1 -346 -24 -553 -94 -820 -112 -430 -342 -768 -675 -993
|
||||||
|
-139 -94 -386 -208 -643 -297 -67 -24 -174 -50 -255 -64 -94 -17 -350 -22
|
||||||
|
-359 -8 -2 4 16 26 40 47 42 37 171 184 204 231 176 256 269 425 360 654 118
|
||||||
|
295 181 554 221 907 19 168 21 222 16 503 -4 173 -11 369 -16 434 -30 341
|
||||||
|
-103 828 -177 1168 -15 73 -18 108 -7 108 4 0 50 -22 102 -49z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.5 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;
|
border-bottom-color: #ffd326;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root, .App {
|
#root,
|
||||||
|
.App {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,12 +35,29 @@ select:focus {
|
|||||||
.TopButton {
|
.TopButton {
|
||||||
height: 60%;
|
height: 60%;
|
||||||
margin: 0px 10px;
|
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%);
|
filter: invert(95%) sepia(0%) saturate(18%) hue-rotate(153deg) brightness(88%) contrast(81%);
|
||||||
|
|
||||||
transition: filter 0.1s ease-in-out;
|
transition: filter 0.1s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.TopButton:hover {
|
.TopButton:hover {
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TopButton:hover img {
|
||||||
filter: invert(100%) sepia(0%) saturate(18%) hue-rotate(153deg) brightness(100%) contrast(100%);
|
filter: invert(100%) sepia(0%) saturate(18%) hue-rotate(153deg) brightness(100%) contrast(100%);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -64,8 +82,8 @@ select:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.arrow-down {
|
.arrow-down {
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
border-left: 50px solid transparent;
|
border-left: 50px solid transparent;
|
||||||
border-right: 50px solid transparent;
|
border-right: 50px solid transparent;
|
||||||
border-top: 50px solid transparent;
|
border-top: 50px solid transparent;
|
||||||
@@ -82,28 +100,31 @@ select:focus {
|
|||||||
|
|
||||||
.BottomSection {
|
.BottomSection {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0%;
|
bottom: 0%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, 0%);
|
transform: translate(-50%, 0%);
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 160px;
|
height: 160px;
|
||||||
|
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
box-shadow: inset 0px 5px 12px -3px rgb(50 50 50 / 75%);
|
box-shadow: inset 0px 5px 12px -3px rgb(50 50 50 / 75%);
|
||||||
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-height: 580px) {
|
.ExtrasMenu {
|
||||||
.BottomSection {
|
|
||||||
height: 150px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-height: 500px) {
|
@media (max-height: 580px) {
|
||||||
.BottomSection {
|
.BottomSection {
|
||||||
height: 140px;
|
height: 150px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-height: 500px) {
|
||||||
|
.BottomSection {
|
||||||
|
height: 140px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
219
src/ui/App.tsx
@@ -1,83 +1,33 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { listen } from '@tauri-apps/api/event'
|
|
||||||
import './App.css'
|
import './App.css'
|
||||||
|
|
||||||
import DownloadHandler from '../utils/download'
|
import DownloadHandler from '../utils/download'
|
||||||
|
import { getConfigOption } from '../utils/configuration'
|
||||||
// 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 { getTheme, loadTheme } from '../utils/themes'
|
import { getTheme, loadTheme } from '../utils/themes'
|
||||||
|
import { convertFileSrc, invoke } from '@tauri-apps/api/tauri'
|
||||||
interface IProps {
|
import { dataDir } from '@tauri-apps/api/path'
|
||||||
[key: string]: never;
|
import { Main } from './Main'
|
||||||
}
|
import { Mods } from './Mods'
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
isDownloading: boolean;
|
page: string
|
||||||
optionsOpen: boolean;
|
bgFile: string
|
||||||
miniDownloadsOpen: boolean;
|
|
||||||
downloadsOpen: boolean;
|
|
||||||
gameDownloadsOpen: boolean;
|
|
||||||
bgFile: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const downloadHandler = new DownloadHandler()
|
||||||
const DEFAULT_BG = 'https://api.grasscutter.io/cultivation/bgfile'
|
const DEFAULT_BG = 'https://api.grasscutter.io/cultivation/bgfile'
|
||||||
|
|
||||||
const downloadHandler = new DownloadHandler()
|
class App extends React.Component<Readonly<unknown>, IState> {
|
||||||
|
constructor(props: Readonly<unknown>) {
|
||||||
class App extends React.Component<IProps, IState> {
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isDownloading: false,
|
page: 'main',
|
||||||
optionsOpen: false,
|
|
||||||
miniDownloadsOpen: false,
|
|
||||||
downloadsOpen: false,
|
|
||||||
gameDownloadsOpen: false,
|
|
||||||
bgFile: DEFAULT_BG,
|
bgFile: DEFAULT_BG,
|
||||||
}
|
}
|
||||||
|
|
||||||
listen('lang_error', (payload) => {
|
|
||||||
console.log(payload)
|
|
||||||
})
|
|
||||||
|
|
||||||
listen('jar_extracted', ({ payload }) => {
|
|
||||||
setConfigOption('grasscutter_path', payload)
|
|
||||||
})
|
|
||||||
|
|
||||||
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() {
|
async componentDidMount() {
|
||||||
const cert_generated = await getConfigOption('cert_generated')
|
|
||||||
const game_exe = await getConfigOption('game_install_path')
|
const game_exe = await getConfigOption('game_install_path')
|
||||||
const game_path = game_exe?.substring(0, game_exe.replace(/\\/g, '/').lastIndexOf('/')) || ''
|
const game_path = game_exe?.substring(0, game_exe.replace(/\\/g, '/').lastIndexOf('/')) || ''
|
||||||
const root_path = game_path?.substring(0, game_path.replace(/\\/g, '/').lastIndexOf('/')) || ''
|
const root_path = game_path?.substring(0, game_path.replace(/\\/g, '/').lastIndexOf('/')) || ''
|
||||||
@@ -92,134 +42,79 @@ class App extends React.Component<IProps, IState> {
|
|||||||
// Get custom bg AFTER theme is loaded !! important !!
|
// Get custom bg AFTER theme is loaded !! important !!
|
||||||
const custom_bg = await getConfigOption('customBackground')
|
const custom_bg = await getConfigOption('customBackground')
|
||||||
|
|
||||||
if(!custom_bg || !/png|jpg|jpeg$/.test(custom_bg)) {
|
if (!custom_bg || !/png|jpg|jpeg$/.test(custom_bg)) {
|
||||||
if(game_path) {
|
if (game_path) {
|
||||||
// Get the bg by invoking, then set the background to that bg.
|
// Get the bg by invoking, then set the background to that bg.
|
||||||
const bgLoc: string = await invoke('get_bg_file', {
|
const bgLoc: string = await invoke('get_bg_file', {
|
||||||
bgPath: root_path,
|
bgPath: root_path,
|
||||||
appdata: await dataDir()
|
appdata: await dataDir(),
|
||||||
})
|
})
|
||||||
|
|
||||||
bgLoc && this.setState({
|
bgLoc &&
|
||||||
bgFile: bgLoc
|
this.setState(
|
||||||
}, this.forceUpdate)
|
{
|
||||||
|
bgFile: bgLoc,
|
||||||
|
},
|
||||||
|
this.forceUpdate
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const isUrl = /^http(s)?:\/\//gm.test(custom_bg)
|
const isUrl = /^http(s)?:\/\//gm.test(custom_bg)
|
||||||
|
|
||||||
if (!isUrl) {
|
if (!isUrl) {
|
||||||
const isValid = await invoke('dir_exists', {
|
const isValid = await invoke('dir_exists', {
|
||||||
path: custom_bg
|
path: custom_bg,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
bgFile: isValid ? convertFileSrc(custom_bg) : DEFAULT_BG
|
{
|
||||||
}, this.forceUpdate)
|
bgFile: isValid ? convertFileSrc(custom_bg) : DEFAULT_BG,
|
||||||
|
},
|
||||||
|
this.forceUpdate
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// Check if URL returns a valid image.
|
// Check if URL returns a valid image.
|
||||||
const isValid = await invoke('valid_url', {
|
const isValid = await invoke('valid_url', {
|
||||||
url: custom_bg
|
url: custom_bg,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
bgFile: isValid ? custom_bg : DEFAULT_BG
|
{
|
||||||
}, this.forceUpdate)
|
bgFile: isValid ? custom_bg : DEFAULT_BG,
|
||||||
|
},
|
||||||
|
this.forceUpdate
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cert_generated) {
|
window.addEventListener('changePage', (e) => {
|
||||||
// Generate the certificate
|
|
||||||
await invoke('generate_ca_files', {
|
|
||||||
path: await dataDir() + 'cultivation'
|
|
||||||
})
|
|
||||||
|
|
||||||
await setConfigOption('cert_generated', true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Period check to only show progress bar when downloading files
|
|
||||||
setInterval(() => {
|
|
||||||
this.setState({
|
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() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="App" style={
|
<div
|
||||||
this.state.bgFile ? {
|
className="App"
|
||||||
background: `url("${this.state.bgFile}") fixed`,
|
style={
|
||||||
} : {}
|
this.state.bgFile
|
||||||
}>
|
? {
|
||||||
<TopBar
|
background: `url("${this.state.bgFile}") fixed`,
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
>
|
||||||
{
|
{(() => {
|
||||||
// Download menu
|
switch (this.state.page) {
|
||||||
this.state.downloadsOpen ? (
|
case 'modding':
|
||||||
<Downloads
|
return <Mods downloadHandler={downloadHandler} />
|
||||||
downloadManager={downloadHandler}
|
default:
|
||||||
closeFn={() => this.setState({ downloadsOpen: false })}
|
return <Main downloadHandler={downloadHandler} />
|
||||||
/>
|
}
|
||||||
) : null
|
})()}
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// Options menu
|
|
||||||
this.state.optionsOpen ? (
|
|
||||||
<Options
|
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import './App.css'
|
|||||||
|
|
||||||
import TopBar from './components/TopBar'
|
import TopBar from './components/TopBar'
|
||||||
|
|
||||||
import {invoke} from '@tauri-apps/api/tauri'
|
import { invoke } from '@tauri-apps/api/tauri'
|
||||||
import {dataDir} from '@tauri-apps/api/path'
|
import { dataDir } from '@tauri-apps/api/path'
|
||||||
import TextInput from './components/common/TextInput'
|
import TextInput from './components/common/TextInput'
|
||||||
|
|
||||||
let proxyAddress = ''
|
let proxyAddress = ''
|
||||||
@@ -15,7 +15,7 @@ async function setProxyAddress(address: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function startProxy() {
|
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' })
|
await invoke('open_in_browser', { url: 'https://hoyoverse.com' })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,14 +24,14 @@ async function stopProxy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function generateCertificates() {
|
async function generateCertificates() {
|
||||||
await invoke('generate_ca_files', { path: await dataDir() + '\\cultivation' })
|
await invoke('generate_ca_files', { path: (await dataDir()) + '\\cultivation' })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateInfo() {
|
async function generateInfo() {
|
||||||
console.log({
|
console.log({
|
||||||
certificatePath: await dataDir() + '\\cultivation\\ca',
|
certificatePath: (await dataDir()) + '\\cultivation\\ca',
|
||||||
isAdmin: await invoke('is_elevated'),
|
isAdmin: await invoke('is_elevated'),
|
||||||
connectingTo: proxyAddress
|
connectingTo: proxyAddress,
|
||||||
})
|
})
|
||||||
alert('check your dev console and send that in #cultivation')
|
alert('check your dev console and send that in #cultivation')
|
||||||
}
|
}
|
||||||
@@ -40,11 +40,11 @@ function none() {
|
|||||||
alert('none')
|
alert('none')
|
||||||
}
|
}
|
||||||
|
|
||||||
class Debug extends React.Component<any, any>{
|
class Debug extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<TopBar optFunc={none} downFunc={none} gameFunc={none} />
|
<TopBar />
|
||||||
<TextInput readOnly={false} initalValue={'change to set proxy address'} onChange={setProxyAddress} />
|
<TextInput readOnly={false} initalValue={'change to set proxy address'} onChange={setProxyAddress} />
|
||||||
<button onClick={startProxy}>start proxy</button>
|
<button onClick={startProxy}>start proxy</button>
|
||||||
<button onClick={stopProxy}>stop proxy</button>
|
<button onClick={stopProxy}>stop proxy</button>
|
||||||
@@ -55,4 +55,4 @@ class Debug extends React.Component<any, any>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Debug
|
export default Debug
|
||||||
|
|||||||
242
src/ui/Main.tsx
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
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/metadata'
|
||||||
|
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 Menu from './components/menu/Menu'
|
||||||
|
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 metadata replacing-purposes
|
||||||
|
listen('game_closed', async () => {
|
||||||
|
const wasPatched = await getConfigOption('patch_metadata')
|
||||||
|
|
||||||
|
if (wasPatched) {
|
||||||
|
const unpatched = await unpatchGame()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
.MiniDialog {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
|
|
||||||
/* Len and width */
|
/* Len and width */
|
||||||
height: 30%;
|
height: 30%;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
@@ -32,4 +32,4 @@
|
|||||||
|
|
||||||
.MiniDialog .ProgressText {
|
.MiniDialog .ProgressText {
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import Close from '../../resources/icons/close.svg'
|
|||||||
import './MiniDialog.css'
|
import './MiniDialog.css'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
children: React.ReactNode[] | React.ReactNode;
|
children: React.ReactNode[] | React.ReactNode
|
||||||
title?: string;
|
title?: string
|
||||||
closeable?: boolean;
|
closeable?: boolean
|
||||||
closeFn: () => void;
|
closeFn: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MiniDialog extends React.Component<IProps, never> {
|
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) => {
|
document.addEventListener('mousedown', (evt) => {
|
||||||
const tgt = evt.target as HTMLElement
|
const tgt = evt.target as HTMLElement
|
||||||
const isInside = tgt.closest('.MiniDialog') !== null
|
const isInside = tgt.closest('.MiniDialog') !== null
|
||||||
|
|
||||||
if (!isInside) {
|
if (!isInside) {
|
||||||
this.props.closeFn()
|
this.props.closeFn()
|
||||||
}
|
}
|
||||||
@@ -33,13 +33,12 @@ export default class MiniDialog extends React.Component<IProps, never> {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="MiniDialog" id="miniDialogContainer">
|
<div className="MiniDialog" id="miniDialogContainer">
|
||||||
{
|
{this.props.closeable !== undefined && this.props.closeable ? (
|
||||||
this.props.closeable !== undefined && this.props.closeable ?
|
<div className="MiniDialogTop" id="miniDialogContainerTop" onClick={this.props.closeFn}>
|
||||||
<div className="MiniDialogTop" id="miniDialogContainerTop" onClick={this.props.closeFn}>
|
<span>{this.props?.title}</span>
|
||||||
<span>{this.props?.title}</span>
|
<img src={Close} className="MiniDialogClose" id="miniDialogButtonClose" />
|
||||||
<img src={Close} className="MiniDialogClose" id="miniDialogButtonClose" />
|
</div>
|
||||||
</div> : null
|
) : null}
|
||||||
}
|
|
||||||
|
|
||||||
<div className="MiniDialogInner" id="miniDialogContent">
|
<div className="MiniDialogInner" id="miniDialogContent">
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
@@ -47,4 +46,4 @@ export default class MiniDialog extends React.Component<IProps, never> {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translate(0%, 0%);
|
transform: translate(0%, 0%);
|
||||||
|
|
||||||
display:flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
@@ -36,14 +36,14 @@
|
|||||||
filter: invert(75%) sepia(0%) saturate(100%) hue-rotate(0deg) brightness(100%) contrast(100%);
|
filter: invert(75%) sepia(0%) saturate(100%) hue-rotate(0deg) brightness(100%) contrast(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-height: 580px) {
|
@media (max-height: 580px) {
|
||||||
.RightBar {
|
.RightBar {
|
||||||
height: calc(100vh - 180px);
|
height: calc(100vh - 180px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-height: 500px) {
|
@media (max-height: 500px) {
|
||||||
.RightBar {
|
.RightBar {
|
||||||
height: calc(100vh - 170px);
|
height: calc(100vh - 170px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { invoke } from '@tauri-apps/api'
|
import { invoke } from '@tauri-apps/api'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import Discord from '../../resources/icons/discord.svg'
|
import Discord from '../../resources/icons/discord.svg'
|
||||||
import Github from '../../resources/icons/github.svg'
|
import Github from '../../resources/icons/github.svg'
|
||||||
|
|
||||||
import './RightBar.css'
|
import './RightBar.css'
|
||||||
|
|
||||||
@@ -28,4 +28,4 @@ export default class RightBar extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,20 +62,32 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 60px;
|
max-height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#officialPlay {
|
.ServerLaunchButtons .BigButton {
|
||||||
width: 60%
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#officialPlay {
|
||||||
|
max-width: 60%;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#akebiLaunch,
|
||||||
#serverLaunch {
|
#serverLaunch {
|
||||||
width: 5%;
|
width: 5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ExtrasMenuButton {
|
||||||
|
width: 5%;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExtrasIcon,
|
||||||
.ServerIcon {
|
.ServerIcon {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
filter: invert(28%) sepia(28%) saturate(1141%) hue-rotate(352deg) brightness(96%) contrast(88%);
|
filter: invert(28%) sepia(28%) saturate(1141%) hue-rotate(352deg) brightness(96%) contrast(88%);
|
||||||
@@ -102,17 +114,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1040px) {
|
@media (max-width: 1040px) {
|
||||||
#playButton {
|
#playButton {
|
||||||
right: 5%;
|
right: 5%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 870px) {
|
@media (max-width: 870px) {
|
||||||
#playButton {
|
#playButton {
|
||||||
min-width: 235px;
|
min-width: 235px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#officialPlay {
|
#officialPlay {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,27 +8,35 @@ import { translate } from '../../utils/language'
|
|||||||
import { invoke } from '@tauri-apps/api/tauri'
|
import { invoke } from '@tauri-apps/api/tauri'
|
||||||
|
|
||||||
import Server from '../../resources/icons/server.svg'
|
import Server from '../../resources/icons/server.svg'
|
||||||
|
import Plus from '../../resources/icons/plus.svg'
|
||||||
|
|
||||||
import './ServerLaunchSection.css'
|
import './ServerLaunchSection.css'
|
||||||
import {dataDir} from '@tauri-apps/api/path'
|
import { dataDir } from '@tauri-apps/api/path'
|
||||||
|
import { getGameExecutable } from '../../utils/game'
|
||||||
|
import { patchGame, unpatchGame } from '../../utils/metadata'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
[key: string]: any
|
openExtras: (playGame: () => void) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
grasscutterEnabled: boolean;
|
grasscutterEnabled: boolean
|
||||||
buttonLabel: string;
|
buttonLabel: string
|
||||||
checkboxLabel: string;
|
checkboxLabel: string
|
||||||
ip: string;
|
ip: string
|
||||||
port: string;
|
port: string
|
||||||
|
|
||||||
ipPlaceholder: string;
|
ipPlaceholder: string
|
||||||
portPlaceholder: string;
|
portPlaceholder: string
|
||||||
|
|
||||||
portHelpText: string;
|
portHelpText: string
|
||||||
|
|
||||||
httpsLabel: string;
|
httpsLabel: string
|
||||||
httpsEnabled: boolean;
|
httpsEnabled: boolean
|
||||||
|
|
||||||
|
swag: boolean
|
||||||
|
akebiSet: boolean
|
||||||
|
migotoSet: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ServerLaunchSection extends React.Component<IProps, IState> {
|
export default class ServerLaunchSection extends React.Component<IProps, IState> {
|
||||||
@@ -45,7 +53,10 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
portPlaceholder: '',
|
portPlaceholder: '',
|
||||||
portHelpText: '',
|
portHelpText: '',
|
||||||
httpsLabel: '',
|
httpsLabel: '',
|
||||||
httpsEnabled: false
|
httpsEnabled: false,
|
||||||
|
swag: false,
|
||||||
|
akebiSet: false,
|
||||||
|
migotoSet: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.toggleGrasscutter = this.toggleGrasscutter.bind(this)
|
this.toggleGrasscutter = this.toggleGrasscutter.bind(this)
|
||||||
@@ -69,6 +80,9 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
portHelpText: await translate('help.port_help_text'),
|
portHelpText: await translate('help.port_help_text'),
|
||||||
httpsLabel: await translate('main.https_enable'),
|
httpsLabel: await translate('main.https_enable'),
|
||||||
httpsEnabled: config.https_enabled || false,
|
httpsEnabled: config.https_enabled || false,
|
||||||
|
swag: config.swag_mode || false,
|
||||||
|
akebiSet: config.akebi_path !== '',
|
||||||
|
migotoSet: config.migoto_path !== '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,65 +93,81 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
|
|
||||||
// Set state as well
|
// Set state as well
|
||||||
this.setState({
|
this.setState({
|
||||||
grasscutterEnabled: config.toggle_grasscutter
|
grasscutterEnabled: config.toggle_grasscutter,
|
||||||
})
|
})
|
||||||
|
|
||||||
await saveConfig(config)
|
await saveConfig(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
async playGame() {
|
async playGame(exe?: string, proc_name?: string) {
|
||||||
const config = await getConfig()
|
const config = await getConfig()
|
||||||
|
|
||||||
if (!config.game_install_path) return alert('Game path not set!')
|
if (!(await getGameExecutable())) {
|
||||||
|
alert('Game executable not set!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Connect to proxy
|
// Connect to proxy
|
||||||
if (config.toggle_grasscutter) {
|
if (config.toggle_grasscutter) {
|
||||||
let game_exe = config.game_install_path
|
if (config.patch_metadata) {
|
||||||
|
const patched = await patchGame()
|
||||||
|
|
||||||
if (game_exe.includes('\\')) {
|
if (!patched) {
|
||||||
game_exe = game_exe.substring(config.game_install_path.lastIndexOf('\\') + 1)
|
alert('Could not patch game!')
|
||||||
} else {
|
return
|
||||||
game_exe = game_exe.substring(config.game_install_path.lastIndexOf('/') + 1)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const game_exe = await getGameExecutable()
|
||||||
|
|
||||||
// Save last connected server and port
|
// Save last connected server and port
|
||||||
await setConfigOption('last_ip', this.state.ip)
|
await setConfigOption('last_ip', this.state.ip)
|
||||||
await setConfigOption('last_port', this.state.port)
|
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', {
|
await invoke('enable_process_watcher', {
|
||||||
process: game_exe
|
process: proc_name || game_exe,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Connect to proxy
|
if (config.use_internal_proxy) {
|
||||||
await invoke('connect', { port: 8365, certificatePath: await dataDir() + '\\cultivation\\ca' })
|
// 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
|
// Open server as well if the options are set
|
||||||
if (config.grasscutter_with_game) {
|
if (config.grasscutter_with_game) {
|
||||||
let jarFolder = config.grasscutter_path
|
const jarFolderArr = config.grasscutter_path.replace(/\\/g, '/').split('/')
|
||||||
|
jarFolderArr.pop()
|
||||||
|
|
||||||
if (jarFolder.includes('/')) {
|
const jarFolder = jarFolderArr.join('/')
|
||||||
jarFolder = jarFolder.substring(0, config.grasscutter_path.lastIndexOf('/'))
|
|
||||||
} else {
|
|
||||||
jarFolder = jarFolder.substring(0, config.grasscutter_path.lastIndexOf('\\'))
|
|
||||||
}
|
|
||||||
|
|
||||||
await invoke('run_jar', {
|
await invoke('run_jar', {
|
||||||
path: config.grasscutter_path,
|
path: config.grasscutter_path,
|
||||||
executeIn: jarFolder,
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Launch the program
|
// Launch the program
|
||||||
const gameExists = await invoke('dir_exists', {
|
const gameExists = await invoke('dir_exists', {
|
||||||
path: config.game_install_path
|
path: exe || config.game_install_path,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (gameExists) await invoke('run_program', { path: config.game_install_path })
|
if (gameExists) await invoke('run_program_relative', { path: exe || config.game_install_path })
|
||||||
else alert('Game not found! At: ' + config.game_install_path)
|
else alert('Game not found! At: ' + (exe || config.game_install_path))
|
||||||
}
|
}
|
||||||
|
|
||||||
async launchServer() {
|
async launchServer() {
|
||||||
@@ -157,19 +187,19 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
await invoke('run_jar', {
|
await invoke('run_jar', {
|
||||||
path: config.grasscutter_path,
|
path: config.grasscutter_path,
|
||||||
executeIn: jarFolder,
|
executeIn: jarFolder,
|
||||||
javaPath: config.java_path || ''
|
javaPath: config.java_path || '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setIp(text: string) {
|
setIp(text: string) {
|
||||||
this.setState({
|
this.setState({
|
||||||
ip: text
|
ip: text,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setPort(text: string) {
|
setPort(text: string) {
|
||||||
this.setState({
|
this.setState({
|
||||||
port: text
|
port: text,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +210,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
|
|
||||||
// Set state as well
|
// Set state as well
|
||||||
this.setState({
|
this.setState({
|
||||||
httpsEnabled: config.https_enabled
|
httpsEnabled: config.https_enabled,
|
||||||
})
|
})
|
||||||
|
|
||||||
await saveConfig(config)
|
await saveConfig(config)
|
||||||
@@ -190,28 +220,54 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
return (
|
return (
|
||||||
<div id="playButton">
|
<div id="playButton">
|
||||||
<div id="serverControls">
|
<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>
|
</div>
|
||||||
|
|
||||||
{
|
{this.state.grasscutterEnabled && (
|
||||||
this.state.grasscutterEnabled && (
|
<div>
|
||||||
<div>
|
<div className="ServerConfig" id="serverConfigContainer">
|
||||||
<div className="ServerConfig" id="serverConfigContainer">
|
<TextInput
|
||||||
<TextInput id="ip" key="ip" placeholder={this.state.ipPlaceholder} onChange={this.setIp} initalValue={this.state.ip} />
|
id="ip"
|
||||||
<TextInput style={{
|
key="ip"
|
||||||
|
placeholder={this.state.ipPlaceholder}
|
||||||
|
onChange={this.setIp}
|
||||||
|
initalValue={this.state.ip}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={{
|
||||||
width: '10%',
|
width: '10%',
|
||||||
}} id="port" key="port" placeholder={this.state.portPlaceholder} onChange={this.setPort} initalValue={this.state.port} />
|
}}
|
||||||
<HelpButton contents={this.state.portHelpText} />
|
id="port"
|
||||||
<Checkbox id="httpsEnable" label={this.state.httpsLabel} onChange={this.toggleHttps} checked={this.state.httpsEnabled} />
|
key="port"
|
||||||
</div>
|
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>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
<div className="ServerLaunchButtons" id="serverLaunchContainer">
|
<div className="ServerLaunchButtons" id="serverLaunchContainer">
|
||||||
<BigButton onClick={this.playGame} id="officialPlay">{this.state.buttonLabel}</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">
|
<BigButton onClick={this.launchServer} id="serverLaunch">
|
||||||
<img className="ServerIcon" id="serverLaunchIcon" src={Server} />
|
<img className="ServerIcon" id="serverLaunchIcon" src={Server} />
|
||||||
</BigButton>
|
</BigButton>
|
||||||
@@ -219,4 +275,4 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,4 +25,30 @@
|
|||||||
#version {
|
#version {
|
||||||
margin: 0px 6px;
|
margin: 0px 6px;
|
||||||
color: #434343;
|
color: #434343;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#unassumingButton {
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0px 6px;
|
||||||
|
color: #141414;
|
||||||
|
|
||||||
|
transition: color 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#unassumingButton:hover {
|
||||||
|
color: #434343;
|
||||||
|
}
|
||||||
|
|
||||||
|
#unassumingButton.spin {
|
||||||
|
color: #fff;
|
||||||
|
animation: spin 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { app } from '@tauri-apps/api'
|
import { app } from '@tauri-apps/api'
|
||||||
import { appWindow } from '@tauri-apps/api/window'
|
import { appWindow } from '@tauri-apps/api/window'
|
||||||
import closeIcon from '../../resources/icons/close.svg'
|
import { getConfig, setConfigOption } from '../../utils/configuration'
|
||||||
import minIcon from '../../resources/icons/min.svg'
|
|
||||||
import cogBtn from '../../resources/icons/cog.svg'
|
|
||||||
import downBtn from '../../resources/icons/download.svg'
|
|
||||||
import gameBtn from '../../resources/icons/game.svg'
|
|
||||||
|
|
||||||
import Tr from '../../utils/language'
|
import Tr from '../../utils/language'
|
||||||
|
|
||||||
import './TopBar.css'
|
import './TopBar.css'
|
||||||
|
import closeIcon from '../../resources/icons/close.svg'
|
||||||
|
import minIcon from '../../resources/icons/min.svg'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
optFunc: () => void;
|
children?: React.ReactNode | React.ReactNode[]
|
||||||
downFunc: () => void;
|
|
||||||
gameFunc: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
version: string;
|
version: string
|
||||||
|
clicks: number
|
||||||
|
intv: NodeJS.Timeout | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TopBar extends React.Component<IProps, IState> {
|
export default class TopBar extends React.Component<IProps, IState> {
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = { version: '0.0.0' }
|
this.state = {
|
||||||
|
version: '0.0.0',
|
||||||
|
clicks: 0,
|
||||||
|
intv: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activateClick = this.activateClick.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
@@ -41,6 +44,39 @@ export default class TopBar extends React.Component<IProps, IState> {
|
|||||||
appWindow.minimize()
|
appWindow.minimize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async activateClick() {
|
||||||
|
const config = await getConfig()
|
||||||
|
|
||||||
|
// They already got it, no need to reactivate
|
||||||
|
if (config.swag_mode) return
|
||||||
|
|
||||||
|
if (this.state.clicks === 2) {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Gotta clear it so it goes back to regular colors
|
||||||
|
this.setState({
|
||||||
|
clicks: 0,
|
||||||
|
})
|
||||||
|
}, 600)
|
||||||
|
|
||||||
|
// Activate... SWAG MODE
|
||||||
|
await setConfigOption('swag_mode', true)
|
||||||
|
|
||||||
|
// Reload the window
|
||||||
|
window.location.reload()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.clicks < 3) {
|
||||||
|
this.setState({
|
||||||
|
clicks: this.state.clicks + 1,
|
||||||
|
intv: setTimeout(() => this.setState({ clicks: 0 }), 1500),
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="TopBar" id="topBarContainer" data-tauri-drag-region>
|
<div className="TopBar" id="topBarContainer" data-tauri-drag-region>
|
||||||
@@ -48,26 +84,30 @@ export default class TopBar extends React.Component<IProps, IState> {
|
|||||||
<span data-tauri-drag-region>
|
<span data-tauri-drag-region>
|
||||||
<Tr text="main.title" />
|
<Tr text="main.title" />
|
||||||
</span>
|
</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>
|
</div>
|
||||||
<div className="TopBtns" id="topBarButtonContainer">
|
<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" />
|
<img src={closeIcon} alt="close" />
|
||||||
</div>
|
</div>
|
||||||
<div id="minBtn" onClick={this.handleMinimize} className='TopButton'>
|
<div id="minBtn" onClick={this.handleMinimize} className="TopButton">
|
||||||
<img src={minIcon} alt="minimize" />
|
<img src={minIcon} alt="minimize" />
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsBtn" onClick={this.props.optFunc} className='TopButton'>
|
{this.props.children}
|
||||||
<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> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,4 +27,4 @@
|
|||||||
|
|
||||||
.BigButton.disabled:hover {
|
.BigButton.disabled:hover {
|
||||||
background: linear-gradient(#949494, #9c9c9c);
|
background: linear-gradient(#949494, #9c9c9c);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import React from 'react'
|
|||||||
import './BigButton.css'
|
import './BigButton.css'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode
|
||||||
onClick: () => any;
|
onClick: () => unknown
|
||||||
id: string;
|
id: string
|
||||||
disabled?: boolean;
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
disabled?: boolean;
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class BigButton extends React.Component<IProps, IState> {
|
export default class BigButton extends React.Component<IProps, IState> {
|
||||||
@@ -17,15 +17,15 @@ export default class BigButton extends React.Component<IProps, IState> {
|
|||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
disabled: this.props.disabled
|
disabled: this.props.disabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.handleClick = this.handleClick.bind(this)
|
this.handleClick = this.handleClick.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: IProps, state: IState) {
|
static getDerivedStateFromProps(props: IProps, _state: IState) {
|
||||||
return {
|
return {
|
||||||
disabled: props.disabled
|
disabled: props.disabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,9 +37,13 @@ export default class BigButton extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
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 className="BigButtonText">{this.props.children}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.Checkbox input[type="checkbox"] {
|
.Checkbox input[type='checkbox'] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
|
|
||||||
.CheckboxDisplay img {
|
.CheckboxDisplay img {
|
||||||
height: 100%;
|
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 {
|
.Checkbox label {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import checkmark from '../../../resources/icons/check.svg'
|
|||||||
import './Checkbox.css'
|
import './Checkbox.css'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
label?: string,
|
label?: string
|
||||||
checked: boolean,
|
checked: boolean
|
||||||
onChange: () => void,
|
onChange: () => void
|
||||||
id: string
|
id?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
@@ -19,14 +19,14 @@ export default class Checkbox extends React.Component<IProps, IState> {
|
|||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
checked: props.checked
|
checked: props.checked,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: IProps, state: IState) {
|
static getDerivedStateFromProps(props: IProps, state: IState) {
|
||||||
if (props.checked !== state.checked) {
|
if (props.checked !== state.checked) {
|
||||||
return {
|
return {
|
||||||
checked: props.checked
|
checked: props.checked,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,14 +41,12 @@ export default class Checkbox extends React.Component<IProps, IState> {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="Checkbox">
|
<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}>
|
<label htmlFor={this.props.id}>
|
||||||
<div className="CheckboxDisplay">
|
<div className="CheckboxDisplay">{this.state.checked ? <img src={checkmark} alt="Checkmark" /> : null}</div>
|
||||||
{this.state.checked ? <img src={checkmark} alt='Checkmark' /> : null}
|
|
||||||
</div>
|
|
||||||
<span>{this.props.label || ''}</span>
|
<span>{this.props.label || ''}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,4 @@
|
|||||||
|
|
||||||
.FileSelectIcon img {
|
.FileSelectIcon img {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ interface IProps {
|
|||||||
placeholder?: string
|
placeholder?: string
|
||||||
folder?: boolean
|
folder?: boolean
|
||||||
customClearBehaviour?: () => void
|
customClearBehaviour?: () => void
|
||||||
|
openFolder?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
@@ -30,7 +31,7 @@ export default class DirInput extends React.Component<IProps, IState> {
|
|||||||
this.state = {
|
this.state = {
|
||||||
value: props.value || '',
|
value: props.value || '',
|
||||||
placeholder: this.props.placeholder || 'Select file or folder...',
|
placeholder: this.props.placeholder || 'Select file or folder...',
|
||||||
folder: this.props.folder || false
|
folder: this.props.folder || false,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.handleIconClick = this.handleIconClick.bind(this)
|
this.handleIconClick = this.handleIconClick.bind(this)
|
||||||
@@ -53,8 +54,8 @@ export default class DirInput extends React.Component<IProps, IState> {
|
|||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
if (!this.props.placeholder) {
|
if (!this.props.placeholder) {
|
||||||
const translation = await translate('components.select_file')
|
const translation = await translate('components.select_file')
|
||||||
this.setState( {
|
this.setState({
|
||||||
placeholder: translation
|
placeholder: translation,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,13 +65,12 @@ export default class DirInput extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
if (this.state.folder) {
|
if (this.state.folder) {
|
||||||
path = await open({
|
path = await open({
|
||||||
directory: true
|
directory: true,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
path = await open({
|
path = await open({
|
||||||
filters: [
|
filters: [{ name: 'Files', extensions: this.props.extensions || ['*'] }],
|
||||||
{ name: 'Files', extensions: this.props.extensions || ['*'] }
|
defaultPath: this.props.openFolder,
|
||||||
]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ export default class DirInput extends React.Component<IProps, IState> {
|
|||||||
if (!path) return
|
if (!path) return
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
value: path
|
value: path,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.props.onChange) this.props.onChange(path)
|
if (this.props.onChange) this.props.onChange(path)
|
||||||
@@ -86,12 +86,13 @@ export default class DirInput extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='DirInput'>
|
<div className="DirInput">
|
||||||
<TextInput
|
<TextInput
|
||||||
value={this.state.value}
|
value={this.state.value}
|
||||||
placeholder={this.state.placeholder}
|
placeholder={this.state.placeholder}
|
||||||
clearable={this.props.clearable !== undefined ? this.props.clearable : true}
|
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 })
|
this.setState({ value: text })
|
||||||
|
|
||||||
if (this.props.onChange) this.props.onChange(text)
|
if (this.props.onChange) this.props.onChange(text)
|
||||||
@@ -105,4 +106,4 @@ export default class DirInput extends React.Component<IProps, IState> {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import DownloadSection from './DownloadSection'
|
|||||||
import './DownloadList.css'
|
import './DownloadList.css'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
downloadManager: DownloadHandler;
|
downloadManager: DownloadHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class DownloadList extends React.Component<IProps, never> {
|
export default class DownloadList extends React.Component<IProps, never> {
|
||||||
@@ -16,17 +16,14 @@ export default class DownloadList extends React.Component<IProps, never> {
|
|||||||
render() {
|
render() {
|
||||||
const list = this.props.downloadManager.getDownloads().map((download) => {
|
const list = this.props.downloadManager.getDownloads().map((download) => {
|
||||||
return (
|
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 {
|
.DownloadStatus {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import ProgressBar from './ProgressBar'
|
|||||||
import './DownloadSection.css'
|
import './DownloadSection.css'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
downloadManager: DownloadHandler;
|
downloadManager: DownloadHandler
|
||||||
downloadName: string;
|
downloadName: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class DownloadSection extends React.Component<IProps, never> {
|
export default class DownloadSection extends React.Component<IProps, never> {
|
||||||
@@ -32,4 +32,4 @@ export default class DownloadSection extends React.Component<IProps, never> {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,4 +30,4 @@
|
|||||||
right: -450%;
|
right: -450%;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Help from '../../../resources/icons/help.svg'
|
|||||||
import MiniDialog from '../MiniDialog'
|
import MiniDialog from '../MiniDialog'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
children?: React.ReactNode[] | React.ReactNode;
|
children?: React.ReactNode[] | React.ReactNode
|
||||||
contents?: string
|
contents?: string
|
||||||
id?: string
|
id?: string
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ export default class HelpButton extends React.Component<IProps, IState> {
|
|||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
opened: false
|
opened: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setOpen = this.setOpen.bind(this)
|
this.setOpen = this.setOpen.bind(this)
|
||||||
@@ -41,14 +41,15 @@ export default class HelpButton extends React.Component<IProps, IState> {
|
|||||||
<img src={Help} />
|
<img src={Help} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="HelpContents" style={{
|
<div
|
||||||
display: this.state.opened ? 'block' : 'none'
|
className="HelpContents"
|
||||||
}}>
|
style={{
|
||||||
<MiniDialog closeFn={this.setClosed}>
|
display: this.state.opened ? 'block' : 'none',
|
||||||
{this.props.contents || this.props.children}
|
}}
|
||||||
</MiniDialog>
|
>
|
||||||
|
<MiniDialog closeFn={this.setClosed}>{this.props.contents || this.props.children}</MiniDialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ import Tr from '../../../utils/language'
|
|||||||
import './ProgressBar.css'
|
import './ProgressBar.css'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
downloadManager: DownloadHandler,
|
downloadManager: DownloadHandler
|
||||||
|
withStats?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
average: number,
|
average: number
|
||||||
files: number,
|
files: number
|
||||||
extracting: number,
|
extracting: number
|
||||||
total: number,
|
total: number
|
||||||
speed: string,
|
speed: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,7 +30,7 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
|||||||
files,
|
files,
|
||||||
extracting,
|
extracting,
|
||||||
total: totalSize,
|
total: totalSize,
|
||||||
speed: '0 B/s'
|
speed: '0 B/s',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,28 +52,33 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
|||||||
return (
|
return (
|
||||||
<div className="MainProgressBarWrapper">
|
<div className="MainProgressBarWrapper">
|
||||||
<div className="ProgressBar">
|
<div className="ProgressBar">
|
||||||
<div className="InnerProgress" style={{
|
<div
|
||||||
width: `${(() => {
|
className="InnerProgress"
|
||||||
// Handles no files downloading
|
style={{
|
||||||
if (this.state.files === 0) {
|
width: `${(() => {
|
||||||
return '100'
|
// Handles no files downloading
|
||||||
}
|
if (this.state.files === 0) {
|
||||||
|
return '100'
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.total <= 0) {
|
if (this.state.total <= 0) {
|
||||||
return '0'
|
return '0'
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.state.average
|
return this.state.average
|
||||||
})()}%`,
|
})()}%`,
|
||||||
}}></div>
|
}}
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="MainProgressText">
|
{(this.props.withStats === undefined || this.props.withStats) && (
|
||||||
<Tr text="main.files_downloading" /> {this.state.files} ({this.state.speed})
|
<div className="MainProgressText">
|
||||||
<br />
|
<Tr text="main.files_downloading" /> {this.state.files} ({this.state.speed})
|
||||||
<Tr text="main.files_extracting" /> {this.state.extracting}
|
<br />
|
||||||
</div>
|
<Tr text="main.files_extracting" /> {this.state.extracting}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
.ProgressBar, .InnerProgress {
|
.ProgressBar,
|
||||||
|
.InnerProgress {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,4 +92,4 @@
|
|||||||
|
|
||||||
.downloadStop:hover {
|
.downloadStop:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { capitalize } from '../../../utils/string'
|
import { capitalize } from '../../../utils/string'
|
||||||
|
|
||||||
import Stop from '../../../resources/icons/close.svg'
|
import Stop from '../../../resources/icons/close.svg'
|
||||||
import './ProgressBar.css'
|
import './ProgressBar.css'
|
||||||
import DownloadHandler from '../../../utils/download'
|
import DownloadHandler from '../../../utils/download'
|
||||||
import { translate } from '../../../utils/language'
|
import { translate } from '../../../utils/language'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
path: string,
|
path: string
|
||||||
downloadManager: DownloadHandler,
|
downloadManager: DownloadHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
progress: number,
|
progress: number
|
||||||
status: string,
|
status: string
|
||||||
total: number,
|
total: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ProgressBar extends React.Component<IProps, IState> {
|
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)
|
const prog = this.props.downloadManager.getDownloadProgress(this.props.path)
|
||||||
this.setState({
|
this.setState({
|
||||||
progress: prog?.progress || 0,
|
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,
|
total: prog?.total || 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -54,24 +54,29 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="ProgressBarWrapper">
|
<div className="ProgressBarWrapper">
|
||||||
<div style={{
|
<div
|
||||||
width: '80%'
|
style={{
|
||||||
}}>
|
width: '80%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="ProgressBar">
|
<div className="ProgressBar">
|
||||||
<div className="InnerProgress" style={{
|
<div
|
||||||
width: `${(() => {
|
className="InnerProgress"
|
||||||
// Handles files with content-lengths of 0
|
style={{
|
||||||
if (this.state.status === 'finished') {
|
width: `${(() => {
|
||||||
return '100'
|
// Handles files with content-lengths of 0
|
||||||
}
|
if (this.state.status === 'finished') {
|
||||||
|
return '100'
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.total <= 0) {
|
if (this.state.total <= 0) {
|
||||||
return '0'
|
return '0'
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.state.progress / this.state.total * 100
|
return (this.state.progress / this.state.total) * 100
|
||||||
})()}%`,
|
})()}%`,
|
||||||
}}></div>
|
}}
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="DownloadControls">
|
<div className="DownloadControls">
|
||||||
<div onClick={this.stopDownload} className="downloadStop">
|
<div onClick={this.stopDownload} className="downloadStop">
|
||||||
@@ -80,10 +85,8 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ProgressText">
|
<div className="ProgressText">{capitalize(this.state.status) || 'Waiting'}</div>
|
||||||
{capitalize(this.state.status) || 'Waiting'}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 16%;
|
right: 16%;
|
||||||
|
|
||||||
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%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,4 +28,4 @@
|
|||||||
|
|
||||||
.TextInputClear {
|
.TextInputClear {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,15 @@ import './TextInput.css'
|
|||||||
import Close from '../../../resources/icons/close.svg'
|
import Close from '../../../resources/icons/close.svg'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
value?: string;
|
value?: string
|
||||||
initalValue?: string;
|
initalValue?: string
|
||||||
placeholder?: string;
|
placeholder?: string
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void
|
||||||
readOnly?: boolean;
|
readOnly?: boolean
|
||||||
id?: string;
|
id?: string
|
||||||
clearable?: boolean;
|
clearable?: boolean
|
||||||
customClearBehaviour?: () => void;
|
customClearBehaviour?: () => void
|
||||||
style?: {
|
style?: React.CSSProperties
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
@@ -26,14 +24,14 @@ export default class TextInput extends React.Component<IProps, IState> {
|
|||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
value: props.value || ''
|
value: props.value || '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
if (this.props.initalValue) {
|
if (this.props.initalValue) {
|
||||||
this.setState({
|
this.setState({
|
||||||
value: this.props.initalValue
|
value: this.props.initalValue,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,26 +43,35 @@ export default class TextInput extends React.Component<IProps, IState> {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="TextInputWrapper" style={this.props.style || {}}>
|
<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) => {
|
<input
|
||||||
this.setState({ value: e.target.value })
|
id={this.props?.id}
|
||||||
if (this.props.onChange) this.props.onChange(e.target.value)
|
readOnly={this.props.readOnly || false}
|
||||||
}} />
|
placeholder={this.props.placeholder || ''}
|
||||||
{
|
className="TextInput"
|
||||||
this.props.clearable ?
|
value={this.state.value}
|
||||||
<div className="TextClear" onClick={() => {
|
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
|
// Run custom behaviour first
|
||||||
if (this.props.customClearBehaviour) return this.props.customClearBehaviour()
|
if (this.props.customClearBehaviour) return this.props.customClearBehaviour()
|
||||||
|
|
||||||
this.setState({ value: '' })
|
this.setState({ value: '' })
|
||||||
|
|
||||||
if (this.props.onChange) this.props.onChange('')
|
if (this.props.onChange) this.props.onChange('')
|
||||||
|
|
||||||
this.forceUpdate()
|
this.forceUpdate()
|
||||||
}}>
|
}}
|
||||||
<img src={Close} className="TextInputClear" />
|
>
|
||||||
</div> : null
|
<img src={Close} className="TextInputClear" />
|
||||||
}
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
.Divider {
|
.Divider {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -13,4 +12,4 @@
|
|||||||
.DividerLine {
|
.DividerLine {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,4 @@ export default class Divider extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,4 +28,4 @@
|
|||||||
|
|
||||||
.DownloadMenuSection .HelpButton img {
|
.DownloadMenuSection .HelpButton img {
|
||||||
filter: none;
|
filter: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { dataDir } from '@tauri-apps/api/path'
|
|||||||
|
|
||||||
import './Downloads.css'
|
import './Downloads.css'
|
||||||
import Divider from './Divider'
|
import Divider from './Divider'
|
||||||
import { getConfigOption, setConfigOption } from '../../../utils/configuration'
|
import { getConfigOption } from '../../../utils/configuration'
|
||||||
import { invoke } from '@tauri-apps/api'
|
import { invoke } from '@tauri-apps/api'
|
||||||
import { listen } from '@tauri-apps/api/event'
|
import { listen } from '@tauri-apps/api/event'
|
||||||
import HelpButton from '../common/HelpButton'
|
import HelpButton from '../common/HelpButton'
|
||||||
@@ -17,11 +17,11 @@ const STABLE_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archiv
|
|||||||
const DEV_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/development.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 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 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/yukiz/GrasscutterResources/-/archive/2.8/GrasscutterResources-2.8.zip'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
closeFn: () => void;
|
closeFn: () => void
|
||||||
downloadManager: DownloadHandler;
|
downloadManager: DownloadHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
@@ -41,7 +41,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
resources_downloading: this.props.downloadManager.downloadingResources(),
|
resources_downloading: this.props.downloadManager.downloadingResources(),
|
||||||
repo_downloading: this.props.downloadManager.downloadingRepo(),
|
repo_downloading: this.props.downloadManager.downloadingRepo(),
|
||||||
grasscutter_set: false,
|
grasscutter_set: false,
|
||||||
resources_exist: false
|
resources_exist: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getGrasscutterFolder = this.getGrasscutterFolder.bind(this)
|
this.getGrasscutterFolder = this.getGrasscutterFolder.bind(this)
|
||||||
@@ -63,7 +63,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
if (!gc_path || gc_path === '') {
|
if (!gc_path || gc_path === '') {
|
||||||
this.setState({
|
this.setState({
|
||||||
grasscutter_set: false,
|
grasscutter_set: false,
|
||||||
resources_exist: false
|
resources_exist: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -72,15 +72,17 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
const path = gc_path.substring(0, gc_path.lastIndexOf('\\'))
|
const path = gc_path.substring(0, gc_path.lastIndexOf('\\'))
|
||||||
|
|
||||||
if (gc_path) {
|
if (gc_path) {
|
||||||
const resources_exist: boolean = await invoke('dir_exists', {
|
const resources_exist: boolean =
|
||||||
path: path + '\\resources'
|
((await invoke('dir_exists', {
|
||||||
}) as boolean && !(await invoke('dir_is_empty', {
|
path: path + '\\resources',
|
||||||
path: path + '\\resources'
|
})) as boolean) &&
|
||||||
})) as boolean
|
(!(await invoke('dir_is_empty', {
|
||||||
|
path: path + '\\resources',
|
||||||
|
})) as boolean)
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
grasscutter_set: gc_path !== '',
|
grasscutter_set: gc_path !== '',
|
||||||
resources_exist
|
resources_exist,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,8 +111,9 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
async downloadGrasscutterStableRepo() {
|
async downloadGrasscutterStableRepo() {
|
||||||
const folder = await this.getGrasscutterFolder()
|
const folder = await this.getGrasscutterFolder()
|
||||||
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () =>{
|
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
|
||||||
unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
|
await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
|
||||||
|
this.toggleButtons()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.toggleButtons()
|
this.toggleButtons()
|
||||||
@@ -118,8 +121,9 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
async downloadGrasscutterDevRepo() {
|
async downloadGrasscutterDevRepo() {
|
||||||
const folder = await this.getGrasscutterFolder()
|
const folder = await this.getGrasscutterFolder()
|
||||||
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () =>{
|
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
|
||||||
unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
|
await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
|
||||||
|
this.toggleButtons()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.toggleButtons()
|
this.toggleButtons()
|
||||||
@@ -127,20 +131,22 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
async downloadGrasscutterStable() {
|
async downloadGrasscutterStable() {
|
||||||
const folder = await this.getGrasscutterFolder()
|
const folder = await this.getGrasscutterFolder()
|
||||||
this.props.downloadManager.addDownload(STABLE_DOWNLOAD, folder + '\\grasscutter.zip', () =>{
|
this.props.downloadManager.addDownload(STABLE_DOWNLOAD, folder + '\\grasscutter.zip', async () => {
|
||||||
unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
|
await unzip(folder + '\\grasscutter.zip', folder + '\\', true)
|
||||||
|
this.toggleButtons
|
||||||
})
|
})
|
||||||
|
|
||||||
// Also add repo download
|
// Also add repo download
|
||||||
this.downloadGrasscutterStableRepo()
|
this.downloadGrasscutterStableRepo()
|
||||||
|
|
||||||
this.toggleButtons()
|
this.toggleButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadGrasscutterLatest() {
|
async downloadGrasscutterLatest() {
|
||||||
const folder = await this.getGrasscutterFolder()
|
const folder = await this.getGrasscutterFolder()
|
||||||
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', () =>{
|
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', async () => {
|
||||||
unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
|
await unzip(folder + '\\grasscutter.zip', folder + '\\', true)
|
||||||
|
this.toggleButtons()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Also add repo download
|
// Also add repo download
|
||||||
@@ -152,24 +158,25 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
async downloadResources() {
|
async downloadResources() {
|
||||||
const folder = await this.getGrasscutterFolder()
|
const folder = await this.getGrasscutterFolder()
|
||||||
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '\\resources.zip', async () => {
|
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '\\resources.zip', async () => {
|
||||||
// Delete the existing folder if it exists
|
// Delete the existing folder if it exists
|
||||||
if (await invoke('dir_exists', {
|
if (
|
||||||
path: folder + '\\resources'
|
await invoke('dir_exists', {
|
||||||
})) {
|
path: folder + '\\resources',
|
||||||
|
})
|
||||||
|
) {
|
||||||
await invoke('dir_delete', {
|
await invoke('dir_delete', {
|
||||||
path: folder + '\\resources'
|
path: folder + '\\resources',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await unzip(folder + '\\resources.zip', folder + '\\', () => {
|
await unzip(folder + '\\resources.zip', folder + '\\', true)
|
||||||
// Rename folder to resources
|
// Rename folder to resources
|
||||||
invoke('rename', {
|
invoke('rename', {
|
||||||
path: folder + '\\Resources',
|
path: folder + '\\Resources',
|
||||||
newName: 'resources'
|
newName: 'resources',
|
||||||
})
|
|
||||||
|
|
||||||
this.toggleButtons()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.toggleButtons()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.toggleButtons()
|
this.toggleButtons()
|
||||||
@@ -183,39 +190,47 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
grasscutter_downloading: this.props.downloadManager.downloadingJar(),
|
grasscutter_downloading: this.props.downloadManager.downloadingJar(),
|
||||||
resources_downloading: this.props.downloadManager.downloadingResources(),
|
resources_downloading: this.props.downloadManager.downloadingResources(),
|
||||||
repo_downloading: this.props.downloadManager.downloadingRepo(),
|
repo_downloading: this.props.downloadManager.downloadingRepo(),
|
||||||
grasscutter_set: gc_path && gc_path !== '',
|
grasscutter_set: gc_path !== '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Menu closeFn={this.props.closeFn} className="Downloads" heading="Downloads">
|
<Menu closeFn={this.props.closeFn} className="Downloads" heading="Downloads">
|
||||||
<div className='DownloadMenuSection' id="downloadMenuContainerGCStable">
|
<div className="DownloadMenuSection" id="downloadMenuContainerGCStable">
|
||||||
<div className='DownloadLabel' id="downloadMenuLabelGCStable">
|
<div className="DownloadLabel" id="downloadMenuLabelGCStable">
|
||||||
<Tr text={
|
<Tr
|
||||||
this.state.grasscutter_set ? 'downloads.grasscutter_stable' : 'downloads.grasscutter_stable_update'
|
text={this.state.grasscutter_set ? 'downloads.grasscutter_stable' : 'downloads.grasscutter_stable_update'}
|
||||||
} />
|
/>
|
||||||
<HelpButton>
|
<HelpButton>
|
||||||
<Tr text="help.gc_stable_jar" />
|
<Tr text="help.gc_stable_jar" />
|
||||||
</HelpButton>
|
</HelpButton>
|
||||||
</div>
|
</div>
|
||||||
<div className='DownloadValue' id="downloadMenuButtonGCStable">
|
<div className="DownloadValue" id="downloadMenuButtonGCStable">
|
||||||
<BigButton disabled={this.state.grasscutter_downloading} onClick={this.downloadGrasscutterStable} id="grasscutterStableBtn" >
|
<BigButton
|
||||||
|
disabled={this.state.grasscutter_downloading}
|
||||||
|
onClick={this.downloadGrasscutterStable}
|
||||||
|
id="grasscutterStableBtn"
|
||||||
|
>
|
||||||
<Tr text="components.download" />
|
<Tr text="components.download" />
|
||||||
</BigButton>
|
</BigButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='DownloadMenuSection' id="downloadMenuContainerGCDev">
|
<div className="DownloadMenuSection" id="downloadMenuContainerGCDev">
|
||||||
<div className='DownloadLabel' id="downloadMenuLabelGCDev">
|
<div className="DownloadLabel" id="downloadMenuLabelGCDev">
|
||||||
<Tr text={
|
<Tr
|
||||||
this.state.grasscutter_set ? 'downloads.grasscutter_latest' : 'downloads.grasscutter_latest_update'
|
text={this.state.grasscutter_set ? 'downloads.grasscutter_latest' : 'downloads.grasscutter_latest_update'}
|
||||||
} />
|
/>
|
||||||
<HelpButton>
|
<HelpButton>
|
||||||
<Tr text="help.gc_dev_jar" />
|
<Tr text="help.gc_dev_jar" />
|
||||||
</HelpButton>
|
</HelpButton>
|
||||||
</div>
|
</div>
|
||||||
<div className='DownloadValue' id="downloadMenuButtonGCDev">
|
<div className="DownloadValue" id="downloadMenuButtonGCDev">
|
||||||
<BigButton disabled={this.state.grasscutter_downloading} onClick={this.downloadGrasscutterLatest} id="grasscutterLatestBtn" >
|
<BigButton
|
||||||
|
disabled={this.state.grasscutter_downloading}
|
||||||
|
onClick={this.downloadGrasscutterLatest}
|
||||||
|
id="grasscutterLatestBtn"
|
||||||
|
>
|
||||||
<Tr text="components.download" />
|
<Tr text="components.download" />
|
||||||
</BigButton>
|
</BigButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -223,32 +238,48 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<div className='DownloadMenuSection' id="downloadMenuContainerGCStableData">
|
<div className="DownloadMenuSection" id="downloadMenuContainerGCStableData">
|
||||||
<div className='DownloadLabel' id="downloadMenuLabelGCStableData">
|
<div className="DownloadLabel" id="downloadMenuLabelGCStableData">
|
||||||
<Tr text={
|
<Tr
|
||||||
this.state.grasscutter_set ? 'downloads.grasscutter_stable_data' : 'downloads.grasscutter_stable_data_update'
|
text={
|
||||||
} />
|
this.state.grasscutter_set
|
||||||
|
? 'downloads.grasscutter_stable_data'
|
||||||
|
: 'downloads.grasscutter_stable_data_update'
|
||||||
|
}
|
||||||
|
/>
|
||||||
<HelpButton>
|
<HelpButton>
|
||||||
<Tr text="help.gc_stable_data" />
|
<Tr text="help.gc_stable_data" />
|
||||||
</HelpButton>
|
</HelpButton>
|
||||||
</div>
|
</div>
|
||||||
<div className='DownloadValue' id="downloadMenuButtonGCStableData">
|
<div className="DownloadValue" id="downloadMenuButtonGCStableData">
|
||||||
<BigButton disabled={this.state.repo_downloading} onClick={this.downloadGrasscutterStableRepo} id="grasscutterStableRepo" >
|
<BigButton
|
||||||
|
disabled={this.state.repo_downloading}
|
||||||
|
onClick={this.downloadGrasscutterStableRepo}
|
||||||
|
id="grasscutterStableRepo"
|
||||||
|
>
|
||||||
<Tr text="components.download" />
|
<Tr text="components.download" />
|
||||||
</BigButton>
|
</BigButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='DownloadMenuSection' id="downloadMenuContainerGCDevData">
|
<div className="DownloadMenuSection" id="downloadMenuContainerGCDevData">
|
||||||
<div className='DownloadLabel' id="downloadMenuLabelGCDevData">
|
<div className="DownloadLabel" id="downloadMenuLabelGCDevData">
|
||||||
<Tr text={
|
<Tr
|
||||||
this.state.grasscutter_set ? 'downloads.grasscutter_latest_data' : 'downloads.grasscutter_latest_data_update'
|
text={
|
||||||
} />
|
this.state.grasscutter_set
|
||||||
|
? 'downloads.grasscutter_latest_data'
|
||||||
|
: 'downloads.grasscutter_latest_data_update'
|
||||||
|
}
|
||||||
|
/>
|
||||||
<HelpButton>
|
<HelpButton>
|
||||||
<Tr text="help.gc_dev_data" />
|
<Tr text="help.gc_dev_data" />
|
||||||
</HelpButton>
|
</HelpButton>
|
||||||
</div>
|
</div>
|
||||||
<div className='DownloadValue' id="downloadMenuButtonGCDevData">
|
<div className="DownloadValue" id="downloadMenuButtonGCDevData">
|
||||||
<BigButton disabled={this.state.repo_downloading} onClick={this.downloadGrasscutterStableRepo} id="grasscutterDevRepo" >
|
<BigButton
|
||||||
|
disabled={this.state.repo_downloading}
|
||||||
|
onClick={this.downloadGrasscutterStableRepo}
|
||||||
|
id="grasscutterDevRepo"
|
||||||
|
>
|
||||||
<Tr text="components.download" />
|
<Tr text="components.download" />
|
||||||
</BigButton>
|
</BigButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -256,15 +287,19 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<div className='DownloadMenuSection' id="downloadMenuContainerResources">
|
<div className="DownloadMenuSection" id="downloadMenuContainerResources">
|
||||||
<div className='DownloadLabel' id="downloadMenuLabelResources">
|
<div className="DownloadLabel" id="downloadMenuLabelResources">
|
||||||
<Tr text="downloads.resources" />
|
<Tr text="downloads.resources" />
|
||||||
<HelpButton>
|
<HelpButton>
|
||||||
<Tr text="help.resources" />
|
<Tr text="help.resources" />
|
||||||
</HelpButton>
|
</HelpButton>
|
||||||
</div>
|
</div>
|
||||||
<div className='DownloadValue' id="downloadMenuButtonResources">
|
<div className="DownloadValue" id="downloadMenuButtonResources">
|
||||||
<BigButton disabled={this.state.resources_downloading || !this.state.grasscutter_set || this.state.resources_exist} onClick={this.downloadResources} id="resourcesBtn" >
|
<BigButton
|
||||||
|
disabled={this.state.resources_downloading || !this.state.grasscutter_set || this.state.resources_exist}
|
||||||
|
onClick={this.downloadResources}
|
||||||
|
id="resourcesBtn"
|
||||||
|
>
|
||||||
<Tr text="components.download" />
|
<Tr text="components.download" />
|
||||||
</BigButton>
|
</BigButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -272,4 +307,4 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
</Menu>
|
</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;
|
||||||
|
}
|
||||||
178
src/ui/components/menu/ExtrasMenu.tsx
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
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()],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
.GameDownloadDir .DirInput .TextInputWrapper input {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ import { unzip } from '../../../utils/zipUtils'
|
|||||||
const GAME_DOWNLOAD = ''
|
const GAME_DOWNLOAD = ''
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
closeFn: () => void;
|
closeFn: () => void
|
||||||
downloadManager: DownloadHandler;
|
downloadManager: DownloadHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
gameDownloading: boolean;
|
gameDownloading: boolean
|
||||||
gameDownloadFolder: string;
|
gameDownloadFolder: string
|
||||||
dirPlaceholder: string;
|
dirPlaceholder: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Downloads extends React.Component<IProps, IState> {
|
export default class Downloads extends React.Component<IProps, IState> {
|
||||||
@@ -29,7 +29,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
this.state = {
|
this.state = {
|
||||||
gameDownloading: false,
|
gameDownloading: false,
|
||||||
gameDownloadFolder: '',
|
gameDownloadFolder: '',
|
||||||
dirPlaceholder: ''
|
dirPlaceholder: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
this.downloadGame = this.downloadGame.bind(this)
|
this.downloadGame = this.downloadGame.bind(this)
|
||||||
@@ -37,7 +37,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
this.setState({
|
this.setState({
|
||||||
dirPlaceholder: await translate('components.select_folder')
|
dirPlaceholder: await translate('components.select_folder'),
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(this.state)
|
console.log(this.state)
|
||||||
@@ -45,39 +45,50 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
async downloadGame() {
|
async downloadGame() {
|
||||||
const folder = this.state.gameDownloadFolder
|
const folder = this.state.gameDownloadFolder
|
||||||
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', () =>{
|
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', async () => {
|
||||||
unzip(folder + '\\game.zip', folder + '\\', () => {
|
await unzip(folder + '\\game.zip', folder + '\\', true)
|
||||||
this.setState({
|
this.setState({
|
||||||
gameDownloading: false
|
gameDownloading: false,
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
gameDownloading: true
|
gameDownloading: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Menu heading='Download Game' closeFn={this.props.closeFn} className="GameDownloadMenu">
|
<Menu heading="Download Game" closeFn={this.props.closeFn} className="GameDownloadMenu">
|
||||||
<div className="GameDownload">
|
<div className="GameDownload">
|
||||||
{
|
{this.state.gameDownloadFolder !== '' && !this.state.gameDownloading ? (
|
||||||
this.state.gameDownloadFolder !== '' && !this.state.gameDownloading ?
|
<BigButton id="downloadGameBtn" onClick={this.downloadGame}>
|
||||||
<BigButton id="downloadGameBtn" onClick={this.downloadGame}>Download Game</BigButton>
|
Download Game
|
||||||
: <BigButton id="disabledGameBtn" onClick={() => null} disabled>Download Game</BigButton>
|
</BigButton>
|
||||||
}
|
) : (
|
||||||
|
<BigButton id="disabledGameBtn" onClick={() => null} disabled>
|
||||||
|
Download Game
|
||||||
|
</BigButton>
|
||||||
|
)}
|
||||||
<HelpButton>
|
<HelpButton>
|
||||||
<Tr text="main.game_help_text" />
|
<Tr text="main.game_help_text" />
|
||||||
</HelpButton>
|
</HelpButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="GameDownloadDir">
|
<div className="GameDownloadDir">
|
||||||
<DirInput folder placeholder={this.state.dirPlaceholder} clearable={false} readonly={true} onChange={(value: string) => this.setState({
|
<DirInput
|
||||||
gameDownloadFolder: value
|
folder
|
||||||
})}/>
|
placeholder={this.state.dirPlaceholder}
|
||||||
|
clearable={false}
|
||||||
|
readonly={true}
|
||||||
|
onChange={(value: string) =>
|
||||||
|
this.setState({
|
||||||
|
gameDownloadFolder: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Menu>
|
</Menu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||