Compare commits

..

107 Commits

Author SHA1 Message Date
SpikeHD
dc20fe5916 set proxy addr only when launching with proxy 2022-07-19 17:52:17 -07:00
SpikeHD
1a6ed38f8f only unpatch when game was patched automatically 2022-07-19 17:49:40 -07:00
SpikeHD
d23d5e3806 version bump! 2022-07-19 17:45:13 -07:00
SpikeHD
365a4f2888 spanish translation 2022-07-19 17:43:31 -07:00
SpikeHD
e270c886db 3dm support that doesn't actually work I just don't wanna fix it 2022-07-19 17:40:22 -07:00
SpikeHD
18a1b0e94c Merge branch 'main' of github.com:Grasscutters/Cultivation 2022-07-19 17:14:59 -07:00
SpikeHD
3799ec648d make proxy optional 2022-07-19 17:14:55 -07:00
SpikeHD
8ff06f6d29 make metadata optional 2022-07-19 17:07:10 -07:00
SpikeHD
6ff1ef932c Merge pull request #35 from ffauzan/patch-1
Fix toggle encryption
2022-07-19 09:11:59 -07:00
SpikeHD
9e29135376 Merge pull request #38 from ffauzan/patch-2
Fix patched metadata checking
2022-07-19 09:10:09 -07:00
ffauzan
109f98db66 Fix patched metadata checking
Issue: The function checked the wrong directory for global-metadata-patched.dat
2022-07-19 16:41:25 +07:00
ffauzan
69201bc8b1 Update file_helpers.rs 2022-07-19 11:52:12 +07:00
ffauzan
656fa2cfe3 Revert "fix conflict"
This reverts commit 1588bee5a3, reversing
changes made to 75b79d0202.
2022-07-19 11:47:38 +07:00
Ffauzan
1588bee5a3 fix conflict 2022-07-19 11:27:28 +07:00
Ffauzan
75b79d0202 Merge branch 'Grasscutters-main' into patch-1 2022-07-19 11:04:03 +07:00
Ffauzan
6d9f1af134 Merge branch 'main' of https://github.com/Grasscutters/Cultivation into Grasscutters-main 2022-07-19 11:03:51 +07:00
SpikeHD
d38459bb8a Merge pull request #37 from Seeker14491/patch-1
Fix lints, update deps, clean readme
2022-07-18 18:06:24 -07:00
ffauzan
c7954d2294 Update file_helpers.rs 2022-07-19 06:39:29 +07:00
Brian Bowman
adbb8e380d Clean up readme 2022-07-18 18:01:08 -05:00
Brian Bowman
4ff9e88185 Update dependencies 2022-07-18 18:01:08 -05:00
Brian Bowman
27a10c58ca Fix lints 2022-07-18 18:01:08 -05:00
ffauzan
44b148f2a4 Fix toggle encryption
Open the config file in write mode so the toggle encryption button is actually working
2022-07-18 22:03:01 +07:00
SpikeHD
6434814d1d fix decryption 2022-07-17 00:08:31 -07:00
SpikeHD
043f3e7ce4 simplify metadata processes 2022-07-16 23:34:55 -07:00
SpikeHD
61ac332cee Log paths on copy error 2022-07-16 22:56:01 -07:00
SpikeHD
a1284cc2ff path_bufs for file helpers 2022-07-16 22:06:26 -07:00
SpikeHD
cf82e9e892 use path bufs for copying files 2022-07-16 22:02:21 -07:00
SpikeHD
bb1874d64a Merge branch 'main' of https://github.com/Grasscutters/Cultivation 2022-07-16 21:26:07 -07:00
SpikeHD
85c0e2473b 2.8 resources download 2022-07-16 21:26:00 -07:00
SpikeHD
9e3b584608 Update README.md 2022-07-16 21:23:28 -07:00
SpikeHD
5568183821 Update README.md 2022-07-16 21:20:56 -07:00
SpikeHD
d64186777f Merge pull request #31 from Grasscutters/patching
CLIENT PATCHING LETS GOOOOOOO
2022-07-16 21:16:01 -07:00
SpikeHD
f2d45d2359 version bump (omg what could this mean) 2022-07-16 21:13:34 -07:00
SpikeHD
d46a2bdc44 Merge branch 'main' into patching 2022-07-16 17:03:36 -07:00
SpikeHD
bb383c5c0a manual proxy cert install 2022-07-16 16:59:16 -07:00
SpikeHD
4b42d0f8b8 reorg 2022-07-16 16:42:20 -07:00
SpikeHD
3d0f8a3ff6 remove unused fs 2022-07-16 16:39:26 -07:00
SpikeHD
56b0c78661 Merge branch 'main' into patching 2022-07-16 16:38:54 -07:00
SpikeHD
ffc37cc203 read and write server config via backend 2022-07-16 16:36:05 -07:00
SpikeHD
8faaba2849 Update README.md 2022-07-16 14:43:29 -07:00
SpikeHD
9d86d9c9ff lint 2022-07-15 21:27:50 -07:00
SpikeHD
8ff1a47fff Merge branch 'main' into patching 2022-07-15 21:14:16 -07:00
SpikeHD
12da09596d Merge pull request #23 from Seeker14491/ci
Set up GitHub Actions lints and checks
2022-07-15 21:11:06 -07:00
SpikeHD
bd54a78e4b emergency metadata recovery 2022-07-15 19:35:57 -07:00
SpikeHD
bb74553bee Update README.md 2022-07-15 12:43:32 -07:00
SpikeHD
d373f46615 Update README.md 2022-07-15 12:43:00 -07:00
SpikeHD
a1b0fec871 Merge branch 'main' into patching 2022-07-14 20:54:02 -07:00
SpikeHD
bae193050f window reload 2022-07-14 20:13:51 -07:00
SpikeHD
fc5ffae1e2 another normal feature 2022-07-14 20:05:11 -07:00
SpikeHD
ab0e05ffe1 scrolling with no scrollbars 2022-07-14 18:52:12 -07:00
SpikeHD
88a1740b91 normal feature 2022-07-14 18:49:49 -07:00
SpikeHD
f24f3af377 just another normal everyday feature, nothing to see here 2022-07-14 18:05:52 -07:00
SpikeHD
9bdb18d4d6 fix metadata restoration 2022-07-13 21:23:41 -07:00
SpikeHD
7cbb600a7f always unpatch game on close 2022-07-13 18:55:06 -07:00
SpikeHD
33c733ce97 Merge branch 'main' into patching 2022-07-13 18:49:24 -07:00
SpikeHD
abafc94379 rework process watcher to be not terrible 2022-07-13 18:49:06 -07:00
SpikeHD
411e11dd8d Merge branch 'main' into patching 2022-07-13 18:26:17 -07:00
SpikeHD
b2453e7c4d fix crash on denying UAC prompt 2022-07-13 18:26:06 -07:00
SpikeHD
30476a86ad Merge main 2022-07-13 18:22:10 -07:00
SpikeHD
375e15e947 fix funky paths 2022-07-13 18:19:55 -07:00
SpikeHD
fd87adc1f6 remove redundant option 2022-07-13 18:15:32 -07:00
SpikeHD
b903c27a22 BIG FAT CLEANUP PART ONE 2022-07-13 18:10:41 -07:00
Brian Bowman
5bf7019482 Also list npm command for running formatting and linting scripts 2022-07-12 23:30:43 -05:00
Brian Bowman
be633eeea5 Document how to run formatting and linting scripts 2022-07-12 23:19:59 -05:00
Brian Bowman
58e683c669 Add format and lint scripts 2022-07-12 20:10:05 -05:00
Brian Bowman
cd5c2985e5 Add rustfmt CI check 2022-07-12 20:10:05 -05:00
Brian Bowman
e41a89b26c Configure and run rustfmt 2022-07-12 20:10:05 -05:00
Brian Bowman
4d05063b61 Set up CI for frontend 2022-07-12 20:10:05 -05:00
Brian Bowman
53e2b0cbed Fix tsc and ESLint warnings 2022-07-12 20:10:05 -05:00
Brian Bowman
51d00add22 Set up CI for backend 2022-07-12 20:10:05 -05:00
SpikeHD
4fc90ee333 cleanup and yarn update 2022-07-12 17:21:03 -07:00
SpikeHD
0ec8782f48 use thread properly lol 2022-07-12 17:15:32 -07:00
SpikeHD
33c67eef06 re-threadify program launching 2022-07-12 12:28:31 -07:00
Benj
a703843eed Move things to file_helpers
These were meant to be here anyway but I didn't put it here because other similar methods were in system_helpers
2022-07-12 14:28:08 +08:00
Benj
ba2a8b7fec Merge tag 'v1.0.2-alpha' into patcher-2.0
# Conflicts:
#	src-tauri/Cargo.lock
#	src-tauri/src/file_helpers.rs
#	src-tauri/src/main.rs
2022-07-12 13:37:44 +08:00
SpikeHD
b3585927ca Merge branch 'main' of github.com:Grasscutters/Cultivation 2022-07-11 19:45:29 -07:00
Benj
d4e284663e Removed package-lock.json 2022-07-11 14:23:58 +08:00
SpikeHD
4c9dad49c4 version bump 2022-07-09 23:14:48 -07:00
SpikeHD
960fcae647 Merge branch 'main' of https://github.com/Grasscutters/Cultivation 2022-07-09 22:52:04 -07:00
SpikeHD
4172ee9106 move copy_file to file_helpers 2022-07-09 22:50:41 -07:00
SpikeHD
8566e7f35e remove legacy bg folder creation 2022-07-09 22:49:37 -07:00
Benj
065043bbe9 Whoops 2022-07-10 11:57:50 +08:00
Benj
2c3a23e841 No more repeated hardcoded strings
and some debug stuff
2022-07-10 11:39:16 +08:00
SpikeHD
0b2296f918 Merge pull request #20 from Seeker14491/cleanup
Update backend deps & Fix clippy lints
2022-07-09 10:35:03 -07:00
Brian Bowman
6f997a38d3 Remove unneeded thread spawn
As of `open` v3.0.0, this function never blocks, so we don't need to spawn a thread.
2022-07-09 07:11:31 -05:00
Brian Bowman
cd128741b4 Update dependencies 2022-07-09 07:11:31 -05:00
Brian Bowman
174a7b5163 Fix clippy lints 2022-07-09 07:11:31 -05:00
Brian Bowman
e75474fde7 Replace lazy_static with once_cell
`once_cell` is a simpler macroless alternative that will be added to the standard library.
2022-07-09 07:05:38 -05:00
Brian Bowman
c3119ce7a7 Remove unused runas dependency 2022-07-09 05:54:04 -05:00
SpikeHD
6c4b546de2 Update README.md 2022-07-09 01:57:41 -07:00
SpikeHD
30388eb6a9 Update README.md 2022-07-09 01:57:08 -07:00
Benj
f35b596eb2 Some globalisation stuff
Game executable now selectable in settings. Translations need updating for "Set Game Path".
2022-07-09 15:11:19 +08:00
SpikeHD
3008f50e1f Merge branch 'main' of github.com:Grasscutters/Cultivation 2022-07-08 22:21:41 -07:00
SpikeHD
99293ad7cf disable encryption toggle when no jar set 2022-07-06 17:57:17 -07:00
Benj
99687f0550 Autopatching on game launch
Plus adding some non-functional options for later
Need to add support for Chinese version of the game
2022-07-07 01:25:54 +08:00
lilmayofuksu
6124d6949c Fix gcc build for real this time 2022-07-06 20:11:44 +03:00
SpikeHD
afdbc5ad80 Merge pull request #16 from phuchptty/main
add vietnamese language file
2022-07-06 09:26:00 -07:00
lilmayofuksu
dd56af8fcb Actually fix g++ builds 2022-07-06 19:04:27 +03:00
ayy lmao
487b36a37e Fix build for g++ 2022-07-06 19:04:27 +03:00
ayy lmao
850b282b70 Build and link mhycrypto statically 2022-07-06 19:04:27 +03:00
Benj
e0272aa38a Re-encrypt Metadata
We in the end game now bois (Time for UI, refactoring, and better functionality)
2022-07-06 19:04:27 +03:00
Benj
27d7c32a73 Key replacement 2022-07-06 19:04:27 +03:00
Benj
936c533ff8 Starting work on brand new patcher (Wooo)
Oh god my sanity is fading again. Please help. oh god. oh god. oh god. Please help me.
2022-07-06 19:04:27 +03:00
phuchptty
1c0edd2bcd feat: add vietnamese language file 2022-07-06 22:38:59 +07:00
SpikeHD
8ac4d063a4 Merge pull request #14 from Seeker14491/unix-fix
Fix build errors on Unix
2022-07-05 21:50:33 -07:00
Brian Bowman
bb9a044e05 Fix reading config on Unix 2022-07-05 22:58:56 -05:00
Brian Bowman
4f63e55a28 Fix build errors on Unix 2022-07-05 19:39:48 -05:00
69 changed files with 9850 additions and 8132 deletions

