mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2025-12-14 08:04:52 +01:00
Compare commits
107 Commits
custom-opt
...
v1.0.4-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
4c9dad49c4 | ||
|
|
960fcae647 | ||
|
|
4172ee9106 | ||
|
|
8566e7f35e | ||
|
|
065043bbe9 | ||
|
|
2c3a23e841 | ||
|
|
0b2296f918 | ||
|
|
6f997a38d3 | ||
|
|
cd128741b4 | ||
|
|
174a7b5163 | ||
|
|
e75474fde7 | ||
|
|
c3119ce7a7 | ||
|
|
6c4b546de2 | ||
|
|
30388eb6a9 | ||
|
|
f35b596eb2 | ||
|
|
3008f50e1f | ||
|
|
99293ad7cf | ||
|
|
99687f0550 | ||
|
|
6124d6949c | ||
|
|
afdbc5ad80 | ||
|
|
dd56af8fcb | ||
|
|
487b36a37e | ||
|
|
850b282b70 | ||
|
|
e0272aa38a | ||
|
|
27d7c32a73 | ||
|
|
936c533ff8 | ||
|
|
1c0edd2bcd | ||
|
|
8ac4d063a4 | ||
|
|
bb9a044e05 | ||
|
|
4f63e55a28 |
@@ -13,6 +13,8 @@ trim_trailing_whitespace = false
|
||||
[*.rs]
|
||||
max_line_length = 100
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{*.ats,*.cts,*.mts,*.ts}]
|
||||
indent_size = 2
|
||||
|
||||
@@ -34,6 +34,26 @@
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"no-explicit-any": "off"
|
||||
"@typescript-eslint/ban-types": [
|
||||
"warn",
|
||||
{
|
||||
"extendDefaults": true,
|
||||
"types": {
|
||||
"{}": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
62
.github/workflows/backend-checks.yml
vendored
Normal file
62
.github/workflows/backend-checks.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
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
|
||||
|
||||
37
.github/workflows/frontend-checks.yml
vendored
Normal file
37
.github/workflows/frontend-checks.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Check frontend
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/frontend-checks.yml"
|
||||
- "src/**"
|
||||
- ".eslintrc.json"
|
||||
- "package.json"
|
||||
- "tsconfig.json"
|
||||
- "yarn.lock"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/frontend-checks.yml"
|
||||
- "src/**"
|
||||
- ".eslintrc.json"
|
||||
- "package.json"
|
||||
- "tsconfig.json"
|
||||
- "yarn.lock"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}-${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tsc-eslint-checks:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install modules
|
||||
run: yarn
|
||||
- name: Run tsc
|
||||
run: yarn tsc --noEmit
|
||||
- name: Run ESLint
|
||||
run: yarn eslint src
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -23,4 +23,5 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# moved lang files
|
||||
/lang
|
||||
/lang
|
||||
package-lock.json
|
||||
|
||||
51
README.md
51
README.md
@@ -1,27 +1,23 @@
|
||||
# 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.**\
|
||||
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** and a majority of features do not work.\
|
||||
There are **no official releases of Cultivation**. You are **required** to build the application from **scratch**.\
|
||||
Please do **NOT install, download, or use pre-compiled versions of Cultivation**. Only use releases from this GitHub repository.
|
||||
# Client Patching Notice
|
||||
For game versions 2.8 and above, Cultivation automatically makes a small patch to your game client when launching using Grasscutter, and restores it upon closing the game. In theory, you should still be totally safe, however it would be dishonest to not explicitly state that **modifying the game client could, theoretically, lead to a ban if you connect to official servers with it**. It is extremely unlikely AND there are no instances known of it happening, but the possibility exists.
|
||||
|
||||
# Cultivation
|
||||
A game launcher designed to easily proxy traffic from anime game to private servers.
|
||||
|
||||
While the Cultivation repository is **open**. This does **not** mean it has released.
|
||||
Please do **NOT install, download, or use pre-compiled versions of Cultivation found elsewhere**. Only use releases from this GitHub repository.
|
||||
|
||||
# Table Of Contents
|
||||
* [Download](#download)
|
||||
* [Developer Quick-start](#developer-quickstart)
|
||||
* [Setup](#setup)
|
||||
* [Building](#building)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* [Code Formatting and Linting](#code-formatting-and-linting)
|
||||
* [Generating Update Artifacts](#generating-update-artifacts)
|
||||
* [Theming](#theming)
|
||||
* [Screenshots](#screenshots)
|
||||
* [Credits](#credits)
|
||||
|
||||
|
||||
# Download
|
||||
[Find release builds here!](https://github.com/Grasscutters/Cultivation/releases)
|
||||
@@ -32,23 +28,28 @@ Once downloaded, extract somewhere and open as administrator.
|
||||
|
||||
### 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)
|
||||
* `npm install` or `yarn install`
|
||||
* `npm run start:dev` or `yarn start:dev`
|
||||
* Install [yarn](https://classic.yarnpkg.com/lang/en/docs/install) (cry about it `npm` lovers)
|
||||
* Install [Rust](https://www.rust-lang.org/tools/install)
|
||||
* `yarn install`
|
||||
* `yarn start:dev`
|
||||
|
||||
### Building
|
||||
`npm run build` or `yarn build`
|
||||
For a release build,
|
||||
- `yarn build`
|
||||
|
||||
Add `--release` or `--debug` depending on what release you are creating. This defaults to `--release`
|
||||
For a debug build,
|
||||
- `yarn build --debug`
|
||||
|
||||
### Updating
|
||||
### Code Formatting and Linting
|
||||
- `yarn format`
|
||||
- `yarn lint`
|
||||
|
||||
### 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.
|
||||
* Run `npm run update` or `yarn build`
|
||||
* The update will be in `src-tauri/target/(release|debug)/msi/Cultivation_X.X.X_x64_xx-XX.msi.zip`
|
||||
* `yarn build`
|
||||
|
||||
# Troubleshooting
|
||||
TODO. Collect common issues before updating.
|
||||
The update will be at `src-tauri/target/(release|debug)/msi/Cultivation_X.X.X_x64_xx-XX.msi.zip`
|
||||
|
||||
# Theming
|
||||
|
||||
@@ -64,4 +65,6 @@ A full theming reference can be found [here!](/THEMES.md)
|
||||
## 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.
|
||||
* [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.
|
||||
|
||||
12
package.json
12
package.json
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "cultivation",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.4",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.0.0-rc.5",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^13.0.0",
|
||||
"@testing-library/user-event": "^13.2.1",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/node": "^16.7.13",
|
||||
"@testing-library/user-event": "^14.2.6",
|
||||
"@types/jest": "^28.1.6",
|
||||
"@types/node": "^18.0.6",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"react": "^18.1.0",
|
||||
@@ -27,7 +27,9 @@
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"tauri": "tauri",
|
||||
"start:dev": "tauri dev"
|
||||
"start:dev": "tauri dev",
|
||||
"format": "cargo fmt --manifest-path ./src-tauri/Cargo.toml --all",
|
||||
"lint": "cargo clippy --manifest-path ./src-tauri/Cargo.toml --no-default-features && yarn tsc --noEmit && yarn eslint src"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
||||
@@ -12,9 +12,7 @@
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>Cultivation</title>
|
||||
<script src="%PUBLIC_URL%/theme-engine.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Passes a message through to the React backend.
|
||||
* @param type The message type.
|
||||
* @param data The message data.
|
||||
*/
|
||||
function passthrough(type, data) {
|
||||
document.dispatchEvent(new CustomEvent('domMessage', {
|
||||
type, msg: data
|
||||
}))
|
||||
}
|
||||
|
||||
function setConfigValue(key, value) {
|
||||
passthrough('updateConfig', {setting: key, value})
|
||||
}
|
||||
1313
src-tauri/Cargo.lock
generated
1313
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "cultivation"
|
||||
version = "1.0.1"
|
||||
version = "0.1.0"
|
||||
description = "A custom launcher for anime game."
|
||||
authors = ["KingRainbow44", "SpikeHD"]
|
||||
license = "Apache-2.0"
|
||||
license = ""
|
||||
repository = "https://github.com/Grasscutters/Cultivation.git"
|
||||
default-run = "cultivation"
|
||||
edition = "2021"
|
||||
@@ -13,35 +13,36 @@ rust-version = "1.57"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.0.0-rc.8", features = [] }
|
||||
cc = "1.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
is_elevated = "0.1.2"
|
||||
registry = "1.2.1"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
sudo = "0.6.0"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.0.0-rc.9", features = ["api-all", "updater"] }
|
||||
tauri = { version = "1.0.0-rc.9", features = ["api-all"] }
|
||||
|
||||
# Access system process info.
|
||||
sysinfo = "0.23.12"
|
||||
sysinfo = "0.24.6"
|
||||
|
||||
# ZIP-archive library.
|
||||
zip-extract = "0.1.1"
|
||||
zip = "0.6.2"
|
||||
|
||||
# For creating a "global" downloads list.
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
# Access to the Windows Registry.
|
||||
registry = "1.2.1"
|
||||
once_cell = "1.13.0"
|
||||
|
||||
# Program opener.
|
||||
open = "2.1.2"
|
||||
open = "3.0.2"
|
||||
duct = "0.13.5"
|
||||
|
||||
# Serialization.
|
||||
serde_json = "1"
|
||||
|
||||
# System process elevation.
|
||||
is_elevated = "0.1.2"
|
||||
runas = "0.2.1"
|
||||
|
||||
# Dependencies for the HTTP(S) proxy.
|
||||
http = "0.2"
|
||||
hudsucker = "0.17.2"
|
||||
@@ -54,6 +55,13 @@ reqwest = { version = "0.11.3", features = ["stream"] }
|
||||
futures-util = "0.3.14"
|
||||
rcgen = { version = "0.9", features = ["x509-parser"] }
|
||||
|
||||
# metadata stuff
|
||||
libloading = "0.7"
|
||||
regex = "1"
|
||||
|
||||
# other
|
||||
file_diff = "1.0.0"
|
||||
|
||||
[features]
|
||||
# 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
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
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()
|
||||
}
|
||||
|
||||
1
src-tauri/keys/dispatchKey.txt
Normal file
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
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": "文件解压中:"
|
||||
},
|
||||
"options": {
|
||||
"game_exec": "选择游戏可执行文件",
|
||||
"game_executable": "选择游戏可执行文件",
|
||||
"grasscutter_jar": "选择 Grasscutter JAR 文件",
|
||||
"java_path": "设置自定义 Java 路径",
|
||||
"grasscutter_with_game": "随游戏自动启动 Grasscutter",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"options": {
|
||||
"enabled": "已啟用",
|
||||
"disabled": "未啟用",
|
||||
"game_exec": "選擇遊戲執行檔",
|
||||
"game_executable": "選擇遊戲執行檔",
|
||||
"grasscutter_jar": "選擇伺服器JAR檔案",
|
||||
"toggle_encryption": "設定加密",
|
||||
"java_path": "設定自定義Java路徑",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"options": {
|
||||
"enabled": "Aktiviert",
|
||||
"disabled": "Deaktiviert",
|
||||
"game_exec": "Spiel Datei auswählen",
|
||||
"game_executable": "Spiel Datei auswählen",
|
||||
"grasscutter_jar": "Grasscuter JAR auswählen",
|
||||
"toggle_encryption": "Verschlüsselung umschalten",
|
||||
"java_path": "Benutzerdefinierten Java Pfad setzen",
|
||||
|
||||
@@ -13,14 +13,19 @@
|
||||
"options": {
|
||||
"enabled": "Enabled",
|
||||
"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",
|
||||
"toggle_encryption": "Toggle Encryption",
|
||||
"install_certificate": "Install Proxy Certificate",
|
||||
"java_path": "Set Custom Java Path",
|
||||
"grasscutter_with_game": "Automatically launch Grasscutter with game",
|
||||
"language": "Select Language",
|
||||
"background": "Set Custom Background (link or image file)",
|
||||
"theme": "Set Theme"
|
||||
"theme": "Set Theme",
|
||||
"patch_metadata": "Automatically Patch Metadata",
|
||||
"use_proxy": "Use Internal Proxy"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "Download Grasscutter Stable Data",
|
||||
@@ -31,7 +36,8 @@
|
||||
"grasscutter_latest": "Download Grasscutter Latest",
|
||||
"grasscutter_stable_update": "Update Grasscutter Stable",
|
||||
"grasscutter_latest_update": "Update Grasscutter Latest",
|
||||
"resources": "Download Grasscutter Resources"
|
||||
"resources": "Download Grasscutter Resources",
|
||||
"game": "Download Game"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Downloading",
|
||||
@@ -43,7 +49,8 @@
|
||||
"components": {
|
||||
"select_file": "Select file or folder...",
|
||||
"select_folder": "Select folder...",
|
||||
"download": "Download"
|
||||
"download": "Download",
|
||||
"install": "Install"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Recent Commits",
|
||||
@@ -57,5 +64,9 @@
|
||||
"gc_stable_data": "Download the current stable Grasscutter data files, which does not come with a jar file. This is useful for updating.",
|
||||
"gc_dev_data": "Download the latest development Grasscutter data files, which does not come with a jar file. This is useful for updating.",
|
||||
"resources": "These are also required to run a Grasscutter server. This button will be grey if you have an existing resources folder with contents inside"
|
||||
},
|
||||
"swag": {
|
||||
"akebi": "Set Akebi Executable",
|
||||
"migoto": "Set 3dMigoto Executable"
|
||||
}
|
||||
}
|
||||
69
src-tauri/lang/es.json
Normal file
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": {
|
||||
"enabled": "active",
|
||||
"disabled": "desactiver",
|
||||
"game_exec": "definir l'executable du jeu",
|
||||
"game_executable": "definir l'executable du jeu",
|
||||
"grasscutter_jar": "definir le Jar Grasscutter",
|
||||
"toggle_encryption": "activer l'encryption",
|
||||
"java_path": "definir un chemin java personnalise",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"files_extracting": "MengExtract File: "
|
||||
},
|
||||
"options": {
|
||||
"game_exec": "Set Game Executable",
|
||||
"game_executable": "Set Game Executable",
|
||||
"grasscutter_jar": "Path ke Grasscutter JAR",
|
||||
"java_path": "Atur kustom Java path",
|
||||
"grasscutter_with_game": "Otomatis Menjalankan Grasscutter Dengan Game",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"options": {
|
||||
"enabled": "Iespē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",
|
||||
"toggle_encryption": "Pārslēgt Šifrēšanu",
|
||||
"java_path": "Iestatiet pielāgotu Java ceļu",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"options": {
|
||||
"enabled": "Включено",
|
||||
"disabled": "Выключено",
|
||||
"game_exec": "Установить исполняемый файл игры",
|
||||
"game_executable": "Установить исполняемый файл игры",
|
||||
"grasscutter_jar": "Установить Grasscutter JAR",
|
||||
"toggle_encryption": "Переключить шифрование",
|
||||
"java_path": "Установить пользовательский путь Java",
|
||||
|
||||
61
src-tauri/lang/vi.json
Normal file
61
src-tauri/lang/vi.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"lang_name": "Tiếng Việt",
|
||||
"main": {
|
||||
"title": "Cultivation",
|
||||
"launch_button": "Khởi Chạy",
|
||||
"gc_enable": "Kết nối đến Grasscutter",
|
||||
"https_enable": "Sử dụng HTTPS",
|
||||
"ip_placeholder": "Địa chỉ máy chủ...",
|
||||
"port_placeholder": "Cổng...",
|
||||
"files_downloading": "Đang tải file: ",
|
||||
"files_extracting": "Đang giải nén tệp tin: "
|
||||
},
|
||||
"options": {
|
||||
"enabled": "Bật",
|
||||
"disabled": "Tắt",
|
||||
"game_exec": "Đường dẫn đến GenshinImpact.exe",
|
||||
"grasscutter_jar": "Đường dẫn đến Grasscutter.jar",
|
||||
"toggle_encryption": "Bật/Tắt mã hoá",
|
||||
"java_path": "Đường dẫn Java tuỳ chỉnh",
|
||||
"grasscutter_with_game": "Tự động khởi chạy Grasscutter cùng game",
|
||||
"language": "Chọn ngôn ngữ",
|
||||
"background": "Ảnh nền tuỳ chỉnh (đường dẫn hoặc tệp tin ảnh)",
|
||||
"theme": "Chọn giao diện"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "Tải xuống dữ liệu Grasscutter bản ổn định",
|
||||
"grasscutter_latest_data": "Tải xuống dữ liệu Grasscutter bản mới nhất",
|
||||
"grasscutter_stable_data_update": "Cập nhật dữ liệu Grasscutter bản ổn định",
|
||||
"grasscutter_latest_data_update": "Cập nhật dữ liệu Grasscutter bản mới nhất",
|
||||
"grasscutter_stable": "Tải xuống Grasscutter phiên bản ổn định",
|
||||
"grasscutter_latest": "Tải xuống Grasscutter phiển bản mới nhất",
|
||||
"grasscutter_stable_update": "Cập nhật Grasscutter ổn định",
|
||||
"grasscutter_latest_update": "Cập nhật Grasscutter mới nhất",
|
||||
"resources": "Tải xuống tài nguyên cho Grasscutter"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Đang tải",
|
||||
"extracting": "Đang giải nén",
|
||||
"error": "Lỗi",
|
||||
"finished": "Đã xong",
|
||||
"stopped": "Đã dừng"
|
||||
},
|
||||
"components": {
|
||||
"select_file": "Chọn tệp tin hoặc thư mục...",
|
||||
"select_folder": "Chọn thư mục...",
|
||||
"download": "Tải xuống"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Cập nhật gần đây",
|
||||
"latest_version": "Phiên bản mới nhất"
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "Đảm bảo đây là cổng của server Dispatch, không phải cổng của server Game. Thường là '443'.",
|
||||
"game_help_text": "Bạn không cần phải sử dụng một bản sao riêng để chơi với Grasscutter. Việc này chỉ xảy ra nếu bạn hạ phiên bản xuống 2.6 hoặc chưa cài đặt trò chơi.",
|
||||
"gc_stable_jar": "Tải xuống phiên bản ổn định của Grasscutter, bảo gồm file jar và các file dữ liệu.",
|
||||
"gc_dev_jar": "Tải xuống phiên bản phát triển mới nhất của Grasscutter, bảo gồm file jar và các file dữ liệu.",
|
||||
"gc_stable_data": "Tải xuống bản ổn định các tệp dữ liệu của Grasscutter, không bao gồm file jar. Phù hợp khi cập nhật.",
|
||||
"gc_dev_data": "Tải xuống bản phát triển mới nhất các tệp dữ liệu của Grasscutter, không bao gồm file jar. Phù hợp khi cập nhật.",
|
||||
"resources": "Chúng được yêu cầu để chạy máy chủ Grasscutter. Nút này sẽ có màu xám nếu bạn có một thư mục tài nguyên có nội dung bên trong"
|
||||
}
|
||||
}
|
||||
BIN
src-tauri/mhycrypto.dll
Normal file
BIN
src-tauri/mhycrypto.dll
Normal file
Binary file not shown.
387
src-tauri/mhycrypto/aes.c
Normal file
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
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
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
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
|
||||
128
src-tauri/mhycrypto/metadata.cpp
Normal file
128
src-tauri/mhycrypto/metadata.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
extern "C" void decrypt_global_metadata(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" void encrypt_global_metadata(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src-tauri/mhycrypto/metadata.h
Normal file
10
src-tauri/mhycrypto/metadata.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef METADATA_H
|
||||
#define METADATA_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
extern "C" void decrypt_global_metadata(uint8_t *data, size_t size);
|
||||
extern "C" void encrypt_global_metadata(uint8_t *data, size_t size);
|
||||
|
||||
#endif //METADATA_H
|
||||
121
src-tauri/mhycrypto/metadatastringdec.cpp
Normal file
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
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
|
||||
3
src-tauri/rustfmt.toml
Normal file
3
src-tauri/rustfmt.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
tab_spaces = 2
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
||||
@@ -1,36 +1,28 @@
|
||||
use lazy_static::lazy_static;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::cmp::min;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use futures_util::StreamExt;
|
||||
|
||||
// This will create a downloads list that will be used to check if we should continue downloading the file
|
||||
lazy_static! {
|
||||
static ref DOWNLOADS: Mutex<Vec<String>> = {
|
||||
let m = Vec::new();
|
||||
Mutex::new(m)
|
||||
};
|
||||
}
|
||||
static DOWNLOADS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
|
||||
|
||||
// Lots of help from: https://gist.github.com/giuliano-oliveira/4d11d6b3bb003dba3a1b53f43d81b30d
|
||||
// and docs ofc
|
||||
#[tauri::command]
|
||||
pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Result<(), String> {
|
||||
// Reqwest setup
|
||||
let res = match reqwest::get(url)
|
||||
.await {
|
||||
let res = match reqwest::get(url).await {
|
||||
Ok(r) => r,
|
||||
Err(_e) => {
|
||||
emit_download_err(window, format!("Failed to request {}", url), path);
|
||||
return Err(format!("Failed to request {}", url));
|
||||
}
|
||||
};
|
||||
let total_size = res
|
||||
.content_length()
|
||||
.unwrap_or(0);
|
||||
let total_size = res.content_length().unwrap_or(0);
|
||||
|
||||
// Create file path
|
||||
let mut file = match File::create(path) {
|
||||
@@ -59,17 +51,17 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu
|
||||
let chunk = match item {
|
||||
Ok(itm) => itm,
|
||||
Err(e) => {
|
||||
emit_download_err(window, format!("Error while downloading file"), path);
|
||||
emit_download_err(window, "Error while downloading file".to_string(), path);
|
||||
return Err(format!("Error while downloading file: {}", e));
|
||||
}
|
||||
};
|
||||
let vect = &chunk.to_vec()[..];
|
||||
|
||||
// Write bytes
|
||||
match file.write_all(&vect) {
|
||||
match file.write_all(vect) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
emit_download_err(window, format!("Error while writing file"), path);
|
||||
emit_download_err(window, "Error while writing file".to_string(), path);
|
||||
return Err(format!("Error while writing file: {}", e));
|
||||
}
|
||||
}
|
||||
@@ -78,29 +70,14 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu
|
||||
let new = min(downloaded + (chunk.len() as u64), total_size);
|
||||
downloaded = new;
|
||||
|
||||
total_downloaded = total_downloaded + chunk.len() as u64;
|
||||
total_downloaded += chunk.len() as u64;
|
||||
|
||||
let mut res_hash = std::collections::HashMap::new();
|
||||
|
||||
res_hash.insert(
|
||||
"downloaded".to_string(),
|
||||
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(),
|
||||
);
|
||||
res_hash.insert("downloaded".to_string(), 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
|
||||
window.emit("download_progress", &res_hash).unwrap();
|
||||
@@ -110,21 +87,14 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu
|
||||
window.emit("download_finished", &path).unwrap();
|
||||
|
||||
// We are done
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn emit_download_err(window: tauri::Window, msg: std::string::String, path: &str) {
|
||||
pub fn emit_download_err(window: tauri::Window, msg: String, path: &str) {
|
||||
let mut res_hash = std::collections::HashMap::new();
|
||||
|
||||
res_hash.insert(
|
||||
"error".to_string(),
|
||||
msg.to_string(),
|
||||
);
|
||||
|
||||
res_hash.insert(
|
||||
"path".to_string(),
|
||||
path.to_string(),
|
||||
);
|
||||
res_hash.insert("error".to_string(), msg);
|
||||
res_hash.insert("path".to_string(), path.to_string());
|
||||
|
||||
window.emit("download_error", &res_hash).unwrap();
|
||||
}
|
||||
@@ -144,4 +114,4 @@ pub fn stop_download(path: String) {
|
||||
if let Err(_e) = std::fs::remove_file(&path) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,156 @@
|
||||
use file_diff::diff;
|
||||
use std::fs;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn rename(path: String, new_name: String) {
|
||||
let mut new_path = path.clone();
|
||||
|
||||
// Check if file/folder to replace exists
|
||||
if !fs::metadata(&path).is_ok() {
|
||||
if fs::metadata(&path).is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if path uses forward or back slashes
|
||||
if new_path.contains("\\") {
|
||||
new_path = path.replace("\\", "/");
|
||||
if new_path.contains('\\') {
|
||||
new_path = path.replace('\\', "/");
|
||||
}
|
||||
|
||||
let path_replaced = &path.replace(&new_path.split("/").last().unwrap(), &new_name);
|
||||
let path_replaced = &path.replace(&new_path.split('/').last().unwrap(), &new_name);
|
||||
|
||||
fs::rename(path, &path_replaced).unwrap();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn dir_create(path: String) {
|
||||
fs::create_dir_all(path).unwrap();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn dir_exists(path: &str) -> bool {
|
||||
return fs::metadata(&path).is_ok();
|
||||
let path_buf = std::path::PathBuf::from(path);
|
||||
fs::metadata(path_buf).is_ok()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn dir_is_empty(path: &str) -> bool {
|
||||
return 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]
|
||||
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]
|
||||
pub fn copy_file(path: String, new_path: String) -> bool {
|
||||
let filename = &path.split('/').last().unwrap();
|
||||
let mut new_path_buf = std::path::PathBuf::from(&new_path);
|
||||
let path_buf = 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()) {
|
||||
std::fs::create_dir_all(&new_path).unwrap();
|
||||
}
|
||||
|
||||
// Copy old to new
|
||||
match std::fs::copy(&path_buf, format!("{}/{}", new_path, filename)) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
println!("Failed to copy file: {}", e);
|
||||
println!("Path: {}", path);
|
||||
println!("New Path: {}", new_path);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use crate::system_helpers::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_lang(window: tauri::Window, lang: String) -> String {
|
||||
let lang = lang.to_lowercase();
|
||||
|
||||
// Send contents of language file back
|
||||
let lang_path: PathBuf = [&install_location(), "lang", &format!("{}.json", lang)].iter().collect();
|
||||
let contents = match std::fs::read_to_string(&lang_path) {
|
||||
let lang_path: PathBuf = [&install_location(), "lang", &format!("{}.json", lang)]
|
||||
.iter()
|
||||
.collect();
|
||||
match std::fs::read_to_string(&lang_path) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
emit_lang_err(window, format!("Failed to read language file: {}", e));
|
||||
return "".to_string();
|
||||
"".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -23,9 +23,9 @@ pub async fn get_languages() -> std::collections::HashMap<String, String> {
|
||||
// for each lang file, set the key as the filename and the value as the lang_name contained in the file
|
||||
let mut languages = std::collections::HashMap::new();
|
||||
|
||||
let mut lang_files = std::fs::read_dir(Path::new(&install_location()).join("lang")).unwrap();
|
||||
let lang_files = std::fs::read_dir(Path::new(&install_location()).join("lang")).unwrap();
|
||||
|
||||
while let Some(entry) = lang_files.next() {
|
||||
for entry in lang_files {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path();
|
||||
let filename = path.file_name().unwrap().to_str().unwrap();
|
||||
@@ -41,16 +41,13 @@ pub async fn get_languages() -> std::collections::HashMap<String, String> {
|
||||
languages.insert(filename.to_string(), content);
|
||||
}
|
||||
|
||||
return languages;
|
||||
languages
|
||||
}
|
||||
|
||||
pub fn emit_lang_err(window: tauri::Window, msg: std::string::String) {
|
||||
pub fn emit_lang_err(window: tauri::Window, msg: String) {
|
||||
let mut res_hash = std::collections::HashMap::new();
|
||||
|
||||
res_hash.insert(
|
||||
"error".to_string(),
|
||||
msg.to_string(),
|
||||
);
|
||||
res_hash.insert("error".to_string(), msg);
|
||||
|
||||
window.emit("lang_error", &res_hash).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,28 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use std::{sync::Mutex, collections::HashMap};
|
||||
use std::path::PathBuf;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
|
||||
use std::thread;
|
||||
use structs::APIQuery;
|
||||
use sysinfo::{System, SystemExt};
|
||||
use structs::{APIQuery};
|
||||
|
||||
mod downloader;
|
||||
mod file_helpers;
|
||||
mod lang;
|
||||
mod metadata_patcher;
|
||||
mod proxy;
|
||||
mod structs;
|
||||
mod system_helpers;
|
||||
mod file_helpers;
|
||||
mod unzip;
|
||||
mod downloader;
|
||||
mod lang;
|
||||
mod proxy;
|
||||
mod web;
|
||||
|
||||
lazy_static! {
|
||||
static ref WATCH_GAME_PROCESS: Mutex<String> = {
|
||||
let m = "".to_string();
|
||||
Mutex::new(m)
|
||||
};
|
||||
}
|
||||
static WATCH_GAME_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
|
||||
|
||||
fn main() {
|
||||
// Start the game process watcher.
|
||||
process_watcher();
|
||||
|
||||
// Make BG folder if it doesn't exist.
|
||||
let bg_folder: PathBuf = [&system_helpers::install_location(), "bg"].iter().collect();
|
||||
std::fs::create_dir_all(&bg_folder).unwrap();
|
||||
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
enable_process_watcher,
|
||||
@@ -48,73 +36,81 @@ fn main() {
|
||||
system_helpers::run_program,
|
||||
system_helpers::run_jar,
|
||||
system_helpers::open_in_browser,
|
||||
system_helpers::copy_file,
|
||||
system_helpers::install_location,
|
||||
system_helpers::is_elevated,
|
||||
proxy::set_proxy_addr,
|
||||
proxy::generate_ca_files,
|
||||
unzip::unzip,
|
||||
file_helpers::rename,
|
||||
file_helpers::dir_create,
|
||||
file_helpers::dir_exists,
|
||||
file_helpers::dir_is_empty,
|
||||
file_helpers::dir_delete,
|
||||
file_helpers::copy_file,
|
||||
file_helpers::copy_file_with_new_name,
|
||||
file_helpers::delete_file,
|
||||
file_helpers::are_files_identical,
|
||||
file_helpers::read_file,
|
||||
file_helpers::write_file,
|
||||
downloader::download_file,
|
||||
downloader::stop_download,
|
||||
lang::get_lang,
|
||||
lang::get_languages,
|
||||
web::valid_url
|
||||
web::valid_url,
|
||||
web::web_get,
|
||||
metadata_patcher::patch_metadata
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
fn process_watcher() {
|
||||
// Every 5 seconds, see if the game process is still running.
|
||||
// If it is not, then we assume the game has closed and disable the proxy
|
||||
// to prevent any requests from being sent to the game.
|
||||
|
||||
// Start a thread so as to not block the main thread.
|
||||
thread::spawn(|| {
|
||||
let mut system = System::new_all();
|
||||
|
||||
loop {
|
||||
// Refresh system info
|
||||
system.refresh_all();
|
||||
|
||||
// Grab the game process name
|
||||
let proc = WATCH_GAME_PROCESS.lock().unwrap().to_string();
|
||||
|
||||
if !&proc.is_empty() {
|
||||
let proc_with_name = system.processes_by_exact_name(&proc);
|
||||
let mut exists = false;
|
||||
|
||||
for _p in proc_with_name {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the game process closes, disable the proxy.
|
||||
if !exists {
|
||||
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
||||
return !proc.is_empty();
|
||||
!proc.is_empty()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn enable_process_watcher(process: String) {
|
||||
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();
|
||||
|
||||
loop {
|
||||
thread::sleep(std::time::Duration::from_secs(5));
|
||||
|
||||
// Refresh system info
|
||||
system.refresh_all();
|
||||
|
||||
// Grab the game process name
|
||||
let proc = WATCH_GAME_PROCESS.lock().unwrap().to_string();
|
||||
|
||||
if !proc.is_empty() {
|
||||
let mut proc_with_name = system.processes_by_exact_name(&proc);
|
||||
let exists = proc_with_name.next().is_some();
|
||||
|
||||
// If the game process closes, disable the proxy.
|
||||
if !exists {
|
||||
println!("Game closed");
|
||||
|
||||
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
|
||||
disconnect();
|
||||
|
||||
window.emit("game_closed", &()).unwrap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -140,11 +136,8 @@ fn disconnect() {
|
||||
|
||||
#[tauri::command]
|
||||
async fn req_get(url: String) -> String {
|
||||
// Send a GET request to the specified URL.
|
||||
let response = web::query(&url.to_string()).await;
|
||||
|
||||
// Send the response body back to the client.
|
||||
return response;
|
||||
// Send a GET request to the specified URL and send the response body back to the client.
|
||||
web::query(&url.to_string()).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -173,14 +166,14 @@ async fn get_theme_list(data_dir: String) -> Vec<HashMap<String, String>> {
|
||||
|
||||
map.insert("json".to_string(), theme_json);
|
||||
map.insert("path".to_string(), path.to_str().unwrap().to_string());
|
||||
|
||||
|
||||
// Push key-value pair containing "json" and "path"
|
||||
themes.push(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return themes;
|
||||
themes
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -204,7 +197,7 @@ async fn get_bg_file(bg_path: String, appdata: String) -> String {
|
||||
}
|
||||
|
||||
// Now we check if the bg folder, which is one directory above the game_path, exists.
|
||||
let bg_img_path = format!("{}\\{}", bg_path.clone().to_string(), file_name.as_str());
|
||||
let bg_img_path = format!("{}\\{}", &bg_path, &file_name);
|
||||
|
||||
// If it doesn't, then we do not have backgrounds to grab.
|
||||
if !file_helpers::dir_exists(&bg_path) {
|
||||
@@ -220,15 +213,15 @@ async fn get_bg_file(bg_path: String, appdata: String) -> String {
|
||||
// The image exists, lets copy it to our local '\bg' folder.
|
||||
let bg_img_path_local = format!("{}\\bg\\{}", copy_loc, file_name.as_str());
|
||||
|
||||
return match std::fs::copy(bg_img_path, bg_img_path_local) {
|
||||
match std::fs::copy(bg_img_path, bg_img_path_local) {
|
||||
Ok(_) => {
|
||||
// Copy was successful, lets return true.
|
||||
format!("{}\\{}", copy_loc, response_data.bg_file.as_str())
|
||||
format!("{}\\{}", copy_loc, response_data.bg_file)
|
||||
}
|
||||
Err(e) => {
|
||||
// Copy failed, lets return false
|
||||
println!("Failed to copy background image: {}", e);
|
||||
"".to_string()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
168
src-tauri/src/metadata_patcher.rs
Normal file
168
src-tauri/src/metadata_patcher.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
use regex::Regex;
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
|
||||
extern "C" {
|
||||
fn decrypt_global_metadata(data: *mut u8, size: u64);
|
||||
fn encrypt_global_metadata(data: *mut u8, size: u64);
|
||||
}
|
||||
|
||||
fn dll_decrypt_global_metadata(
|
||||
data: *mut u8,
|
||||
size: u64,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
unsafe {
|
||||
decrypt_global_metadata(data, size);
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn dll_encrypt_global_metadata(
|
||||
data: *mut u8,
|
||||
size: u64,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
unsafe {
|
||||
encrypt_global_metadata(data, size);
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn patch_metadata(metadata_folder: &str) -> bool {
|
||||
let metadata_file = &(metadata_folder.to_owned() + "\\global-metadata-unpatched.dat");
|
||||
println!("Patching metadata file: {}", metadata_file);
|
||||
let decrypted = decrypt_metadata(metadata_file);
|
||||
if do_vecs_match(&decrypted, &Vec::new()) {
|
||||
println!("Failed to decrypt metadata file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
let modified = replace_keys(&decrypted);
|
||||
if do_vecs_match(&modified, &Vec::new()) {
|
||||
println!("Failed to replace keys in metadata file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
let encrypted = encrypt_metadata(&modified);
|
||||
if do_vecs_match(&encrypted, &Vec::new()) {
|
||||
println!("Failed to re-encrypt metadata file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
//write encrypted to file
|
||||
let mut file = match OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(&(metadata_folder.to_owned() + "\\global-metadata-patched.dat"))
|
||||
{
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
println!("Failed to open global-metadata: {}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
file.write_all(&encrypted).unwrap();
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn decrypt_metadata(file_path: &str) -> Vec<u8> {
|
||||
let mut file = match File::open(file_path) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
println!("Failed to open global-metadata: {}", e);
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let mut data = Vec::new();
|
||||
|
||||
// Read metadata file
|
||||
match file.read_to_end(&mut data) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!("Failed to read global-metadata: {}", e);
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt metadata file
|
||||
match dll_decrypt_global_metadata(data.as_mut_ptr(), data.len().try_into().unwrap()) {
|
||||
Ok(_) => {
|
||||
println!("Successfully decrypted global-metadata");
|
||||
data
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to decrypt global-metadata: {}", e);
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_keys(data: &[u8]) -> Vec<u8> {
|
||||
let mut new_data = String::new();
|
||||
|
||||
unsafe {
|
||||
let data_str = String::from_utf8_unchecked(data.to_vec());
|
||||
|
||||
let re = Regex::new(r"<RSAKeyValue>((.|\n|\r)*?)</RSAKeyValue>").unwrap();
|
||||
let matches = re.find_iter(&data_str);
|
||||
|
||||
// dispatch key is index 3
|
||||
// password key is index 2
|
||||
|
||||
for (i, rmatch) in matches.enumerate() {
|
||||
let key = rmatch.as_str();
|
||||
|
||||
if i == 2 {
|
||||
println!("Replacing password key");
|
||||
new_data = replace_rsa_key(&data_str, key, "passwordKey.txt");
|
||||
} else if i == 3 {
|
||||
println!("Replacing dispatch key");
|
||||
new_data = replace_rsa_key(&new_data, key, "dispatchKey.txt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new_data.as_bytes().to_vec();
|
||||
}
|
||||
|
||||
fn replace_rsa_key(old_data: &str, to_replace: &str, file_name: &str) -> String {
|
||||
// Read dispatch key file
|
||||
unsafe {
|
||||
let mut new_key_file = match File::open(&("keys/".to_owned() + file_name)) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
println!("Failed to open keys/{}: {}", file_name, e);
|
||||
return String::new();
|
||||
}
|
||||
};
|
||||
let mut key_data = Vec::new();
|
||||
new_key_file.read_to_end(&mut key_data).unwrap();
|
||||
let new_key = String::from_utf8_unchecked(key_data.to_vec());
|
||||
|
||||
// Replace old key with new key
|
||||
old_data.replace(to_replace, &new_key)
|
||||
}
|
||||
}
|
||||
|
||||
fn encrypt_metadata(old_data: &[u8]) -> Vec<u8> {
|
||||
let mut data = old_data.to_vec();
|
||||
match dll_encrypt_global_metadata(data.as_mut_ptr(), data.len().try_into().unwrap()) {
|
||||
Ok(_) => {
|
||||
println!("Successfully encrypted global-metadata");
|
||||
data
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to encrypt global-metadata: {}", e);
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_vecs_match<T: PartialEq>(a: &Vec<T>, b: &Vec<T>) -> bool {
|
||||
let matching = a.iter().zip(b.iter()).filter(|&(a, b)| a == b).count();
|
||||
matching == a.len() && matching == b.len()
|
||||
}
|
||||
@@ -3,38 +3,35 @@
|
||||
* https://github.com/omjadas/hudsucker/blob/main/examples/log.rs
|
||||
*/
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use std::{sync::Mutex, str::FromStr};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{str::FromStr, sync::Mutex};
|
||||
|
||||
use rcgen::*;
|
||||
use hudsucker::{
|
||||
async_trait::async_trait,
|
||||
certificate_authority::RcgenAuthority,
|
||||
hyper::{Body, Request, Response},
|
||||
*,
|
||||
};
|
||||
use rcgen::*;
|
||||
|
||||
use std::fs;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
use registry::{Hive, Data, Security};
|
||||
|
||||
use rustls_pemfile as pemfile;
|
||||
use tauri::http::Uri;
|
||||
use crate::system_helpers::run_command;
|
||||
|
||||
#[cfg(windows)]
|
||||
use registry::{Data, Hive, Security};
|
||||
|
||||
async fn shutdown_signal() {
|
||||
tokio::signal::ctrl_c().await
|
||||
tokio::signal::ctrl_c()
|
||||
.await
|
||||
.expect("Failed to install CTRL+C signal handler");
|
||||
}
|
||||
|
||||
// Global ver for getting server address.
|
||||
lazy_static! {
|
||||
static ref SERVER: Mutex<String> = {
|
||||
let m = "http://localhost:443".to_string();
|
||||
Mutex::new(m)
|
||||
};
|
||||
}
|
||||
static SERVER: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new("http://localhost:443".to_string()));
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ProxyHandler;
|
||||
@@ -46,16 +43,18 @@ pub fn set_proxy_addr(addr: String) {
|
||||
|
||||
#[async_trait]
|
||||
impl HttpHandler for ProxyHandler {
|
||||
async fn handle_request(&mut self,
|
||||
_context: &HttpContext,
|
||||
mut request: Request<Body>,
|
||||
async fn handle_request(
|
||||
&mut self,
|
||||
_context: &HttpContext,
|
||||
mut request: Request<Body>,
|
||||
) -> RequestOrResponse {
|
||||
let uri = request.uri().to_string();
|
||||
let uri_path = request.uri().path();
|
||||
|
||||
|
||||
if uri.contains("hoyoverse.com") || uri.contains("mihoyo.com") || uri.contains("yuanshen.com") {
|
||||
// Create new URI.
|
||||
let new_uri = Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path).as_str()).unwrap();
|
||||
let new_uri =
|
||||
Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path).as_str()).unwrap();
|
||||
// Set request URI to the new one.
|
||||
*request.uri_mut() = new_uri;
|
||||
}
|
||||
@@ -63,10 +62,13 @@ impl HttpHandler for ProxyHandler {
|
||||
RequestOrResponse::Request(request)
|
||||
}
|
||||
|
||||
async fn handle_response(&mut self,
|
||||
_context: &HttpContext,
|
||||
response: Response<Body>,
|
||||
) -> Response<Body> { response }
|
||||
async fn handle_response(
|
||||
&mut self,
|
||||
_context: &HttpContext,
|
||||
response: Response<Body>,
|
||||
) -> Response<Body> {
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,20 +76,22 @@ impl HttpHandler for ProxyHandler {
|
||||
*/
|
||||
pub async fn create_proxy(proxy_port: u16, certificate_path: String) {
|
||||
// Get the certificate and private key.
|
||||
let mut private_key_bytes: &[u8] = &fs::read(format!("{}\\private.key", certificate_path)).expect("Could not read private key");
|
||||
let mut ca_cert_bytes: &[u8] = &fs::read(format!("{}\\cert.crt", certificate_path)).expect("Could not read certificate");
|
||||
let mut private_key_bytes: &[u8] =
|
||||
&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.
|
||||
let private_key = rustls::PrivateKey(
|
||||
pemfile::pkcs8_private_keys(&mut private_key_bytes)
|
||||
.expect("Failed to parse private key")
|
||||
.remove(0)
|
||||
.remove(0),
|
||||
);
|
||||
|
||||
let ca_cert = rustls::Certificate(
|
||||
pemfile::certs(&mut ca_cert_bytes)
|
||||
.expect("Failed to parse CA certificate")
|
||||
.remove(0)
|
||||
.remove(0),
|
||||
);
|
||||
|
||||
// Create the certificate authority.
|
||||
@@ -109,37 +113,58 @@ pub async fn create_proxy(proxy_port: u16, certificate_path: String) {
|
||||
/**
|
||||
* Connects to the local HTTP(S) proxy server.
|
||||
*/
|
||||
#[cfg(windows)]
|
||||
pub fn connect_to_proxy(proxy_port: u16) {
|
||||
if cfg!(target_os = "windows") {
|
||||
// Create 'ProxyServer' string.
|
||||
let server_string: String = format!("http=127.0.0.1:{};https=127.0.0.1:{}", proxy_port, proxy_port);
|
||||
// Create 'ProxyServer' string.
|
||||
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.
|
||||
let settings = Hive::CurrentUser.open(r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", Security::Write).unwrap();
|
||||
// Fetch the 'Internet Settings' registry key.
|
||||
let settings = Hive::CurrentUser
|
||||
.open(
|
||||
r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
|
||||
Security::Write,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Set registry values.
|
||||
settings.set_value("ProxyServer", &Data::String(server_string.parse().unwrap())).unwrap();
|
||||
settings.set_value("ProxyEnable", &Data::U32(1)).unwrap();
|
||||
}
|
||||
// Set registry values.
|
||||
settings
|
||||
.set_value("ProxyServer", &Data::String(server_string.parse().unwrap()))
|
||||
.unwrap();
|
||||
settings.set_value("ProxyEnable", &Data::U32(1)).unwrap();
|
||||
|
||||
println!("Connected to the proxy.");
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn connect_to_proxy(_proxy_port: u16) {
|
||||
println!("Connecting to the proxy is not implemented on this platform.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the local HTTP(S) proxy server.
|
||||
*/
|
||||
#[cfg(windows)]
|
||||
pub fn disconnect_from_proxy() {
|
||||
if cfg!(target_os = "windows") {
|
||||
// Fetch the 'Internet Settings' registry key.
|
||||
let settings = Hive::CurrentUser.open(r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", Security::Write).unwrap();
|
||||
// Fetch the 'Internet Settings' registry key.
|
||||
let settings = Hive::CurrentUser
|
||||
.open(
|
||||
r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
|
||||
Security::Write,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Set registry values.
|
||||
settings.set_value("ProxyEnable", &Data::U32(0)).unwrap();
|
||||
}
|
||||
// Set registry values.
|
||||
settings.set_value("ProxyEnable", &Data::U32(0)).unwrap();
|
||||
|
||||
println!("Disconnected from proxy.");
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn disconnect_from_proxy() {}
|
||||
|
||||
/*
|
||||
* Generates a private key and certificate used by the certificate authority.
|
||||
* Additionally installs the certificate and private key in the Root CA store.
|
||||
@@ -155,7 +180,7 @@ pub fn generate_ca_files(path: &Path) {
|
||||
details.push(DnType::OrganizationName, "Grasscutters");
|
||||
details.push(DnType::CountryName, "CN");
|
||||
details.push(DnType::LocalityName, "CN");
|
||||
|
||||
|
||||
// Set details in the parameter.
|
||||
params.distinguished_name = details;
|
||||
// Set other properties.
|
||||
@@ -163,9 +188,9 @@ pub fn generate_ca_files(path: &Path) {
|
||||
params.key_usages = vec![
|
||||
KeyUsagePurpose::DigitalSignature,
|
||||
KeyUsagePurpose::KeyCertSign,
|
||||
KeyUsagePurpose::CrlSign
|
||||
KeyUsagePurpose::CrlSign,
|
||||
];
|
||||
|
||||
|
||||
// Create certificate.
|
||||
let cert = Certificate::from_params(params).unwrap();
|
||||
let cert_crt = cert.serialize_pem().unwrap();
|
||||
@@ -174,26 +199,37 @@ pub fn generate_ca_files(path: &Path) {
|
||||
// Make certificate directory.
|
||||
let cert_dir = path.join("ca");
|
||||
match fs::create_dir(&cert_dir) {
|
||||
Ok(_) => {},
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Write the certificate to a file.
|
||||
let cert_path = cert_dir.join("cert.crt");
|
||||
match fs::write(&cert_path, cert_crt) {
|
||||
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.
|
||||
let private_key_path = cert_dir.join("private.key");
|
||||
match fs::write(&private_key_path, private_key) {
|
||||
Ok(_) => println!("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),
|
||||
Ok(_) => println!(
|
||||
"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_ca_files(&cert_path);
|
||||
}
|
||||
@@ -201,12 +237,33 @@ pub fn generate_ca_files(path: &Path) {
|
||||
/*
|
||||
* Attempts to install the certificate authority's certificate into the Root CA store.
|
||||
*/
|
||||
#[cfg(windows)]
|
||||
pub fn install_ca_files(cert_path: &Path) {
|
||||
if cfg!(target_os = "windows") {
|
||||
run_command("certutil", vec!["-user", "-addstore", "Root", cert_path.to_str().unwrap()]);
|
||||
} else {
|
||||
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(
|
||||
"certutil",
|
||||
vec!["-user", "-addstore", "Root", cert_path.to_str().unwrap()],
|
||||
);
|
||||
println!("Installed certificate.");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
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(),
|
||||
],
|
||||
);
|
||||
println!("Installed certificate.");
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows, target_os = "macos")))]
|
||||
pub fn install_ca_files(_cert_path: &Path) {
|
||||
println!("Certificate installation is not supported on this platform.");
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@ use serde::Deserialize;
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct APIQuery {
|
||||
pub bg_file: String,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
|
||||
use std::thread;
|
||||
use tauri;
|
||||
use open;
|
||||
use duct::cmd;
|
||||
|
||||
use crate::file_helpers;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn run_program(path: String) {
|
||||
// Open the program from the specified path.
|
||||
|
||||
// Open in new thread to prevent blocking.
|
||||
thread::spawn(move || {
|
||||
open::that(&path).unwrap();
|
||||
std::thread::spawn(move || {
|
||||
// Without unwrap_or, this can crash when UAC prompt is denied
|
||||
open::that(&path).unwrap_or(());
|
||||
});
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn run_command(program: &str, args: Vec<&str>) {
|
||||
cmd(program, args).run()
|
||||
.expect("Failed to run command");
|
||||
cmd(program, args).run().expect("Failed to run command");
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -31,7 +23,10 @@ pub fn run_jar(path: String, execute_in: String, java_path: String) {
|
||||
};
|
||||
|
||||
// Open the program from the specified path.
|
||||
match open::with(format!("/k cd /D \"{}\" & {}", &execute_in, &command).to_string(), "C:\\Windows\\System32\\cmd.exe") {
|
||||
match open::with(
|
||||
format!("/k cd /D \"{}\" & {}", &execute_in, &command),
|
||||
"C:\\Windows\\System32\\cmd.exe",
|
||||
) {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("Failed to open jar ({} from {}): {}", &path, &execute_in, e),
|
||||
};
|
||||
@@ -46,26 +41,6 @@ pub fn open_in_browser(url: String) {
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn copy_file(path: String, new_path: String) -> bool {
|
||||
let filename = &path.split("/").last().unwrap();
|
||||
let mut new_path_buf = std::path::PathBuf::from(&new_path);
|
||||
|
||||
// If the new path doesn't exist, create it.
|
||||
if !file_helpers::dir_exists(new_path_buf.pop().to_string().as_str()) {
|
||||
std::fs::create_dir_all(&new_path).unwrap();
|
||||
}
|
||||
|
||||
// Copy old to new
|
||||
match std::fs::copy(&path, format!("{}/{}", new_path, filename)) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
println!("Failed to copy file: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn install_location() -> String {
|
||||
let mut exe_path = std::env::current_exe().unwrap();
|
||||
@@ -76,7 +51,14 @@ pub fn install_location() -> String {
|
||||
return exe_path.to_str().unwrap().to_string();
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tauri::command]
|
||||
pub fn is_elevated() -> bool {
|
||||
return is_elevated::is_elevated();
|
||||
}
|
||||
is_elevated::is_elevated()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tauri::command]
|
||||
pub fn is_elevated() -> bool {
|
||||
sudo::check() == sudo::RunningAs::Root
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use zip_extract;
|
||||
use zip;
|
||||
use std::fs::File;
|
||||
use std::path;
|
||||
use std::thread;
|
||||
@@ -25,21 +23,18 @@ pub fn unzip(window: tauri::Window, zipfile: String, destpath: String) {
|
||||
|
||||
match zip_extract::extract(&f, &full_path, true) {
|
||||
Ok(_) => {
|
||||
println!("Extracted zip file to: {}", full_path.to_str().unwrap_or("Error"));
|
||||
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("error".to_string(), e.to_string());
|
||||
|
||||
res_hash.insert(
|
||||
"path".to_string(),
|
||||
zipfile.to_string(),
|
||||
);
|
||||
res_hash.insert("path".to_string(), zipfile.to_string());
|
||||
|
||||
window.emit("download_error", &res_hash).unwrap();
|
||||
}
|
||||
@@ -52,7 +47,9 @@ pub fn unzip(window: tauri::Window, zipfile: String, destpath: String) {
|
||||
|
||||
// If the contents is a jar file, emit that we have extracted a new jar file
|
||||
if name.ends_with(".jar") {
|
||||
window.emit("jar_extracted", destpath.to_string() + name).unwrap();
|
||||
window
|
||||
.emit("jar_extracted", destpath.to_string() + name)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Delete zip file
|
||||
@@ -67,4 +64,4 @@ pub fn unzip(window: tauri::Window, zipfile: String, destpath: String) {
|
||||
|
||||
window.emit("extract_end", &zipfile).unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,13 @@ use reqwest::header::USER_AGENT;
|
||||
pub(crate) async fn query(site: &str) -> String {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let response = client.get(site).header(USER_AGENT, "cultivation").send().await.unwrap();
|
||||
return response.text().await.unwrap();
|
||||
let response = client
|
||||
.get(site)
|
||||
.header(USER_AGENT, "cultivation")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
response.text().await.unwrap()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -12,7 +17,18 @@ pub(crate) async fn valid_url(url: String) -> bool {
|
||||
// Check if we get a 200 response
|
||||
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();
|
||||
|
||||
return 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,7 +7,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "Cultivation",
|
||||
"version": "1.0.1"
|
||||
"version": "1.0.4"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
@@ -55,7 +55,9 @@
|
||||
"signingIdentity": null
|
||||
},
|
||||
"resources": [
|
||||
"lang/*.json"
|
||||
"lang/*.json",
|
||||
"keys/*",
|
||||
"./mhycrypto.dll"
|
||||
],
|
||||
"targets": "all",
|
||||
"windows": {
|
||||
@@ -72,7 +74,7 @@
|
||||
"csp": "default-src 'self' https://asset.localhost; img-src 'self'; img-src https://* asset: https://asset.localhost"
|
||||
},
|
||||
"updater": {
|
||||
"active": true,
|
||||
"active": false,
|
||||
"dialog": true,
|
||||
"endpoints": [
|
||||
"https://api.grasscutter.io/cultivation/updater?version={{current_version}}",
|
||||
|
||||
@@ -17,7 +17,6 @@ let isDebug = false;
|
||||
isDebug = await getConfigOption('debug_enabled')
|
||||
})
|
||||
|
||||
// Render the app.
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
{
|
||||
@@ -26,10 +25,5 @@ root.render(
|
||||
</React.StrictMode>
|
||||
)
|
||||
|
||||
// Enable web vitals if needed.
|
||||
import reportWebVitals from './utils/reportWebVitals'
|
||||
isDebug && reportWebVitals(console.log)
|
||||
|
||||
// Setup DOM message passing.
|
||||
import { parseMessageFromDOM } from './utils/dom'
|
||||
document.addEventListener<string>('domMessage', parseMessageFromDOM)
|
||||
isDebug && reportWebVitals(console.log)
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"name": "Example Theme",
|
||||
"version": "420.69",
|
||||
"description": "Show off some of the abilities of the Cultivation theme system",
|
||||
|
||||
"includes": {
|
||||
"_README": "You can include any amount of CSS and JS files here. Paths are relative to the theme directory.",
|
||||
|
||||
"css": ["/index.css"],
|
||||
"js": ["/index.js"]
|
||||
},
|
||||
|
||||
"settings": [
|
||||
{
|
||||
"label": "Example Setting",
|
||||
"type": "input",
|
||||
"className": "Input",
|
||||
|
||||
"data": {
|
||||
"placeholder": "Enter a value",
|
||||
"initialValue": "Change this value"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Example Setting",
|
||||
"type": "checkbox",
|
||||
"className": "Checkbox"
|
||||
}
|
||||
],
|
||||
|
||||
"_README": "These are optional. Including neither will result in the launcher simply using the default background choice.",
|
||||
"customBackgroundPath": "/background/bg.png",
|
||||
"customBackgroundURL": ""
|
||||
}
|
||||
67
src/resources/icons/akebi.svg
Normal file
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 |
@@ -22,6 +22,7 @@ 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 { unpatchGame } from '../utils/metadata'
|
||||
|
||||
interface IProps {
|
||||
[key: string]: never;
|
||||
@@ -56,10 +57,25 @@ class App extends React.Component<IProps, IState> {
|
||||
console.log(payload)
|
||||
})
|
||||
|
||||
listen('jar_extracted', ({ 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()
|
||||
|
||||
console.log(`unpatched game? ${unpatched}`)
|
||||
|
||||
if (!unpatched) {
|
||||
alert(`Could not unpatch game! (You should be able to find your metadata backup in ${await dataDir()}\\cultivation\\)`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let min = false
|
||||
|
||||
// periodically check if we need to min/max based on whether the game is open
|
||||
@@ -194,6 +210,7 @@ class App extends React.Component<IProps, IState> {
|
||||
// Options menu
|
||||
this.state.optionsOpen ? (
|
||||
<Options
|
||||
downloadManager={downloadHandler}
|
||||
closeFn={() => this.setState({ optionsOpen: !this.state.optionsOpen })}
|
||||
/>
|
||||
) : null
|
||||
|
||||
@@ -40,7 +40,7 @@ function none() {
|
||||
alert('none')
|
||||
}
|
||||
|
||||
class Debug extends React.Component<any, any>{
|
||||
class Debug extends React.Component{
|
||||
render() {
|
||||
return (
|
||||
<div className="App">
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.BottomSection .CheckboxDisplay {
|
||||
.BottomSection .CheckboxDisplay {
|
||||
margin-right: 6px;
|
||||
box-shadow: 0 0 5px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
@@ -62,20 +62,27 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
|
||||
height: 100%;
|
||||
max-height: 60px;
|
||||
}
|
||||
|
||||
#officialPlay {
|
||||
width: 60%
|
||||
.ServerLaunchButtons .BigButton {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#officialPlay {
|
||||
max-width: 60%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#akebiLaunch,
|
||||
#serverLaunch {
|
||||
width: 5%;
|
||||
}
|
||||
|
||||
.AkebiIcon,
|
||||
.ServerIcon {
|
||||
height: 20px;
|
||||
filter: invert(28%) sepia(28%) saturate(1141%) hue-rotate(352deg) brightness(96%) contrast(88%);
|
||||
|
||||
@@ -8,12 +8,12 @@ import { translate } from '../../utils/language'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
import Server from '../../resources/icons/server.svg'
|
||||
import Akebi from '../../resources/icons/akebi.svg'
|
||||
|
||||
import './ServerLaunchSection.css'
|
||||
import {dataDir} from '@tauri-apps/api/path'
|
||||
|
||||
interface IProps {
|
||||
[key: string]: any
|
||||
}
|
||||
import { getGameExecutable } from '../../utils/game'
|
||||
import { patchGame, unpatchGame } from '../../utils/metadata'
|
||||
|
||||
interface IState {
|
||||
grasscutterEnabled: boolean;
|
||||
@@ -29,10 +29,12 @@ interface IState {
|
||||
|
||||
httpsLabel: string;
|
||||
httpsEnabled: boolean;
|
||||
|
||||
swag: boolean;
|
||||
}
|
||||
|
||||
export default class ServerLaunchSection extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
constructor(props: {}) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -45,11 +47,14 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
portPlaceholder: '',
|
||||
portHelpText: '',
|
||||
httpsLabel: '',
|
||||
httpsEnabled: false
|
||||
httpsEnabled: false,
|
||||
swag: false
|
||||
}
|
||||
|
||||
this.toggleGrasscutter = this.toggleGrasscutter.bind(this)
|
||||
this.playGame = this.playGame.bind(this)
|
||||
this.launchAkebi = this.launchAkebi.bind(this)
|
||||
this.launch3dm = this.launch3dm.bind(this)
|
||||
this.setIp = this.setIp.bind(this)
|
||||
this.setPort = this.setPort.bind(this)
|
||||
this.toggleHttps = this.toggleHttps.bind(this)
|
||||
@@ -69,6 +74,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
portHelpText: await translate('help.port_help_text'),
|
||||
httpsLabel: await translate('main.https_enable'),
|
||||
httpsEnabled: config.https_enabled || false,
|
||||
swag: config.swag_mode || false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -85,43 +91,48 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
await saveConfig(config)
|
||||
}
|
||||
|
||||
async playGame() {
|
||||
async playGame(exe?: string, proc_name?: string) {
|
||||
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
|
||||
if (config.toggle_grasscutter) {
|
||||
let game_exe = config.game_install_path
|
||||
if (config.patch_metadata) {
|
||||
const patched = await patchGame()
|
||||
|
||||
if (game_exe.includes('\\')) {
|
||||
game_exe = game_exe.substring(config.game_install_path.lastIndexOf('\\') + 1)
|
||||
} else {
|
||||
game_exe = game_exe.substring(config.game_install_path.lastIndexOf('/') + 1)
|
||||
if (!patched) {
|
||||
alert('Could not patch game!')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const game_exe = await getGameExecutable()
|
||||
|
||||
// Save last connected server and port
|
||||
await setConfigOption('last_ip', this.state.ip)
|
||||
await setConfigOption('last_port', this.state.port)
|
||||
|
||||
// Set IP
|
||||
await invoke('set_proxy_addr', { addr: (this.state.httpsEnabled ? 'https':'http') + '://' + this.state.ip + ':' + this.state.port })
|
||||
await invoke('enable_process_watcher', {
|
||||
process: game_exe
|
||||
process: proc_name || game_exe
|
||||
})
|
||||
|
||||
// Connect to proxy
|
||||
await invoke('connect', { port: 8365, certificatePath: await dataDir() + '\\cultivation\\ca' })
|
||||
if (config.use_internal_proxy) {
|
||||
// Set IP
|
||||
await invoke('set_proxy_addr', { addr: (this.state.httpsEnabled ? 'https':'http') + '://' + this.state.ip + ':' + this.state.port })
|
||||
// Connect to proxy
|
||||
await invoke('connect', { port: 8365, certificatePath: await dataDir() + '\\cultivation\\ca' })
|
||||
}
|
||||
|
||||
// Open server as well if the options are set
|
||||
if (config.grasscutter_with_game) {
|
||||
let jarFolder = config.grasscutter_path
|
||||
const jarFolderArr = config.grasscutter_path.replace(/\\/g, '/').split('/')
|
||||
jarFolderArr.pop()
|
||||
|
||||
if (jarFolder.includes('/')) {
|
||||
jarFolder = jarFolder.substring(0, config.grasscutter_path.lastIndexOf('/'))
|
||||
} else {
|
||||
jarFolder = jarFolder.substring(0, config.grasscutter_path.lastIndexOf('\\'))
|
||||
}
|
||||
const jarFolder = jarFolderArr.join('/')
|
||||
|
||||
await invoke('run_jar', {
|
||||
path: config.grasscutter_path,
|
||||
@@ -129,15 +140,22 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
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
|
||||
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 })
|
||||
else alert('Game not found! At: ' + config.game_install_path)
|
||||
if (gameExists) await invoke('run_program', { path: exe || config.game_install_path })
|
||||
else alert('Game not found! At: ' + (exe || config.game_install_path))
|
||||
}
|
||||
|
||||
async launchServer() {
|
||||
@@ -161,6 +179,28 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
})
|
||||
}
|
||||
|
||||
async launchAkebi() {
|
||||
const config = await getConfig()
|
||||
|
||||
// Get game exe from game path, so we can watch it
|
||||
const pathArr = config.game_install_path.replace(/\\/g, '/').split('/')
|
||||
const gameExec = pathArr[pathArr.length - 1]
|
||||
|
||||
await this.playGame(config.akebi_path, gameExec)
|
||||
}
|
||||
|
||||
async launch3dm() {
|
||||
const config = await getConfig()
|
||||
|
||||
// First launch 3dm
|
||||
invoke('run_program', {
|
||||
path: config.migoto_path
|
||||
})
|
||||
|
||||
// Then play the game as normal
|
||||
await this.playGame()
|
||||
}
|
||||
|
||||
setIp(text: string) {
|
||||
this.setState({
|
||||
ip: text
|
||||
@@ -205,13 +245,25 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
<Checkbox id="httpsEnable" label={this.state.httpsLabel} onChange={this.toggleHttps} checked={this.state.httpsEnabled} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
<div className="ServerLaunchButtons" id="serverLaunchContainer">
|
||||
<BigButton onClick={this.playGame} id="officialPlay">{this.state.buttonLabel}</BigButton>
|
||||
{
|
||||
this.state.swag && (
|
||||
<>
|
||||
<BigButton onClick={this.launchAkebi} id="akebiLaunch">
|
||||
<img className="AkebiIcon" id="akebiIcon" src={Akebi} />
|
||||
</BigButton>
|
||||
<BigButton onClick={this.launch3dm} id="serverLaunch">
|
||||
3DM
|
||||
</BigButton>
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
<BigButton onClick={this.launchServer} id="serverLaunch">
|
||||
<img className="ServerIcon" id="serverLaunchIcon" src={Server} />
|
||||
</BigButton>
|
||||
|
||||
@@ -25,4 +25,30 @@
|
||||
#version {
|
||||
margin: 0px 6px;
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,11 @@ import closeIcon from '../../resources/icons/close.svg'
|
||||
import minIcon from '../../resources/icons/min.svg'
|
||||
import cogBtn from '../../resources/icons/cog.svg'
|
||||
import downBtn from '../../resources/icons/download.svg'
|
||||
import gameBtn from '../../resources/icons/game.svg'
|
||||
|
||||
import Tr from '../../utils/language'
|
||||
|
||||
import './TopBar.css'
|
||||
import { getConfig, setConfigOption } from '../../utils/configuration'
|
||||
|
||||
interface IProps {
|
||||
optFunc: () => void;
|
||||
@@ -19,13 +19,21 @@ interface IProps {
|
||||
|
||||
interface IState {
|
||||
version: string;
|
||||
clicks: number;
|
||||
intv: NodeJS.Timeout | null;
|
||||
}
|
||||
|
||||
export default class TopBar extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
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() {
|
||||
@@ -41,6 +49,39 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
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() {
|
||||
return (
|
||||
<div className="TopBar" id="topBarContainer" data-tauri-drag-region>
|
||||
@@ -50,6 +91,16 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
</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 className="TopBtns" id="topBarButtonContainer">
|
||||
<div id="closeBtn" onClick={this.handleClose} className='TopButton'>
|
||||
<img src={closeIcon} alt="close" />
|
||||
|
||||
@@ -3,7 +3,7 @@ import './BigButton.css'
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode;
|
||||
onClick: () => any;
|
||||
onClick: () => unknown;
|
||||
id: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
@@ -23,7 +23,7 @@ export default class BigButton extends React.Component<IProps, IState> {
|
||||
this.handleClick = this.handleClick.bind(this)
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: IProps, state: IState) {
|
||||
static getDerivedStateFromProps(props: IProps, _state: IState) {
|
||||
return {
|
||||
disabled: props.disabled
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ interface IProps {
|
||||
readonly?: boolean
|
||||
placeholder?: string
|
||||
folder?: boolean
|
||||
customClearBehaviour?: () => void
|
||||
customClearBehaviour?: () => void,
|
||||
openFolder?: string
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -67,10 +68,12 @@ export default class DirInput extends React.Component<IProps, IState> {
|
||||
directory: true
|
||||
})
|
||||
} else {
|
||||
console.log(this.props.openFolder)
|
||||
path = await open({
|
||||
filters: [
|
||||
{ name: 'Files', extensions: this.props.extensions || ['*'] }
|
||||
]
|
||||
],
|
||||
defaultPath: this.props.openFolder
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,7 @@ interface IProps {
|
||||
id?: string;
|
||||
clearable?: boolean;
|
||||
customClearBehaviour?: () => void;
|
||||
style?: {
|
||||
[key: string]: any;
|
||||
}
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import React from 'react'
|
||||
import TextInput from './TextInput'
|
||||
import Checkbox from './Checkbox'
|
||||
|
||||
/*
|
||||
* Valid types for the theme option value.
|
||||
* - input: A text input.
|
||||
* - dropdown: A select/dropdown input.
|
||||
* - checkbox: A toggle.
|
||||
* - button: A button.
|
||||
*/
|
||||
|
||||
interface IProps {
|
||||
type: string;
|
||||
className?: string;
|
||||
jsCallback?: string;
|
||||
data: InputSettings;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
toggled: boolean
|
||||
}
|
||||
|
||||
export interface InputSettings {
|
||||
/* Input. */
|
||||
placeholder?: string;
|
||||
initialValue?: string;
|
||||
|
||||
/* Dropdown. */
|
||||
options?: string[];
|
||||
|
||||
/* Checkbox. */
|
||||
toggled?: boolean
|
||||
id?: string;
|
||||
|
||||
/* Button. */
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export default class ThemeOptionValue extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
toggled: false
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: IProps, state: IState) {
|
||||
return { toggled: props.data.toggled || state.toggled }
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const data = this.props.data
|
||||
|
||||
if(this.props.type == 'checkbox')
|
||||
this.setState({ toggled: data.toggled || false })
|
||||
}
|
||||
|
||||
async onChange() {
|
||||
// Change toggled state if needed.
|
||||
if(this.props.type == 'checkbox')
|
||||
this.setState({
|
||||
toggled: !this.state.toggled
|
||||
})
|
||||
|
||||
if(!this.props.jsCallback)
|
||||
return
|
||||
}
|
||||
|
||||
render() {
|
||||
const data = this.props.data
|
||||
|
||||
switch(this.props.type) {
|
||||
case 'input':
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<TextInput placeholder={data.placeholder} initalValue={data.initialValue} />
|
||||
</div>
|
||||
)
|
||||
case 'dropdown':
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<select>
|
||||
{data.options ? data.options.map((option, index) => {
|
||||
return <option key={index}>{option}</option>
|
||||
}) : null}
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
case 'button':
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<button>{data.text}</button>
|
||||
</div>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<Checkbox checked={this.state?.toggled} onChange={this.onChange} id={this.props.className || 'a'} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { dataDir } from '@tauri-apps/api/path'
|
||||
|
||||
import './Downloads.css'
|
||||
import Divider from './Divider'
|
||||
import { getConfigOption, setConfigOption } from '../../../utils/configuration'
|
||||
import { getConfigOption } from '../../../utils/configuration'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import HelpButton from '../common/HelpButton'
|
||||
@@ -17,7 +17,7 @@ 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 STABLE_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/stable/Grasscutter.zip'
|
||||
const DEV_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/development/Grasscutter.zip'
|
||||
const RESOURCES_DOWNLOAD = 'https://github.com/Koko-boya/Grasscutter_Resources/archive/refs/heads/main.zip'
|
||||
const RESOURCES_DOWNLOAD = 'https://gitlab.com/yukiz/GrasscutterResources/-/archive/2.8/GrasscutterResources-2.8.zip'
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void;
|
||||
@@ -183,7 +183,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
grasscutter_downloading: this.props.downloadManager.downloadingJar(),
|
||||
resources_downloading: this.props.downloadManager.downloadingResources(),
|
||||
repo_downloading: this.props.downloadManager.downloadingRepo(),
|
||||
grasscutter_set: gc_path && gc_path !== '',
|
||||
grasscutter_set: gc_path !== '',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
border-radius: 10px;
|
||||
|
||||
box-shadow: 0px 0px 5px 3px rgba(0, 0, 0, 0.2);
|
||||
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.Menu::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.MenuInner {
|
||||
|
||||
@@ -7,15 +7,17 @@ import Tr, { getLanguages, translate } from '../../../utils/language'
|
||||
import { setConfigOption, getConfig, getConfigOption } from '../../../utils/configuration'
|
||||
import Checkbox from '../common/Checkbox'
|
||||
import Divider from './Divider'
|
||||
import { getTheme, getThemeList, ThemeList } from '../../../utils/themes'
|
||||
import { getThemeList } from '../../../utils/themes'
|
||||
import * as server from '../../../utils/server'
|
||||
|
||||
import './Options.css'
|
||||
import BigButton from '../common/BigButton'
|
||||
import ThemeOptionValue from '../common/ThemeOptionValue'
|
||||
import DownloadHandler from '../../../utils/download'
|
||||
import * as meta from '../../../utils/metadata'
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void;
|
||||
downloadManager: DownloadHandler;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -29,8 +31,13 @@ interface IState {
|
||||
themes: string[]
|
||||
theme: string
|
||||
encryption: boolean
|
||||
|
||||
theme_object: ThemeList|null;
|
||||
patch_metadata: boolean
|
||||
use_internal_proxy: boolean
|
||||
swag: boolean
|
||||
|
||||
// Swag stuff
|
||||
akebi_path: string
|
||||
migoto_path: string
|
||||
}
|
||||
|
||||
export default class Options extends React.Component<IProps, IState> {
|
||||
@@ -48,22 +55,32 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
themes: ['default'],
|
||||
theme: '',
|
||||
encryption: false,
|
||||
|
||||
theme_object: null
|
||||
patch_metadata: false,
|
||||
use_internal_proxy: false,
|
||||
swag: false,
|
||||
|
||||
// Swag stuff
|
||||
akebi_path: '',
|
||||
migoto_path: ''
|
||||
}
|
||||
|
||||
this.setGameExec = this.setGameExec.bind(this)
|
||||
this.setGameExecutable = this.setGameExecutable.bind(this)
|
||||
this.setGrasscutterJar = this.setGrasscutterJar.bind(this)
|
||||
this.setJavaPath = this.setJavaPath.bind(this)
|
||||
this.setAkebi = this.setAkebi.bind(this)
|
||||
this.setMigoto = this.setMigoto.bind(this)
|
||||
this.toggleGrasscutterWithGame = this.toggleGrasscutterWithGame.bind(this)
|
||||
this.setCustomBackground = this.setCustomBackground.bind(this)
|
||||
this.toggleEncryption = this.toggleEncryption.bind(this)
|
||||
this.restoreMetadata = this.restoreMetadata.bind(this)
|
||||
this.toggleMetadata = this.toggleMetadata.bind(this)
|
||||
this.toggleProxy = this.toggleProxy.bind(this)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const config = await getConfig()
|
||||
const languages = await getLanguages()
|
||||
|
||||
|
||||
// Remove jar from path
|
||||
const path = config.grasscutter_path.replace(/\\/g, '/')
|
||||
const folderPath = path.substring(0, path.lastIndexOf('/'))
|
||||
@@ -77,21 +94,25 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
language_options: languages,
|
||||
current_language: config.language || 'en',
|
||||
bg_url_or_path: config.customBackground || '',
|
||||
themes: (await getThemeList()).map(t => t.name),
|
||||
themes: (await getThemeList()).map((t) => t.name),
|
||||
theme: config.theme || 'default',
|
||||
encryption: await translate(encEnabled ? 'options.enabled' : 'options.disabled'),
|
||||
|
||||
theme_object: (await getTheme(config.theme))
|
||||
patch_metadata: config.patch_metadata || false,
|
||||
use_internal_proxy: config.use_internal_proxy || false,
|
||||
swag: config.swag_mode || false,
|
||||
|
||||
// Swag stuff
|
||||
akebi_path: config.akebi_path || '',
|
||||
})
|
||||
|
||||
this.forceUpdate()
|
||||
}
|
||||
|
||||
setGameExec(value: string) {
|
||||
setGameExecutable(value: string) {
|
||||
setConfigOption('game_install_path', value)
|
||||
|
||||
this.setState({
|
||||
game_install_path: value
|
||||
game_install_path: value,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,7 +120,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
setConfigOption('grasscutter_path', value)
|
||||
|
||||
this.setState({
|
||||
grasscutter_path: value
|
||||
grasscutter_path: value,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -107,7 +128,23 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
setConfigOption('java_path', value)
|
||||
|
||||
this.setState({
|
||||
java_path: value
|
||||
java_path: value,
|
||||
})
|
||||
}
|
||||
|
||||
setAkebi(value: string) {
|
||||
setConfigOption('akebi_path', value)
|
||||
|
||||
this.setState({
|
||||
akebi_path: value
|
||||
})
|
||||
}
|
||||
|
||||
setMigoto(value: string) {
|
||||
setConfigOption('migoto_path', value)
|
||||
|
||||
this.setState({
|
||||
migoto_path: value
|
||||
})
|
||||
}
|
||||
|
||||
@@ -126,27 +163,27 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
setConfigOption('grasscutter_with_game', changedVal)
|
||||
|
||||
this.setState({
|
||||
grasscutter_with_game: changedVal
|
||||
grasscutter_with_game: changedVal,
|
||||
})
|
||||
}
|
||||
|
||||
async setCustomBackground(value: string) {
|
||||
const isUrl = /^http(s)?:\/\//gm.test(value)
|
||||
const isUrl = /^(?:http(s)?:\/\/)/gm.test(value)
|
||||
|
||||
if (!value) return await setConfigOption('customBackground', '')
|
||||
|
||||
if (!isUrl) {
|
||||
const filename = value.replace(/\\/g, '/').split('/').pop()
|
||||
const localBgPath = (await dataDir() as string).replace(/\\/g, '/')
|
||||
|
||||
const localBgPath = ((await dataDir()) as string).replace(/\\/g, '/')
|
||||
|
||||
await setConfigOption('customBackground', `${localBgPath}/cultivation/bg/${filename}`)
|
||||
|
||||
|
||||
// Copy the file over to the local directory
|
||||
await invoke('copy_file', {
|
||||
path: value.replace(/\\/g, '/'),
|
||||
newPath: `${localBgPath}cultivation/bg/`
|
||||
newPath: `${localBgPath}cultivation/bg/`,
|
||||
})
|
||||
|
||||
|
||||
window.location.reload()
|
||||
} else {
|
||||
await setConfigOption('customBackground', value)
|
||||
@@ -170,72 +207,174 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
await server.toggleEncryption(folderPath + '/config.json')
|
||||
|
||||
this.setState({
|
||||
encryption: await translate(await server.encryptionEnabled(folderPath + '/config.json') ? 'options.enabled' : 'options.disabled')
|
||||
encryption: await translate(
|
||||
(await server.encryptionEnabled(folderPath + '/config.json')) ? 'options.enabled' : 'options.disabled'
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
async restoreMetadata() {
|
||||
console.log(this.props)
|
||||
await meta.restoreMetadata(this.props.downloadManager)
|
||||
}
|
||||
|
||||
async installCert() {
|
||||
await invoke('generate_ca_files', {
|
||||
path: await dataDir() + 'cultivation'
|
||||
})
|
||||
}
|
||||
|
||||
async toggleMetadata() {
|
||||
const changedVal = !(await getConfigOption('patch_metadata'))
|
||||
|
||||
await setConfigOption('patch_metadata', changedVal)
|
||||
|
||||
this.setState({
|
||||
patch_metadata: changedVal,
|
||||
})
|
||||
}
|
||||
|
||||
async toggleProxy() {
|
||||
const changedVal = !(await getConfigOption('use_internal_proxy'))
|
||||
|
||||
await setConfigOption('use_internal_proxy', changedVal)
|
||||
|
||||
this.setState({
|
||||
use_internal_proxy: changedVal,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const themeSettings = this.state.theme_object?.settings
|
||||
|
||||
return (
|
||||
<Menu closeFn={this.props.closeFn} className="Options" heading="Options">
|
||||
<div className='OptionSection' id="menuOptionsContainerGameExec">
|
||||
<div className='OptionLabel' id="menuOptionsLabelGameExec">
|
||||
<Tr text="options.game_exec" />
|
||||
<div className="OptionSection" id="menuOptionsContainerGamePath">
|
||||
<div className="OptionLabel" id="menuOptionsLabelGamePath">
|
||||
<Tr text="options.game_path" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsDirGameExec">
|
||||
<DirInput onChange={this.setGameExec} value={this.state?.game_install_path} extensions={['exe']} />
|
||||
<div className="OptionValue" id="menuOptionsDirGamePath">
|
||||
<DirInput onChange={this.setGameExecutable} value={this.state?.game_install_path} extensions={['exe']} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="OptionSection" id="menuOptionsContainermetaDownload">
|
||||
<div className="OptionLabel" id="menuOptionsLabelmetaDownload">
|
||||
<Tr text="options.recover_metadata" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsButtonmetaDownload">
|
||||
<BigButton onClick={this.restoreMetadata} id="metaDownload">
|
||||
<Tr text='components.download' />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="OptionSection" id="menuOptionsContainerPatchMeta">
|
||||
<div className="OptionLabel" id="menuOptionsLabelPatchMeta">
|
||||
<Tr text="options.patch_metadata" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxPatchMeta">
|
||||
<Checkbox
|
||||
onChange={this.toggleMetadata}
|
||||
checked={this.state?.patch_metadata}
|
||||
id="patchMeta"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="OptionSection" id="menuOptionsContainerUseProxy">
|
||||
<div className="OptionLabel" id="menuOptionsLabelUseProxy">
|
||||
<Tr text="options.use_proxy" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxUseProxy">
|
||||
<Checkbox
|
||||
onChange={this.toggleProxy}
|
||||
checked={this.state?.use_internal_proxy}
|
||||
id="useProxy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className='OptionSection' id="menuOptionsContainerGCJar">
|
||||
<div className='OptionLabel' id="menuOptionsLabelGCJar">
|
||||
<Tr text="options.grasscutter_jar" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsDirGCJar">
|
||||
<div className="OptionValue" id="menuOptionsDirGCJar">
|
||||
<DirInput onChange={this.setGrasscutterJar} value={this.state?.grasscutter_path} extensions={['jar']} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='OptionSection' id="menuOptionsContainerToggleEnc">
|
||||
<div className='OptionLabel' id="menuOptionsLabelToggleEnc">
|
||||
<div className="OptionSection" id="menuOptionsContainerToggleEnc">
|
||||
<div className="OptionLabel" id="menuOptionsLabelToggleEnc">
|
||||
<Tr text="options.toggle_encryption" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsButtonToggleEnc">
|
||||
<div className="OptionValue" id="menuOptionsButtonToggleEnc">
|
||||
<BigButton onClick={this.toggleEncryption} id="toggleEnc">
|
||||
{
|
||||
this.state.encryption
|
||||
}
|
||||
{this.state.encryption}
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className='OptionSection' id="menuOptionsContainerInstallCert">
|
||||
<div className='OptionLabel' id="menuOptionsLabelInstallCert">
|
||||
<Tr text="options.install_certificate" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsButtonInstallCert">
|
||||
<BigButton disabled={false} onClick={this.installCert} id="installCert">
|
||||
<Tr text="components.install" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
this.state.swag && (
|
||||
<>
|
||||
<Divider />
|
||||
<div className='OptionSection' id="menuOptionsContainerAkebi">
|
||||
<div className='OptionLabel' id="menuOptionsLabelAkebi">
|
||||
<Tr text="swag.akebi" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsDirAkebi">
|
||||
<DirInput onChange={this.setAkebi} value={this.state?.akebi_path} extensions={['exe']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='OptionSection' id="menuOptionsContainerMigoto">
|
||||
<div className='OptionLabel' id="menuOptionsLabelMigoto">
|
||||
<Tr text="swag.migoto" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsDirMigoto">
|
||||
<DirInput onChange={this.setMigoto} value={this.state?.migoto_path} extensions={['exe']} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className='OptionSection' id="menuOptionsContainerGCWGame">
|
||||
<div className='OptionLabel' id="menuOptionsLabelGCWDame">
|
||||
|
||||
<div className="OptionSection" id="menuOptionsContainerGCWGame">
|
||||
<div className="OptionLabel" id="menuOptionsLabelGCWDame">
|
||||
<Tr text="options.grasscutter_with_game" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsCheckboxGCWGame">
|
||||
<Checkbox onChange={this.toggleGrasscutterWithGame} checked={this.state?.grasscutter_with_game} id="gcWithGame" />
|
||||
<div className="OptionValue" id="menuOptionsCheckboxGCWGame">
|
||||
<Checkbox
|
||||
onChange={this.toggleGrasscutterWithGame}
|
||||
checked={this.state?.grasscutter_with_game}
|
||||
id="gcWithGame"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className='OptionSection' id="menuOptionsContainerThemes">
|
||||
<div className='OptionLabel' id="menuOptionsLabelThemes">
|
||||
<div className="OptionSection" id="menuOptionsContainerThemes">
|
||||
<div className="OptionLabel" id="menuOptionsLabelThemes">
|
||||
<Tr text="options.theme" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsSelectThemes">
|
||||
<select value={this.state.theme} id="menuOptionsSelectMenuThemes" onChange={(event) => {
|
||||
this.setTheme(event.target.value)
|
||||
}}>
|
||||
{this.state.themes.map(t => (
|
||||
<option
|
||||
key={t}
|
||||
value={t}>
|
||||
|
||||
<div className="OptionValue" id="menuOptionsSelectThemes">
|
||||
<select
|
||||
value={this.state.theme}
|
||||
id="menuOptionsSelectMenuThemes"
|
||||
onChange={(event) => {
|
||||
this.setTheme(event.target.value)
|
||||
}}
|
||||
>
|
||||
{this.state.themes.map((t) => (
|
||||
<option key={t} value={t}>
|
||||
{t}
|
||||
</option>
|
||||
))}
|
||||
@@ -245,20 +384,20 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className='OptionSection' id="menuOptionsContainerJavaPath">
|
||||
<div className='OptionLabel' id="menuOptionsLabelJavaPath">
|
||||
<div className="OptionSection" id="menuOptionsContainerJavaPath">
|
||||
<div className="OptionLabel" id="menuOptionsLabelJavaPath">
|
||||
<Tr text="options.java_path" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsDirJavaPath">
|
||||
<div className="OptionValue" id="menuOptionsDirJavaPath">
|
||||
<DirInput onChange={this.setJavaPath} value={this.state?.java_path} extensions={['exe']} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='OptionSection' id="menuOptionsContainerBG">
|
||||
<div className='OptionLabel' id="menuOptionsLabelBG">
|
||||
<div className="OptionSection" id="menuOptionsContainerBG">
|
||||
<div className="OptionLabel" id="menuOptionsLabelBG">
|
||||
<Tr text="options.background" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsDirBG">
|
||||
<div className="OptionValue" id="menuOptionsDirBG">
|
||||
<DirInput
|
||||
onChange={this.setCustomBackground}
|
||||
value={this.state?.bg_url_or_path}
|
||||
@@ -273,43 +412,27 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='OptionSection' id="menuOptionsContainerLang">
|
||||
<div className='OptionLabel' id="menuOptionsLabelLang">
|
||||
<div className="OptionSection" id="menuOptionsContainerLang">
|
||||
<div className="OptionLabel" id="menuOptionsLabelLang">
|
||||
<Tr text="options.language" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsSelectLang">
|
||||
<select value={this.state.current_language} id="menuOptionsSelectMenuLang" onChange={(event) => {
|
||||
this.setLanguage(event.target.value)
|
||||
}}>
|
||||
{this.state.language_options.map(lang => (
|
||||
<option
|
||||
key={Object.keys(lang)[0]}
|
||||
value={Object.keys(lang)[0]}>
|
||||
|
||||
<div className="OptionValue" id="menuOptionsSelectLang">
|
||||
<select
|
||||
value={this.state.current_language}
|
||||
id="menuOptionsSelectMenuLang"
|
||||
onChange={(event) => {
|
||||
this.setLanguage(event.target.value)
|
||||
}}
|
||||
>
|
||||
{this.state.language_options.map((lang) => (
|
||||
<option key={Object.keys(lang)[0]} value={Object.keys(lang)[0]}>
|
||||
{lang[Object.keys(lang)[0]]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{
|
||||
themeSettings ? themeSettings.map((settings, index) => {
|
||||
return (
|
||||
<div className='OptionSection' key={index}>
|
||||
<div className='OptionLabel'>
|
||||
{settings.label}
|
||||
</div>
|
||||
<div className='OptionValue'>
|
||||
<ThemeOptionValue type={settings.type} className={settings.className} data={settings.data} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}) : null
|
||||
}
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,28 @@ interface IProps {
|
||||
|
||||
interface IState {
|
||||
selected: string;
|
||||
news: any;
|
||||
commitList: any;
|
||||
news?: JSX.Element;
|
||||
commitList?: JSX.Element[];
|
||||
}
|
||||
|
||||
interface GrasscutterAPIResponse {
|
||||
commits: {
|
||||
gc_stable: CommitResponse[];
|
||||
gc_dev: CommitResponse[];
|
||||
cultivation: CommitResponse[];
|
||||
}
|
||||
}
|
||||
|
||||
interface CommitResponse {
|
||||
sha: string;
|
||||
commit: Commit;
|
||||
}
|
||||
|
||||
interface Commit {
|
||||
author: {
|
||||
name: string;
|
||||
};
|
||||
message: string;
|
||||
}
|
||||
|
||||
export default class NewsSection extends React.Component<IProps, IState> {
|
||||
@@ -21,8 +41,6 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
|
||||
this.state = {
|
||||
selected: props.selected || 'commits',
|
||||
news: null,
|
||||
commitList: null
|
||||
}
|
||||
|
||||
this.setSelected = this.setSelected.bind(this)
|
||||
@@ -42,40 +60,41 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
|
||||
async showLatestCommits() {
|
||||
if (!this.state.commitList) {
|
||||
const commits: string = await invoke('req_get', { url: 'https://api.grasscutter.io/cultivation/query' })
|
||||
let obj
|
||||
const response: string = await invoke('req_get', { url: 'https://api.grasscutter.io/cultivation/query' })
|
||||
let grasscutterApiResponse: GrasscutterAPIResponse | null = null
|
||||
|
||||
try {
|
||||
obj = JSON.parse(commits)
|
||||
grasscutterApiResponse = JSON.parse(response)
|
||||
} catch(e) {
|
||||
obj = {}
|
||||
grasscutterApiResponse = null
|
||||
}
|
||||
|
||||
// If it didn't work, use official API
|
||||
if (!obj.commits) {
|
||||
const commits: string = await invoke('req_get', { url: 'https://api.github.com/repos/Grasscutters/Grasscutter/commits' })
|
||||
obj = JSON.parse(commits)
|
||||
let commits: CommitResponse[]
|
||||
if (grasscutterApiResponse?.commits == null) {
|
||||
// If it didn't work, use official API
|
||||
const response: string = await invoke('req_get', { url: 'https://api.github.com/repos/Grasscutters/Grasscutter/commits' })
|
||||
commits = JSON.parse(response)
|
||||
} else {
|
||||
obj = obj.commits.gc_stable
|
||||
commits = grasscutterApiResponse.commits.gc_stable
|
||||
}
|
||||
|
||||
// Probably rate-limited
|
||||
if (!Array.isArray(obj)) return
|
||||
if (!Array.isArray(commits)) return
|
||||
|
||||
// Get only first 5
|
||||
const commitsList = obj.slice(0, 10)
|
||||
const commitsListHtml = commitsList.map((commit: any) => {
|
||||
const commitsList = commits.slice(0, 10)
|
||||
const commitsListHtml = commitsList.map((commitResponse: CommitResponse) => {
|
||||
return (
|
||||
<tr className="Commit" id="newsCommitsTable" key={commit.sha}>
|
||||
<td className="CommitAuthor"><span>{commit.commit.author.name}</span></td>
|
||||
<td className="CommitMessage"><span>{commit.commit.message}</span></td>
|
||||
<tr className="Commit" id="newsCommitsTable" key={commitResponse.sha}>
|
||||
<td className="CommitAuthor"><span>{commitResponse.commit.author.name}</span></td>
|
||||
<td className="CommitMessage"><span>{commitResponse.commit.message}</span></td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
|
||||
this.setState({
|
||||
commitList: commitsListHtml,
|
||||
news: commitsListHtml
|
||||
news: <>{commitsListHtml}</>
|
||||
})
|
||||
}
|
||||
|
||||
@@ -83,12 +102,16 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
async showNews() {
|
||||
let news = <tr></tr>
|
||||
let news: JSX.Element | JSX.Element[] = <tr></tr>
|
||||
|
||||
switch(this.state.selected) {
|
||||
case 'commits':
|
||||
news = await this.showLatestCommits()
|
||||
case 'commits': {
|
||||
const commits = await this.showLatestCommits()
|
||||
if (commits != null) {
|
||||
news = commits
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'latest_version':
|
||||
news = <tr><td>Latest version</td></tr>
|
||||
@@ -100,7 +123,7 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
this.setState({
|
||||
news
|
||||
news: <>{news}</>
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ let defaultConfig: Configuration
|
||||
cert_generated: false,
|
||||
theme: 'default',
|
||||
https_enabled: false,
|
||||
debug_enabled: false
|
||||
debug_enabled: false,
|
||||
patch_metadata: true,
|
||||
use_internal_proxy: true,
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -43,20 +45,27 @@ export interface Configuration {
|
||||
theme: string
|
||||
https_enabled: boolean
|
||||
debug_enabled: boolean
|
||||
patch_metadata: boolean
|
||||
use_internal_proxy: boolean
|
||||
swag_mode?: boolean
|
||||
|
||||
// Swag stuff
|
||||
akebi_path?: string
|
||||
migoto_path?: string
|
||||
}
|
||||
|
||||
export async function setConfigOption(key: string, value: any): Promise<void> {
|
||||
const config: any = await getConfig()
|
||||
export async function setConfigOption<K extends keyof Configuration>(key: K, value: Configuration[K]): Promise<void> {
|
||||
const config = await getConfig()
|
||||
config[key] = value
|
||||
|
||||
await saveConfig(<Configuration> config)
|
||||
}
|
||||
|
||||
export async function getConfigOption(key: string): Promise<any> {
|
||||
const config: any = await getConfig()
|
||||
const defaults: any = defaultConfig
|
||||
export async function getConfigOption<K extends keyof Configuration>(key: K): Promise<Configuration[K]> {
|
||||
const config = await getConfig()
|
||||
const defaults = defaultConfig
|
||||
|
||||
return config[key] || defaults[key]
|
||||
return config[key] === null || config[key] === undefined ? defaults[key] : config[key]
|
||||
}
|
||||
|
||||
export async function getConfig() {
|
||||
@@ -84,7 +93,7 @@ export async function saveConfig(obj: Configuration) {
|
||||
async function readConfigFile() {
|
||||
const local = await dataDir()
|
||||
|
||||
if (!configFilePath) configFilePath = local + 'cultivation\\configuration.json'
|
||||
if (!configFilePath) configFilePath = local + 'cultivation/configuration.json'
|
||||
|
||||
// Ensure Cultivation dir exists
|
||||
const dirs = await fs.readDir(local)
|
||||
@@ -94,12 +103,12 @@ async function readConfigFile() {
|
||||
await fs.createDir(local + 'cultivation').catch(e => console.log(e))
|
||||
}
|
||||
|
||||
const innerDirs = await fs.readDir(local + '\\cultivation')
|
||||
const innerDirs = await fs.readDir(local + '/cultivation')
|
||||
|
||||
// Create grasscutter dir for potential installation
|
||||
if (!innerDirs.find((fileOrDir) => fileOrDir?.name === 'grasscutter')) {
|
||||
// Create dir
|
||||
await fs.createDir(local + 'cultivation\\grasscutter').catch(e => console.log(e))
|
||||
await fs.createDir(local + 'cultivation/grasscutter').catch(e => console.log(e))
|
||||
}
|
||||
|
||||
const dataFiles = await fs.readDir(local + 'cultivation')
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { setConfigOption } from './configuration'
|
||||
|
||||
interface DOMMessage {
|
||||
type: string
|
||||
data: ConfigUpdate
|
||||
}
|
||||
|
||||
interface ConfigUpdate {
|
||||
setting: string
|
||||
value: any
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a message received from the DOM.
|
||||
* @param document The document.
|
||||
* @param msg The message received from the DOM.
|
||||
*/
|
||||
export function parseMessageFromDOM(document: Document, msg: any): void {
|
||||
msg = msg.detail
|
||||
|
||||
if(!msg || !msg.type || !msg.data)
|
||||
return
|
||||
|
||||
switch(msg.type) {
|
||||
case 'updateConfig':
|
||||
if(!msg.data.setting || !msg.data.value)
|
||||
return
|
||||
setConfigOption(msg.data.setting, msg.data.value)
|
||||
return
|
||||
}
|
||||
}
|
||||
27
src/utils/game.ts
Normal file
27
src/utils/game.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { getConfig } from './configuration'
|
||||
|
||||
export async function getGameExecutable() {
|
||||
const config = await getConfig()
|
||||
|
||||
if(!config.game_install_path) {
|
||||
return null
|
||||
}
|
||||
|
||||
const pathArr = config.game_install_path.replace(/\\/g, '/').split('/')
|
||||
return pathArr[pathArr.length - 1]
|
||||
}
|
||||
|
||||
export async function getGameFolder() {
|
||||
const config = await getConfig()
|
||||
|
||||
if(!config.game_install_path) {
|
||||
return null
|
||||
}
|
||||
|
||||
const pathArr = config.game_install_path.replace(/\\/g, '/').split('/')
|
||||
pathArr.pop()
|
||||
|
||||
const path = pathArr.join('/')
|
||||
|
||||
return path
|
||||
}
|
||||
228
src/utils/metadata.ts
Normal file
228
src/utils/metadata.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import DownloadHandler from './download'
|
||||
import { getGameExecutable, getGameFolder } from './game'
|
||||
|
||||
export async function patchMetadata() {
|
||||
const metadataExists = await invoke('dir_exists', {
|
||||
path: await getGameMetadataPath() + '\\global-metadata.dat'
|
||||
})
|
||||
|
||||
if (!metadataExists) {
|
||||
return false
|
||||
}
|
||||
|
||||
console.log('Copying unpatched metadata to backup location')
|
||||
|
||||
// Copy unpatched metadata to backup location
|
||||
const copiedMeta = await invoke('copy_file_with_new_name', {
|
||||
path: await getGameMetadataPath() + '\\global-metadata.dat',
|
||||
newPath: await getBackupMetadataPath(),
|
||||
newName: 'global-metadata-unpatched.dat'
|
||||
})
|
||||
|
||||
if (!copiedMeta) {
|
||||
console.log(await getBackupMetadataPath())
|
||||
return false
|
||||
}
|
||||
|
||||
// backup was successful! Time to patch
|
||||
|
||||
console.log('Patching backedup metadata')
|
||||
|
||||
const patchedMeta = await invoke('patch_metadata', {
|
||||
metadataFolder: await getBackupMetadataPath(),
|
||||
})
|
||||
|
||||
if (!patchedMeta) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Patch also worked! Time to replace
|
||||
console.log('Replacing unpatched game metadata with patched metadata')
|
||||
|
||||
const replacedMeta = await invoke('copy_file_with_new_name', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-patched.dat',
|
||||
newPath: await getGameMetadataPath(),
|
||||
newName: 'global-metadata.dat'
|
||||
})
|
||||
|
||||
if (!replacedMeta) {
|
||||
return false
|
||||
}
|
||||
|
||||
console.log('Replacement successful!')
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export async function patchGame() {
|
||||
const backupExists = await invoke('dir_exists', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-unpatched.dat'
|
||||
})
|
||||
|
||||
if (!backupExists) {
|
||||
// No backup found? Patching creates one
|
||||
const patched = await patchMetadata()
|
||||
|
||||
if (!patched) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have a patch already?
|
||||
const patchedExists = await invoke('dir_exists', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-patched.dat'
|
||||
})
|
||||
|
||||
if (!patchedExists) {
|
||||
// No patch found? Patching creates one
|
||||
const patched = await patchMetadata()
|
||||
|
||||
if (!patched) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Are we already patched? If so, that's fine, just continue as normal
|
||||
const gameIsPatched = await invoke('are_files_identical', {
|
||||
path1: await getBackupMetadataPath() + '\\global-metadata-patched.dat',
|
||||
path2: await getGameMetadataPath() + '\\global-metadata.dat'
|
||||
})
|
||||
|
||||
if (gameIsPatched) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Is the current backup the same as the games current metadata?
|
||||
const backupIsCurrent = await invoke('are_files_identical', {
|
||||
path1: await getBackupMetadataPath() + '\\global-metadata-unpatched.dat',
|
||||
path2: await getGameMetadataPath() + '\\global-metadata.dat'
|
||||
})
|
||||
|
||||
// Game has probably been updated. We need to repatch the game...
|
||||
if (!backupIsCurrent) {
|
||||
const deletedOldBackup = await invoke('delete_file', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-unpatched.dat'
|
||||
})
|
||||
const deletedOldPatched = await invoke('delete_file', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-patched.dat'
|
||||
})
|
||||
|
||||
// It's fine if these deletes fail. The game will be replaced anyway.
|
||||
if (!deletedOldBackup) {
|
||||
console.log('Warning: Failed to delete old backup!')
|
||||
}
|
||||
|
||||
if (!deletedOldPatched) {
|
||||
console.log('Warning: Failed to delete old patched metadata!')
|
||||
}
|
||||
|
||||
console.log('Patching Metadata')
|
||||
|
||||
const patched = await patchMetadata()
|
||||
|
||||
if (!patched) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
console.log('Metadata is not patched')
|
||||
console.log('Replacing unpatched metadata')
|
||||
|
||||
// Finally, replace the unpatched metadata with the patched one
|
||||
const replaced = await invoke('copy_file_with_new_name', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-patched.dat',
|
||||
newPath: await getGameMetadataPath(),
|
||||
newName: 'global-metadata.dat'
|
||||
})
|
||||
|
||||
if (!replaced) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export async function unpatchGame() {
|
||||
const backupExists = await invoke('dir_exists', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-unpatched.dat'
|
||||
})
|
||||
|
||||
if (!backupExists) {
|
||||
// Let's just hope the game isn't on a patched metadata since we don't have a backup...
|
||||
return true
|
||||
}
|
||||
|
||||
const replaced = await invoke('copy_file_with_new_name', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-unpatched.dat',
|
||||
newPath: await getGameMetadataPath(),
|
||||
newName: 'global-metadata.dat'
|
||||
})
|
||||
|
||||
return replaced
|
||||
}
|
||||
|
||||
export async function getGameMetadataPath() {
|
||||
const gameExec = await getGameExecutable()
|
||||
|
||||
if (!gameExec) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (await getGameFolder() + '\\' + gameExec.replace('.exe', '_Data') + '\\Managed\\Metadata').replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
export async function getBackupMetadataPath() {
|
||||
return await dataDir() + 'cultivation\\metadata'
|
||||
}
|
||||
|
||||
export async function globalMetadataLink() {
|
||||
const versionAPIUrl = 'https://sdk-os-static.mihoyo.com/hk4e_global/mdk/launcher/api/resource?channel_id=1&key=gcStgarh&launcher_id=10&sub_channel_id=0'
|
||||
|
||||
// Get versions from API
|
||||
const versions = JSON.parse(await invoke('web_get', {
|
||||
url: versionAPIUrl
|
||||
}))
|
||||
|
||||
if (!versions || versions.retcode !== 0) {
|
||||
console.log('Failed to get versions from API')
|
||||
return null
|
||||
}
|
||||
|
||||
// Get latest version
|
||||
const latest = versions.data.game.latest
|
||||
|
||||
return latest.decompressed_path as string + '/GenshinImpact_Data/Managed/Metadata/global-metadata.dat'
|
||||
}
|
||||
|
||||
export async function restoreMetadata(manager: DownloadHandler) {
|
||||
const metaLink = await globalMetadataLink()
|
||||
|
||||
if (!metaLink) {
|
||||
console.log('Could not get global metadata link!')
|
||||
return false
|
||||
}
|
||||
|
||||
// Should make sure metadata path exists since the user may have deleted it
|
||||
await invoke('dir_create', {
|
||||
path: await getBackupMetadataPath()
|
||||
})
|
||||
|
||||
// It is possible the unpatched backup is mistakenly patched
|
||||
await invoke('delete_file', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-unpatched.dat'
|
||||
})
|
||||
|
||||
// Download the file
|
||||
manager.addDownload(metaLink, await getBackupMetadataPath() + '\\global-metadata-unpatched.dat', () => {
|
||||
unpatchGame()
|
||||
})
|
||||
console.log('Restoring backedup metadata')
|
||||
|
||||
await unpatchGame()
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { fs } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
|
||||
export async function toggleEncryption(path: string) {
|
||||
let serverConf
|
||||
|
||||
try {
|
||||
serverConf = JSON.parse(await fs.readTextFile(path))
|
||||
serverConf = JSON.parse(await invoke('read_file', {
|
||||
path,
|
||||
}))
|
||||
} catch(e) {
|
||||
console.log(`Server config at ${path} not found or invalid`)
|
||||
return
|
||||
@@ -16,9 +18,9 @@ export async function toggleEncryption(path: string) {
|
||||
serverConf.server.http.encryption.useInRouting = !enabled
|
||||
|
||||
// Write file
|
||||
await fs.writeFile({
|
||||
await invoke('write_file', {
|
||||
path,
|
||||
contents: JSON.stringify(serverConf)
|
||||
contents: JSON.stringify(serverConf, null, 2),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,7 +28,9 @@ export async function encryptionEnabled(path: string) {
|
||||
let serverConf
|
||||
|
||||
try {
|
||||
serverConf = JSON.parse(await fs.readTextFile(path))
|
||||
serverConf = JSON.parse(await invoke('read_file', {
|
||||
path,
|
||||
}))
|
||||
} catch(e) {
|
||||
console.log(`Server config at ${path} not found or invalid`)
|
||||
return false
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import {invoke} from '@tauri-apps/api'
|
||||
import {dataDir} from '@tauri-apps/api/path'
|
||||
import {convertFileSrc} from '@tauri-apps/api/tauri'
|
||||
import {getConfig, setConfigOption} from './configuration'
|
||||
|
||||
import {InputSettings} from '../ui/components/common/ThemeOptionValue'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { getConfig, setConfigOption } from './configuration'
|
||||
|
||||
interface Theme {
|
||||
name: string
|
||||
@@ -15,16 +13,6 @@ interface Theme {
|
||||
css: string[]
|
||||
js: string[]
|
||||
}
|
||||
|
||||
// Custom settings.
|
||||
settings?: {
|
||||
label: string // The setting's label.
|
||||
type: string // The setting's type.
|
||||
data: InputSettings // The data for the setting.
|
||||
|
||||
className?: string // The name of the class this setting should take.
|
||||
jsCallback?: string // The name of the callback method that should be invoked.
|
||||
}[]
|
||||
|
||||
customBackgroundURL?: string
|
||||
customBackgroundPath?: string
|
||||
@@ -35,7 +23,7 @@ interface BackendThemeList {
|
||||
path: string
|
||||
}
|
||||
|
||||
export interface ThemeList extends Theme {
|
||||
interface ThemeList extends Theme {
|
||||
path: string
|
||||
}
|
||||
|
||||
@@ -49,7 +37,6 @@ const defaultTheme = {
|
||||
},
|
||||
path: 'default'
|
||||
}
|
||||
|
||||
export async function getThemeList() {
|
||||
// Do some invoke to backend to get the theme list
|
||||
const themes = await invoke('get_theme_list', {
|
||||
@@ -90,11 +77,6 @@ export async function getTheme(name: string) {
|
||||
return themes.find(t => t.name === name) || defaultTheme
|
||||
}
|
||||
|
||||
export async function getSelectedTheme() {
|
||||
const config = await getConfig()
|
||||
return await getTheme(config.theme)
|
||||
}
|
||||
|
||||
export async function loadTheme(theme: ThemeList, document: Document) {
|
||||
// Get config, since we will set the custom background in there
|
||||
const config = await getConfig()
|
||||
|
||||
Reference in New Issue
Block a user