View File

@@ -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

View File

@@ -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
View 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
View 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
View File

@@ -23,4 +23,5 @@ yarn-debug.log*
yarn-error.log*
# moved lang files
/lang
/lang
package-lock.json

View File

@@ -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.

View File

@@ -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": [

View File

@@ -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>

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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()
}

View File

@@ -0,0 +1 @@
<RSAKeyValue><Modulus>AMW28dptX3h8q0O4z/vJrQxf6cmC6yVilgHRL98GazrYzmc3ixj87JpHIJ3IKEYV+HU/tYrUjEfY/ZtPzsLB9lKBelN9i8QjkFkA9QDICGYwJCXibxU67Z/HzENe9NQpG2i01SI0TJU8PJDV7zQPwPVGraIg5ouExRupq8UymaSHEyJ7zxKZCtgO0LKdROLJBSvI5srMu7kYTGmB7T07Ab8T9M595YSgd1vh06qZ3nsF1h4wg3y+zW28vdY28+RCj2V1i7oVyL0dQruLYq7qK8FycZl2j9R0GaJ8rRAjVP1Dsz+hjS3atHhQxOG9OFo6d/euedRvfWIhT9p6h1SeTjE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>

View File

@@ -0,0 +1,7 @@
<RSAKeyValue>
<Exponent>AQAB</Exponent>
<Modulus>yytg/H9lz7Lm0XcA8LMqIyXPVNApYTcSepT4VDLB4qqqFC3s
/Huv8vN7zA/P4uoREIu8KMenADFk7uwrZSxoMWwJgn6A7sbAt1cqAaUXB
9J4NzhL0x3AFTiHEQbw86hRvm2VGkbA5sWnr0NZw8SGBBY+EODwNIt51G
dBA7eoUQU=</Modulus>
</RSAKeyValue>

View File

@@ -11,7 +11,7 @@
"files_extracting": "文件解压中:"
},
"options": {
"game_exec": "选择游戏可执行文件",
"game_executable": "选择游戏可执行文件",
"grasscutter_jar": "选择 Grasscutter JAR 文件",
"java_path": "设置自定义 Java 路径",
"grasscutter_with_game": "随游戏自动启动 Grasscutter",

View File

@@ -13,7 +13,7 @@
"options": {
"enabled": "已啟用",
"disabled": "未啟用",
"game_exec": "選擇遊戲執行檔",
"game_executable": "選擇遊戲執行檔",
"grasscutter_jar": "選擇伺服器JAR檔案",
"toggle_encryption": "設定加密",
"java_path": "設定自定義Java路徑",

View File

@@ -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",

View File

@@ -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
View 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"
}
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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
View 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

Binary file not shown.

387
src-tauri/mhycrypto/aes.c Normal file
View 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
View 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

View 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);
}

View 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

View 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);
}
}
}

View 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

View 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);
}
}

View 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
View File

@@ -0,0 +1,3 @@
tab_spaces = 2
use_field_init_shorthand = true
use_try_shorthand = true

View File

@@ -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
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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()
}
};
}
}

View 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()
}

View File

@@ -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.");
}

View File

@@ -5,4 +5,4 @@ use serde::Deserialize;
#[derive(Deserialize)]
pub(crate) struct APIQuery {
pub bg_file: String,
}
}

View File

@@ -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
}

View File

@@ -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();
});
}
}

View File

@@ -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
}

View File

@@ -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}}",

View File

@@ -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)

View File

@@ -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": ""
}

View 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

View File

@@ -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

View File

@@ -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">

View File

@@ -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%);

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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" />

View File

@@ -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
}

View File

@@ -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
})
}

View File

@@ -12,9 +12,7 @@ interface IProps {
id?: string;
clearable?: boolean;
customClearBehaviour?: () => void;
style?: {
[key: string]: any;
}
style?: React.CSSProperties;
}
interface IState {

View File

@@ -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>
)
}
}
}

View File

@@ -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 !== '',
})
}

View File

@@ -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 {

View File

@@ -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>
)
}
}
}

View File

@@ -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}</>
})
}

View File

@@ -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')

View File

@@ -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
View 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
View 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
}

View File

@@ -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

View File

@@ -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()

13453
yarn.lock

File diff suppressed because it is too large Load Diff