Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5709a1ec9d | ||
|
|
4b243f8ea5 | ||
|
|
045115280f | ||
|
|
3a4fbaa46e | ||
|
|
51bdafb6da | ||
|
|
540d52c408 | ||
|
|
d532fa1211 | ||
|
|
09c24a777f | ||
|
|
bc93256bda | ||
|
|
c06f243a9b | ||
|
|
0ef50b889b | ||
|
|
d9b820c842 | ||
|
|
986259d96b | ||
|
|
97454de75e | ||
|
|
f61f4eed51 | ||
|
|
99b45ddf52 | ||
|
|
fafec01fe3 | ||
|
|
0c910b7317 | ||
|
|
6f2be3c5a5 | ||
|
|
d2b8124877 | ||
|
|
247150c62a | ||
|
|
0370576c11 |
39
.github/workflows/build.yml
vendored
@@ -24,9 +24,9 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: lts/*
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -34,31 +34,30 @@ jobs:
|
||||
toolchain: stable
|
||||
|
||||
- name: Install deps and build
|
||||
run: yarn && yarn build --debug
|
||||
run: yarn && yarn tauri build
|
||||
|
||||
- name: Compress build
|
||||
uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: src-tauri/target/debug/lang/ src-tauri/target/debug/keys/ src-tauri/target/debug/patch/ src-tauri/target/debug/Cultivation.exe src-tauri/target/debug/bundle/msi/
|
||||
files: src-tauri/target/release/lang/ src-tauri/target/release/keys/ src-tauri/target/release/patch/ src-tauri/target/release/Cultivation.exe src-tauri/target/release/bundle/msi/
|
||||
recursive: true
|
||||
dest: Cultivation.zip
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CultivationWin
|
||||
path: Cultivation.zip
|
||||
|
||||
build-ubuntu:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: lts/*
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -66,20 +65,32 @@ jobs:
|
||||
toolchain: stable
|
||||
|
||||
- name: Install libraries
|
||||
run: sudo apt install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install libwebkit2gtk-4.1-dev \
|
||||
build-essential \
|
||||
curl \
|
||||
wget \
|
||||
file \
|
||||
libxdo-dev \
|
||||
libssl-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev \
|
||||
protobuf-compiler \
|
||||
libprotobuf-dev
|
||||
|
||||
- name: Install deps and build
|
||||
run: yarn && yarn build --debug
|
||||
- name: Install deps and build release
|
||||
run: yarn && NO_STRIP=true yarn tauri build
|
||||
|
||||
- name: Compress build
|
||||
uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: src-tauri/target/debug/lang/ src-tauri/target/debug/keys/ src-tauri/target/debug/patch/ src-tauri/target/debug/cultivation
|
||||
files: src-tauri/target/release/lang/ src-tauri/target/release/keys/ src-tauri/target/release/patch/ src-tauri/target/release/cultivation src-tauri/target/release/bundle/
|
||||
recursive: true
|
||||
dest: Cultivation.zip
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CultivationLinux
|
||||
path: Cultivation.zip
|
||||
|
||||
44
.gitignore
vendored
@@ -1,28 +1,24 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.vs
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# moved lang files
|
||||
/lang
|
||||
package-lock.json
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
8
.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
14
.idea/cultivation.iml
generated
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src-tauri/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/src-tauri/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
7
.idea/discord.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
||||
40
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,40 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HtmlUnknownBooleanAttribute" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpUrlsUsage" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="ignoredUrls">
|
||||
<list>
|
||||
<option value="http://localhost" />
|
||||
<option value="http://127.0.0.1" />
|
||||
<option value="http://0.0.0.0" />
|
||||
<option value="http://www.w3.org/" />
|
||||
<option value="http://json-schema.org/draft" />
|
||||
<option value="http://java.sun.com/" />
|
||||
<option value="http://xmlns.jcp.org/" />
|
||||
<option value="http://javafx.com/javafx/" />
|
||||
<option value="http://javafx.com/fxml" />
|
||||
<option value="http://maven.apache.org/xsd/" />
|
||||
<option value="http://maven.apache.org/POM/" />
|
||||
<option value="http://www.springframework.org/schema/" />
|
||||
<option value="http://www.springframework.org/tags" />
|
||||
<option value="http://www.springframework.org/security/tags" />
|
||||
<option value="http://www.thymeleaf.org" />
|
||||
<option value="http://www.jboss.org/j2ee/schema/" />
|
||||
<option value="http://www.jboss.com/xml/ns/" />
|
||||
<option value="http://www.ibm.com/webservices/xsd" />
|
||||
<option value="http://activemq.apache.org/schema/" />
|
||||
<option value="http://schema.cloudfoundry.org/spring/" />
|
||||
<option value="http://schemas.xmlsoap.org/" />
|
||||
<option value="http://cxf.apache.org/schemas/" />
|
||||
<option value="http://primefaces.org/ui" />
|
||||
<option value="http://tiles.apache.org/" />
|
||||
<option value="http://api.grasscutter.io" />
|
||||
<option value="http://api.grasscutters.xyz" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSIgnoredPromiseFromCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
8
.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/cultivation.iml" filepath="$PROJECT_DIR$/.idea/cultivation.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
|
||||
}
|
||||
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"rust-analyzer.linkedProjects": ["src-tauri/Cargo.toml"]
|
||||
}
|
||||
14
index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tauri + React + Typescript</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
75
package.json
@@ -1,67 +1,34 @@
|
||||
{
|
||||
"name": "cultivation",
|
||||
"version": "1.6.1",
|
||||
"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": "^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",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.4.2",
|
||||
"web-vitals": "^2.1.0"
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "cross-env BROWSER=none react-scripts start",
|
||||
"postbuild:windows": "xcopy /E /H /C /I /Y \".\\src-tauri\\lang\" \".\\src-tauri\\target\\release\\lang\"",
|
||||
"postbuild:linux": "cp -r \"./src-tauri/lang\" \"./lang\"",
|
||||
"build:windows": "yarn tauri build",
|
||||
"build:linux": "yarn tauri build",
|
||||
"build": "react-scripts build && run-script-os",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"tauri": "tauri",
|
||||
"start:dev": "tauri dev -- -- --no-admin",
|
||||
"format": "cargo fmt --manifest-path ./src-tauri/Cargo.toml --all && yarn prettier --write --cache --loglevel warn .",
|
||||
"lint": "cargo clippy --manifest-path ./src-tauri/Cargo.toml --no-default-features && yarn tsc --noEmit && yarn eslint src",
|
||||
"lint:fix": "cargo clippy --manifest-path ./src-tauri/Cargo.toml --no-default-features --fix --allow-dirty && yarn tsc --noEmit && yarn eslint --fix src",
|
||||
"prepare": "husky install"
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-dialog": "~2.4.2",
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-shell": "~2.3.3",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"web-vitals": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.0.0-rc.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.22.0",
|
||||
"@typescript-eslint/parser": "^5.22.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.15.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"husky": "^8.0.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"prettier": "^2.7.1",
|
||||
"run-script-os": "^1.1.6"
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Tauri-powered anime game launcher" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>Cultivation</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"short_name": "Cultivation",
|
||||
"name": "Cultivation",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
6
public/tauri.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="206" height="231" viewBox="0 0 206 231" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M143.143 84C143.143 96.1503 133.293 106 121.143 106C108.992 106 99.1426 96.1503 99.1426 84C99.1426 71.8497 108.992 62 121.143 62C133.293 62 143.143 71.8497 143.143 84Z" fill="#FFC131"/>
|
||||
<ellipse cx="84.1426" cy="147" rx="22" ry="22" transform="rotate(180 84.1426 147)" fill="#24C8DB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M166.738 154.548C157.86 160.286 148.023 164.269 137.757 166.341C139.858 160.282 141 153.774 141 147C141 144.543 140.85 142.121 140.558 139.743C144.975 138.204 149.215 136.139 153.183 133.575C162.73 127.404 170.292 118.608 174.961 108.244C179.63 97.8797 181.207 86.3876 179.502 75.1487C177.798 63.9098 172.884 53.4021 165.352 44.8883C157.82 36.3744 147.99 30.2165 137.042 27.1546C126.095 24.0926 114.496 24.2568 103.64 27.6274C92.7839 30.998 83.1319 37.4317 75.8437 46.1553C74.9102 47.2727 74.0206 48.4216 73.176 49.5993C61.9292 50.8488 51.0363 54.0318 40.9629 58.9556C44.2417 48.4586 49.5653 38.6591 56.679 30.1442C67.0505 17.7298 80.7861 8.57426 96.2354 3.77762C111.685 -1.01901 128.19 -1.25267 143.769 3.10474C159.348 7.46215 173.337 16.2252 184.056 28.3411C194.775 40.457 201.767 55.4101 204.193 71.404C206.619 87.3978 204.374 103.752 197.73 118.501C191.086 133.25 180.324 145.767 166.738 154.548ZM41.9631 74.275L62.5557 76.8042C63.0459 72.813 63.9401 68.9018 65.2138 65.1274C57.0465 67.0016 49.2088 70.087 41.9631 74.275Z" fill="#FFC131"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.4045 76.4519C47.3493 70.6709 57.2677 66.6712 67.6171 64.6132C65.2774 70.9669 64 77.8343 64 85.0001C64 87.1434 64.1143 89.26 64.3371 91.3442C60.0093 92.8732 55.8533 94.9092 51.9599 97.4256C42.4128 103.596 34.8505 112.392 30.1816 122.756C25.5126 133.12 23.9357 144.612 25.6403 155.851C27.3449 167.09 32.2584 177.598 39.7906 186.112C47.3227 194.626 57.153 200.784 68.1003 203.846C79.0476 206.907 90.6462 206.743 101.502 203.373C112.359 200.002 122.011 193.568 129.299 184.845C130.237 183.722 131.131 182.567 131.979 181.383C143.235 180.114 154.132 176.91 164.205 171.962C160.929 182.49 155.596 192.319 148.464 200.856C138.092 213.27 124.357 222.426 108.907 227.222C93.458 232.019 76.9524 232.253 61.3736 227.895C45.7948 223.538 31.8055 214.775 21.0867 202.659C10.3679 190.543 3.37557 175.59 0.949823 159.596C-1.47592 143.602 0.768139 127.248 7.41237 112.499C14.0566 97.7497 24.8183 85.2327 38.4045 76.4519ZM163.062 156.711L163.062 156.711C162.954 156.773 162.846 156.835 162.738 156.897C162.846 156.835 162.954 156.773 163.062 156.711Z" fill="#24C8DB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
1
public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
5
src-tauri/.gitignore
vendored
@@ -1,4 +1,7 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
WixTools
|
||||
|
||||
# Generated by Tauri
|
||||
# will have schema files for capabilities auto-completion
|
||||
/gen/schemas
|
||||
|
||||
5818
src-tauri/Cargo.lock
generated
@@ -1,92 +1,71 @@
|
||||
[package]
|
||||
name = "cultivation"
|
||||
version = "1.6.1"
|
||||
version = "2.0.0"
|
||||
description = "A custom launcher for anime game."
|
||||
authors = ["KingRainbow44", "SpikeHD"]
|
||||
license = ""
|
||||
repository = "https://github.com/Grasscutters/Cultivation.git"
|
||||
default-run = "cultivation"
|
||||
authors = ["KingRainbow44", "SpikeHD", "NotThorny"]
|
||||
edition = "2021"
|
||||
rust-version = "1.57"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
# The `_lib` suffix may seem redundant but it is necessary
|
||||
# to make the lib name unique and wouldn't conflict with the bin name.
|
||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||
name = "cultivation_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.0.0-rc.8", features = [] }
|
||||
cc = "1.0"
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
is_elevated = "0.1.2"
|
||||
registry = "1.2.1"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
sudo = "0.6.0"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
anyhow = "1.0.58"
|
||||
os_type = "2.6"
|
||||
term-detect = "0.1.7"
|
||||
which = "4.4"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.0.9", features = ["api-all"] }
|
||||
|
||||
# Arg parsing
|
||||
args = "2.0"
|
||||
getopts = "0.2"
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-opener = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
args = "2.2.0"
|
||||
once_cell = "1.21.3"
|
||||
|
||||
# Access system process info.
|
||||
sysinfo = "0.28.4"
|
||||
|
||||
# ZIP-archive library.
|
||||
zip-extract = "0.1.1"
|
||||
unrar = "0.4.4"
|
||||
zip = "0.6.2"
|
||||
sevenz-rust = "0.2.9"
|
||||
|
||||
# For creating a "global" downloads list.
|
||||
once_cell = "1.13.0"
|
||||
|
||||
# Program opener.
|
||||
open = "3.0.2"
|
||||
futures-util = "0.3.31"
|
||||
file_diff = "1.0.0"
|
||||
tokio = { version = "1.48.0", features = ["signal"] }
|
||||
hudsucker = { version = "0.21", features = ["rcgen-ca", "rustls-client"], default-features = false }
|
||||
dirs = "6.0.0"
|
||||
reqwest = {version = "0.12.24", features = ["stream"] }
|
||||
unrar = "0.5.8"
|
||||
zip = "6.0.0"
|
||||
sevenz-rust = "0.6.1"
|
||||
simple-zip = "1.0.1"
|
||||
ctrlc = "3.5.1"
|
||||
getopts = "0.2.24"
|
||||
open = "5.3.3"
|
||||
rust-ini = "0.21.3"
|
||||
rcgen = { version = "0.9", features = ["x509-parser"] }
|
||||
rustls-pemfile = "1.0.0"
|
||||
tauri-plugin-fs = "2.4.4"
|
||||
tauri-plugin-dialog = "2.4.2"
|
||||
tauri-plugin-shell = "2.3.3"
|
||||
|
||||
# Services
|
||||
windows-service = "0.6.0"
|
||||
|
||||
# Serialization.
|
||||
serde_json = "1"
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
sudo = "0.6.0"
|
||||
|
||||
# Dependencies for the HTTP(S) proxy.
|
||||
http = "0.2"
|
||||
hudsucker = "0.19.2"
|
||||
tracing = "0.1.21"
|
||||
tokio-rustls = "0.23.0"
|
||||
tokio-tungstenite = "0.17.0"
|
||||
tokio = { version = "1.20.4", features = ["signal"] }
|
||||
rustls-pemfile = "1.0.0"
|
||||
reqwest = { version = "0.11.3", features = ["stream"] }
|
||||
futures-util = "0.3.14"
|
||||
rcgen = { version = "0.9", features = ["x509-parser"] }
|
||||
|
||||
# metadata stuff
|
||||
regex = "1"
|
||||
|
||||
# other
|
||||
file_diff = "1.0.0"
|
||||
rust-ini = "0.18.0"
|
||||
ctrlc = "3.2.3"
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
term-detect = "0.1.8"
|
||||
anyhow = "1.0.100"
|
||||
which = "8.0.0"
|
||||
os_type = "2.6"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies.anime-launcher-sdk]
|
||||
git = "https://github.com/an-anime-team/anime-launcher-sdk.git"
|
||||
tag = "1.11.1"
|
||||
tag = "1.32.0"
|
||||
default-features = false
|
||||
features = ["all", "genshin"]
|
||||
|
||||
[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
|
||||
default = [ "custom-protocol" ]
|
||||
# this feature is used used for production builds where `devPath` points to the filesystem
|
||||
# DO NOT remove this
|
||||
custom-protocol = [ "tauri/custom-protocol" ]
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
fn main() {
|
||||
cc::Build::new()
|
||||
.include("mhycrypto")
|
||||
.cpp(true)
|
||||
.file("mhycrypto/memecrypto.cpp")
|
||||
.file("mhycrypto/metadata.cpp")
|
||||
.file("mhycrypto/metadatastringdec.cpp")
|
||||
.compile("mhycrypto");
|
||||
|
||||
cc::Build::new()
|
||||
.include("mhycrypto")
|
||||
.file("mhycrypto/aes.c")
|
||||
.compile("mhycrypto-aes");
|
||||
|
||||
tauri_build::build()
|
||||
tauri_build::build()
|
||||
}
|
||||
|
||||
33
src-tauri/capabilities/default.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default",
|
||||
"fs:allow-read",
|
||||
"fs:allow-write",
|
||||
"fs:allow-app-write",
|
||||
"fs:allow-app-read",
|
||||
"fs:allow-app-write-recursive",
|
||||
"fs:allow-app-read-recursive",
|
||||
"fs:allow-appcache-write",
|
||||
"fs:allow-appcache-read",
|
||||
{
|
||||
"identifier": "fs:scope",
|
||||
"allow": ["$DATA", "$DATA/cultivation", "$DATA/cultivation/**"]
|
||||
},
|
||||
"shell:allow-execute",
|
||||
"shell:allow-open",
|
||||
"dialog:allow-open",
|
||||
"dialog:allow-save",
|
||||
"dialog:allow-message",
|
||||
"dialog:allow-ask",
|
||||
"dialog:allow-confirm",
|
||||
"core:app:allow-app-show",
|
||||
"core:app:allow-app-hide",
|
||||
"fs:default",
|
||||
"dialog:default"
|
||||
]
|
||||
}
|
||||
@@ -37,11 +37,13 @@
|
||||
"web_cache": "删除 webCaches 文件夹",
|
||||
"launch_args": "启动参数",
|
||||
"offline_mode": "离线模式",
|
||||
"fix_res": "修复登录超时"
|
||||
"fix_res": "修复登录超时",
|
||||
"show_version": "在按钮上显示游戏版本",
|
||||
"save_profile": "保存配置配置文件"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "下载 Grasscutter 一体化",
|
||||
"grasscutter_fullquest": "下载 6.0 一体化",
|
||||
"grasscutter_fullquest": "下载 6.1 一体化",
|
||||
"grasscutter_stable_data": "下载 Grasscutter 稳定版数据",
|
||||
"grasscutter_latest_data": "下载 Grasscutter 开发版数据",
|
||||
"grasscutter_stable_data_update": "更新 Grasscutter 稳定版数据",
|
||||
|
||||
@@ -37,11 +37,13 @@
|
||||
"web_cache": "刪除 webCaches 文件夾",
|
||||
"launch_args": "啟動參數",
|
||||
"offline_mode": "離線模式",
|
||||
"fix_res": "修復登入逾時"
|
||||
"fix_res": "修復登入逾時",
|
||||
"show_version": "在按鈕上顯示遊戲版本",
|
||||
"save_profile": "儲存配置設定檔"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "下載Grasscutter多合一下載",
|
||||
"grasscutter_fullquest": "下载 6.0 一体化",
|
||||
"grasscutter_fullquest": "下载 6.1 一体化",
|
||||
"grasscutter_stable_data": "下載Grasscutter穩定版數據(Data)",
|
||||
"grasscutter_latest_data": "下載Grasscutter開發板數據(Data)",
|
||||
"grasscutter_stable_data_update": "更新Grasscutter穩定版數據(Data)",
|
||||
|
||||
@@ -39,11 +39,13 @@
|
||||
"web_cache": "WebCaches-Ordner löschen",
|
||||
"launch_args": "Start-Argumente",
|
||||
"offline_mode": "Offline-Modus",
|
||||
"fix_res": "Login-Zeitüberschreitung beheben"
|
||||
"fix_res": "Login-Zeitüberschreitung beheben",
|
||||
"show_version": "Spielversion in Schaltflächen anzeigen",
|
||||
"save_profile": "Profil speichern"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Grasscutter All-in-One herunterladen",
|
||||
"grasscutter_fullquest": "6.0 All-in-One herunterladen",
|
||||
"grasscutter_fullquest": "6.1 All-in-One herunterladen",
|
||||
"grasscutter_stable_data": "Stabile Grasscutter-Daten herunterladen",
|
||||
"grasscutter_latest_data": "Neueste Grasscutter-Daten herunterladen",
|
||||
"grasscutter_stable_data_update": "Stabile Grasscutter-Daten aktualisieren",
|
||||
|
||||
@@ -39,11 +39,13 @@
|
||||
"web_cache": "Delete webCaches folder",
|
||||
"launch_args": "Launch Args",
|
||||
"offline_mode": "Offline Mode",
|
||||
"fix_res": "Fix Login Timeout"
|
||||
"fix_res": "Fix Login Timeout",
|
||||
"show_version": "Show game version in buttons",
|
||||
"save_profile": "Save profile"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Download Grasscutter 4.0 All-in-One",
|
||||
"grasscutter_fullquest": "Download 6.0 All-in-One",
|
||||
"grasscutter_fullquest": "Download 6.1 All-in-One",
|
||||
"grasscutter_stable_data": "Download Grasscutter Stable Data",
|
||||
"grasscutter_latest_data": "Download Grasscutter Latest Data",
|
||||
"grasscutter_stable_data_update": "Update Grasscutter Stable Data",
|
||||
|
||||
@@ -37,11 +37,13 @@
|
||||
"web_cache": "Eliminar la carpeta webCaches",
|
||||
"launch_args": "Args de lanzamiento",
|
||||
"offline_mode": "Modo sin conexión",
|
||||
"fix_res": "Reparar el tiempo de espera"
|
||||
"fix_res": "Reparar el tiempo de espera",
|
||||
"show_version": "Mostrar versión del juego en botones",
|
||||
"save_profile": "Guardar perfil"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Descargar datos todo en uno de Grasscutter",
|
||||
"grasscutter_fullquest": "Descargar datos todo en uno de 6.0",
|
||||
"grasscutter_fullquest": "Descargar datos todo en uno de 6.1",
|
||||
"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",
|
||||
|
||||
@@ -37,11 +37,13 @@
|
||||
"web_cache": "Supprimer le dossier webCaches",
|
||||
"launch_args": "Arguments de lancement",
|
||||
"offline_mode": "Mode hors ligne",
|
||||
"fix_res": "Réparation du login"
|
||||
"fix_res": "Réparation du login",
|
||||
"show_version": "Afficher la version du jeu sur les boutons",
|
||||
"save_profile": "Enregistrer le profil"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Telecharger Grasscutter tout-en-un",
|
||||
"grasscutter_fullquest": "Télécharger les 6.0 tout-en-un",
|
||||
"grasscutter_fullquest": "Télécharger les 6.1 tout-en-un",
|
||||
"grasscutter_stable_data": "Télécharger les donnees de Grasscutter (version stable)",
|
||||
"grasscutter_latest_data": "Télécharger les donnees de Grasscutter (derniere version)",
|
||||
"grasscutter_stable_data_update": "Mettre à jour les données de Grasscutter (version stable)",
|
||||
|
||||
@@ -36,11 +36,13 @@
|
||||
"web_cache": "Hapus folder webCaches",
|
||||
"launch_args": "Luncurkan Args",
|
||||
"offline_mode": "Mode Offline",
|
||||
"fix_res": "Perbaiki batas waktu login"
|
||||
"fix_res": "Perbaiki batas waktu login",
|
||||
"show_version": "Tampilkan versi game pada tombol",
|
||||
"save_profile": "Simpan profil"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Sedang Mendownload Grasscutter Semua Dalam Satu",
|
||||
"grasscutter_fullquest": "Unduh 6.0 semua dalam satu",
|
||||
"grasscutter_fullquest": "Unduh 6.1 semua dalam satu",
|
||||
"grasscutter_stable_data": "Sedang Mendownload Grasscutter Versi Stabil",
|
||||
"grasscutter_latest_data": "Sedang Mendownload Grasscutter Data Terbaru",
|
||||
"grasscutter_stable_data_update": "Memperbaharui Grasscutter Data Stabil",
|
||||
|
||||
@@ -37,11 +37,13 @@
|
||||
"web_cache": "Elimina la cartella webCaches",
|
||||
"launch_args": "Argomenti di lancio",
|
||||
"offline_mode": "Modalità Offline",
|
||||
"fix_res": "Correggere il timeout dell'accesso"
|
||||
"fix_res": "Correggere il timeout dell'accesso",
|
||||
"show_version": "Mostra la versione del gioco sui pulsanti",
|
||||
"save_profile": "Salva il profilo"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Scarica Grasscutter Tutto-in-Uno",
|
||||
"grasscutter_fullquest": "Scarica 6.0 Tutto-in-Uno",
|
||||
"grasscutter_fullquest": "Scarica 6.1 Tutto-in-Uno",
|
||||
"grasscutter_stable_data": "Scarica i dati di Grasscutter Stabili",
|
||||
"grasscutter_latest_data": "Scarica i dati di Grasscutter Più Recenti",
|
||||
"grasscutter_stable_data_update": "Aggiorna i dati di Grasscutter Stabili",
|
||||
|
||||
@@ -37,11 +37,13 @@
|
||||
"check_aagl": "その他のオプションは、他のランチャーをチェックしてください",
|
||||
"grasscutter_elevation": "制限されたポートでのGCの実行方法",
|
||||
"web_cache": "webCachesフォルダを削除",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "Launch Args",
|
||||
"show_version": "ボタンにゲームバージョンを表示",
|
||||
"save_profile": "プロフィールを保存"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Grasscutter All-in-Oneをダウンロード",
|
||||
"grasscutter_fullquest": "6.0 All-in-Oneをダウンロード",
|
||||
"grasscutter_fullquest": "6.1 All-in-Oneをダウンロード",
|
||||
"grasscutter_stable_data": "Grasscutter安定版データファイルをダウンロード",
|
||||
"grasscutter_latest_data": "Grasscutter最新版データファイルをダウンロード",
|
||||
"grasscutter_stable_data_update": "Grasscutter安定版データファイルをアップデート",
|
||||
|
||||
@@ -37,11 +37,13 @@
|
||||
"web_cache": "webCaches 폴더 삭제",
|
||||
"launch_args": "실행 인수",
|
||||
"offline_mode": "오프라인 모드",
|
||||
"fix_res": "로그인 시간 초과 수정"
|
||||
"fix_res": "로그인 시간 초과 수정",
|
||||
"show_version": "버튼에 게임 버전 표시",
|
||||
"save_profile": "프로필 저장"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "올인원 Grasscutter 다운로드",
|
||||
"grasscutter_fullquest": "6.0 올인원 다운로드",
|
||||
"grasscutter_fullquest": "6.1 올인원 다운로드",
|
||||
"grasscutter_stable_data": "안정적인 데이터 다운로드",
|
||||
"grasscutter_latest_data": "최신 데이터 다운로드",
|
||||
"grasscutter_stable_data_update": "안정적 데이터 업데이트",
|
||||
|
||||
@@ -35,11 +35,13 @@
|
||||
"web_cache": "Dzēsiet mapi WebCaches",
|
||||
"launch_args": "Palaišanas args",
|
||||
"offline_mode": "Bezsaistes režīms",
|
||||
"fix_res": "Fiksēt pieteikšanās laika"
|
||||
"fix_res": "Fiksēt pieteikšanās laika",
|
||||
"show_version": "Pogās redzamā spēles versija",
|
||||
"save_profile": "Saglabāt profilu"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Lejupielādējiet Grasscutter viss vienā",
|
||||
"grasscutter_fullquest": "Lejupielādēt 6.0 viss vienā",
|
||||
"grasscutter_fullquest": "Lejupielādēt 6.1 viss vienā",
|
||||
"grasscutter_stable_data": "Lejupielādējiet Grasscutter stabilos datus",
|
||||
"grasscutter_latest_data": "Lejupielādējiet Grasscutter jaunākos datus",
|
||||
"grasscutter_stable_data_update": "Atjauniniet Grasscutter stabilos datus",
|
||||
|
||||
@@ -36,11 +36,13 @@
|
||||
"web_cache": "Verwijder de webCaches-map",
|
||||
"launch_args": "Args starten",
|
||||
"offline_mode": "Offline Modus",
|
||||
"fix_res": "Time-out inloggen verhelpen"
|
||||
"fix_res": "Time-out inloggen verhelpen",
|
||||
"show_version": "Spelversie weergegeven op knoppen",
|
||||
"save_profile": "Profiel opslaan"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Grasscutter Alles-in-één Downloaden",
|
||||
"grasscutter_fullquest": "Alles-in-één 6.0 downloaden",
|
||||
"grasscutter_fullquest": "Alles-in-één 6.1 downloaden",
|
||||
"grasscutter_stable_data": "Download Stabiele Gegevens Van Grasscutter",
|
||||
"grasscutter_latest_data": "Download De Nieuwste Gegevens Van Grasscutter",
|
||||
"grasscutter_stable_data_update": "Stabiele gegevens Van Grasscutter bijwerken",
|
||||
|
||||
@@ -39,11 +39,13 @@
|
||||
"web_cache": "Usuń folder webCaches",
|
||||
"launch_args": "Argumenty uruchamiania",
|
||||
"offline_mode": "Tryb offline",
|
||||
"fix_res": "Napraw limit czasu logowania"
|
||||
"fix_res": "Napraw limit czasu logowania",
|
||||
"show_version": "Wersja gry wyświetlana na przyciskach",
|
||||
"save_profile": "Zapisz profil"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Pobierz Grasscutter (wszystko w jednym)",
|
||||
"grasscutter_fullquest": "Pobierz 6.0 (wszystko w jednym)",
|
||||
"grasscutter_fullquest": "Pobierz 6.1 (wszystko w jednym)",
|
||||
"grasscutter_stable_data": "Pobierz stabilne dane Grasscuttera",
|
||||
"grasscutter_latest_data": "Pobierz najnowsze dane Grasscuttera",
|
||||
"grasscutter_stable_data_update": "Zaaktualizuj stabilne dane Grasscuttera",
|
||||
|
||||
@@ -37,11 +37,13 @@
|
||||
"web_cache": "Excluir pasta webCaches",
|
||||
"launch_args": "Argumentos de lançamento",
|
||||
"offline_mode": "Modo offline",
|
||||
"fix_res": "Corrigir o tempo limite de login"
|
||||
"fix_res": "Corrigir o tempo limite de login",
|
||||
"show_version": "Versão do jogo exibida nos botões",
|
||||
"save_profile": "Salvar perfil"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Baixar o Grasscutter Tudo-em-Um",
|
||||
"grasscutter_fullquest": "Baixar de 6.0 em um só lugar",
|
||||
"grasscutter_fullquest": "Baixar de 6.1 em um só lugar",
|
||||
"grasscutter_stable_data": "Baixar os Dados do Grasscutter Estável",
|
||||
"grasscutter_latest_data": "Baixar os Dados do Grasscutter Mais Recente",
|
||||
"grasscutter_stable_data_update": "Atualizar os Dados do Grasscutter Estável",
|
||||
|
||||
@@ -36,11 +36,13 @@
|
||||
"web_cache": "Удалить папку webCaches",
|
||||
"launch_args": "Параметры запуска",
|
||||
"offline_mode": "Автономный режим",
|
||||
"fix_res": "Исправить таймаут входа в систему"
|
||||
"fix_res": "Исправить таймаут входа в систему",
|
||||
"show_version": "Версия игры отображается на кнопках",
|
||||
"save_profile": "Сохранить профиль"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Скачать все в одном Grasscutter",
|
||||
"grasscutter_fullquest": "Скачать 6.0 все в одном",
|
||||
"grasscutter_fullquest": "Скачать 6.1 все в одном",
|
||||
"grasscutter_stable_data": "Скачать стабильные данные Grasscutter",
|
||||
"grasscutter_latest_data": "Скачать последние данные Grasscutter",
|
||||
"grasscutter_stable_data_update": "Обновить стабильные данные Grasscutter",
|
||||
|
||||
@@ -37,11 +37,13 @@
|
||||
"web_cache": "Xóa thư mục webCaches",
|
||||
"launch_args": "Khởi chạy đối số",
|
||||
"offline_mode": "Chế độ ngoại tuyến",
|
||||
"fix_res": "Sửa lỗi hết thời gian đăng nhập"
|
||||
"fix_res": "Sửa lỗi hết thời gian đăng nhập",
|
||||
"show_version": "Hiển thị phiên bản trò chơi trên các nút",
|
||||
"save_profile": "Lưu hồ sơ"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Tải Grasscutter tất cả trong một",
|
||||
"grasscutter_fullquest": "Tải 6.0 tất cả trong một",
|
||||
"grasscutter_fullquest": "Tải 6.1 tất cả trong một",
|
||||
"grasscutter_stable_data": "Tải dữ liệu Grasscutter bản ổn định",
|
||||
"grasscutter_latest_data": "Tải dữ liệu Grasscutter bản mới nhất",
|
||||
"grasscutter_stable_data_update": "Cập nhật dữ liệu Grasscutter bản ổn định",
|
||||
|
||||
@@ -1,387 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* \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
|
||||
@@ -1,31 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#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
|
||||
@@ -1,146 +0,0 @@
|
||||
#include "metadata.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "memecrypto.h"
|
||||
#include "metadatastringdec.h"
|
||||
|
||||
unsigned char initial_prev_xor[] = { 0xad, 0x2f, 0x42, 0x30, 0x67, 0x04, 0xb0, 0x9c, 0x9d, 0x2a, 0xc0, 0xba, 0x0e, 0xbf, 0xa5, 0x68 };
|
||||
|
||||
bool get_global_metadata_keys(uint8_t *src, size_t srcn, uint8_t *longkey, uint8_t *shortkey) {
|
||||
if (srcn != 0x4000)
|
||||
return false;
|
||||
|
||||
if (*(uint16_t *) (src + 0xc8) != 0xfc2e || *(uint16_t *) (src + 0xca) != 0x2cfe)
|
||||
return true;
|
||||
|
||||
auto offB00 = *(uint16_t *) (src + 0xd2);
|
||||
|
||||
for (size_t i = 0; i < 16; i++)
|
||||
shortkey[i] = src[offB00 + i] ^ src[0x3000 + i];
|
||||
|
||||
for (size_t i = 0; i < 0xb00; i++)
|
||||
longkey[i] = src[offB00 + 0x10 + i] ^ src[0x3000 + 0x10 + i] ^ shortkey[i % 16];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gen_global_metadata_key(uint8_t* src, size_t srcn) {
|
||||
if (srcn != 0x4000)
|
||||
return false;
|
||||
|
||||
#if 0
|
||||
std::vector<uint8_t> read_file(const char* n);
|
||||
auto data = read_file("xorpad.bin");
|
||||
memcpy(src, data.data(), 0x4000);
|
||||
|
||||
return false;
|
||||
#endif
|
||||
|
||||
std::mt19937_64 rand (0xDEADBEEF);
|
||||
|
||||
uint64_t* key = (uint64_t*)src;
|
||||
|
||||
for (size_t i = 0; i < srcn / sizeof(uint64_t); i++)
|
||||
key[i] = rand();
|
||||
|
||||
*(uint16_t *) (src + 0xc8) = 0xfc2e; // Magic
|
||||
*(uint16_t *) (src + 0xca) = 0x2cfe; // Magic
|
||||
*(uint16_t *) (src + 0xd2) = rand() & 0x1FFFu; // Just some random value
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void decrypt_global_metadata_inner(uint8_t *data, size_t size) {
|
||||
uint8_t longkey[0xB00];
|
||||
uint8_t longkeyp[0xB0];
|
||||
uint8_t shortkey[16];
|
||||
get_global_metadata_keys(data + size - 0x4000, 0x4000, longkey, shortkey);
|
||||
for (int i = 0; i < 16; i++)
|
||||
shortkey[i] ^= initial_prev_xor[i];
|
||||
memecrypto_prepare_key(longkey, longkeyp);
|
||||
|
||||
auto perentry = (uint32_t) (size / 0x100 / 0x40);
|
||||
for (int i = 0; i < 0x100; i++) {
|
||||
auto off = (0x40u * perentry) * i;
|
||||
|
||||
uint8_t prev[16];
|
||||
memcpy(prev, shortkey, 16);
|
||||
for (int j = 0; j < 4; j++) {
|
||||
uint8_t curr[16];
|
||||
memcpy(curr, &data[off + j * 0x10], 16);
|
||||
|
||||
memecrypto_decrypt(longkeyp, curr);
|
||||
|
||||
for (int k = 0; k < 16; k++)
|
||||
curr[k] ^= prev[k];
|
||||
|
||||
memcpy(prev, &data[off + j * 0x10], 16);
|
||||
memcpy(&data[off + j * 0x10], curr, 16);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t literal_dec_key[0x5000];
|
||||
recrypt_global_metadata_header_string_fields(data, size, literal_dec_key);
|
||||
recrypt_global_metadata_header_string_literals(data, size, literal_dec_key);
|
||||
}
|
||||
|
||||
extern "C" int decrypt_global_metadata(uint8_t *data, size_t size) {
|
||||
try {
|
||||
decrypt_global_metadata_inner(data, size);
|
||||
return 0;
|
||||
} catch (...) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void encrypt_global_metadata_inner(uint8_t* data, size_t size) {
|
||||
uint8_t literal_dec_key[0x5000];
|
||||
|
||||
gen_global_metadata_key(data + size - 0x4000, 0x4000);
|
||||
|
||||
generate_key_for_global_metadata_header_string(data, size, literal_dec_key);
|
||||
|
||||
recrypt_global_metadata_header_string_literals(data, size, literal_dec_key);
|
||||
recrypt_global_metadata_header_string_fields(data, size, literal_dec_key);
|
||||
|
||||
uint8_t longkey[0xB00];
|
||||
uint8_t longkeyp[0xB0];
|
||||
uint8_t shortkey[16];
|
||||
|
||||
get_global_metadata_keys(data + size - 0x4000, 0x4000, longkey, shortkey);
|
||||
for (int i = 0; i < 16; i++)
|
||||
shortkey[i] ^= initial_prev_xor[i];
|
||||
memecrypto_prepare_key(longkey, longkeyp);
|
||||
|
||||
auto perentry = (uint32_t) (size / 0x100 / 0x40);
|
||||
for (int i = 0; i < 0x100; i++) {
|
||||
auto off = (0x40u * perentry) * i;
|
||||
|
||||
uint8_t prev[16];
|
||||
memcpy(prev, shortkey, 16);
|
||||
for (int j = 0; j < 4; j++) {
|
||||
uint8_t curr[16];
|
||||
memcpy(curr, &data[off + j * 0x10], 16);
|
||||
|
||||
for (int k = 0; k < 16; k++)
|
||||
curr[k] ^= prev[k];
|
||||
|
||||
memecrypto_encrypt(longkeyp, curr);
|
||||
|
||||
memcpy(prev, curr, 16);
|
||||
memcpy(&data[off + j * 0x10], curr, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int encrypt_global_metadata(uint8_t* data, size_t size) {
|
||||
try {
|
||||
encrypt_global_metadata_inner(data, size);
|
||||
return 0;
|
||||
} catch (...) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#ifndef METADATA_H
|
||||
#define METADATA_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
extern "C" int decrypt_global_metadata(uint8_t *data, size_t size);
|
||||
extern "C" int encrypt_global_metadata(uint8_t *data, size_t size);
|
||||
|
||||
#endif //METADATA_H
|
||||
@@ -1,121 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#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
|
||||
@@ -1,4 +0,0 @@
|
||||
newline_style = "Unix"
|
||||
tab_spaces = 2
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
||||
@@ -30,20 +30,33 @@ pub struct Configuration {
|
||||
pub redirect_more: Option<bool>,
|
||||
pub launch_args: Option<String>,
|
||||
pub offline_mode: Option<bool>,
|
||||
pub show_version: Option<bool>,
|
||||
pub profile: Option<String>,
|
||||
}
|
||||
|
||||
pub fn config_path() -> PathBuf {
|
||||
let mut path = tauri::api::path::data_dir().unwrap();
|
||||
pub fn config_path(profile: String) -> PathBuf {
|
||||
let mut path = dirs::data_dir().unwrap();
|
||||
path.push("cultivation");
|
||||
path.push("configuration.json");
|
||||
if profile.as_str() == "default" {
|
||||
path.push("configuration.json");
|
||||
} else {
|
||||
path.push("profiles");
|
||||
path.push(profile + ".json");
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
pub fn get_config() -> Configuration {
|
||||
let path = config_path();
|
||||
pub fn get_config(profile_name: String) -> Configuration {
|
||||
let path = config_path(profile_name.clone());
|
||||
let config = std::fs::read_to_string(path).unwrap_or("{}".to_string());
|
||||
let config: Configuration = serde_json::from_str(&config).unwrap_or_default();
|
||||
|
||||
//let default = String::from("default");
|
||||
let prof = config.profile.clone().unwrap_or_default();
|
||||
if prof != String::from("default") && prof != profile_name.clone() {
|
||||
return get_config(prof.clone());
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use tauri::Emitter;
|
||||
|
||||
use std::cmp::min;
|
||||
use std::fs::File;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use tauri::Emitter;
|
||||
|
||||
use crate::system_helpers::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
||||
541
src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,541 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use args::{Args, ArgsError};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use proxy::set_proxy_addr;
|
||||
use tauri::{Listener, Emitter};
|
||||
use std::io::Write;
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
use tauri::async_runtime::block_on;
|
||||
|
||||
use std::thread;
|
||||
use sysinfo::{Pid, ProcessExt, System, SystemExt};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use crate::admin::reopen_as_admin;
|
||||
#[cfg(target_os = "windows")]
|
||||
use system_helpers::is_elevated;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::{
|
||||
thread::{sleep, JoinHandle},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
mod admin;
|
||||
mod config;
|
||||
mod downloader;
|
||||
mod file_helpers;
|
||||
mod gamebanana;
|
||||
mod lang;
|
||||
mod patch;
|
||||
mod proxy;
|
||||
mod release;
|
||||
mod system_helpers;
|
||||
mod unzip;
|
||||
mod web;
|
||||
|
||||
static WATCH_GAME_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
|
||||
static WATCH_GRASSCUTTER_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
|
||||
static GC_PID: std::sync::Mutex<usize> = Mutex::new(696969);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub static AAGL_THREAD: Lazy<Mutex<Option<JoinHandle<()>>>> = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
fn try_flush() {
|
||||
std::io::stdout().flush().unwrap_or(())
|
||||
}
|
||||
|
||||
async fn parse_args(inp: &Vec<String>) -> Result<Args, ArgsError> {
|
||||
let mut args = Args::new(
|
||||
"Cultivation",
|
||||
"Private server helper program for an Anime Game",
|
||||
);
|
||||
args.flag("h", "help", "Print various CLI args");
|
||||
args.flag("p", "proxy", "Start the proxy server");
|
||||
args.flag("G", "launch-game", "Launch the game");
|
||||
args.flag("o", "other-redirects", "Redirect other certain anime games");
|
||||
args.flag(
|
||||
"A",
|
||||
"no-admin",
|
||||
"Launch without requiring admin permissions",
|
||||
);
|
||||
args.flag(
|
||||
"g",
|
||||
"no-gui",
|
||||
"Run in CLI mode. Requires -A to be passed as well.",
|
||||
);
|
||||
args.flag("s", "server", "Launch the configured GC server");
|
||||
args.flag(
|
||||
"P",
|
||||
"patch",
|
||||
"Patch your game before launching, with whatever your game version needs",
|
||||
);
|
||||
args.flag(
|
||||
"N",
|
||||
"non-elevated-game",
|
||||
"Launch the game without admin permissions",
|
||||
);
|
||||
args.option(
|
||||
"H",
|
||||
"host",
|
||||
"Set host to connect to (eg. 'localhost:443' or 'my.awesomeserver.com:6969)",
|
||||
"SERVER_HOST",
|
||||
getopts::Occur::Optional,
|
||||
None,
|
||||
);
|
||||
args.option(
|
||||
"a",
|
||||
"game-args",
|
||||
"Arguments to pass to the game process, if launching it",
|
||||
r#""-opt-one -opt-two""#,
|
||||
getopts::Occur::Optional,
|
||||
None,
|
||||
);
|
||||
|
||||
args.parse(inp).unwrap();
|
||||
|
||||
let config = config::get_config(String::from("default"));
|
||||
|
||||
if args.value_of("help")? {
|
||||
println!("{}", args.full_usage());
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
// Patch if needed
|
||||
if args.value_of("patch")? {
|
||||
patch::patch_game(false, 0.to_string()).await;
|
||||
}
|
||||
|
||||
if args.value_of("launch-game")? {
|
||||
let game_path = config.game_install_path;
|
||||
let game_args: String = args.value_of("game-args").unwrap_or_default();
|
||||
|
||||
if game_path.is_some() {
|
||||
if args.value_of("non-elevated-game")? {
|
||||
system_helpers::run_un_elevated(game_path.unwrap(), Some(game_args))
|
||||
} else {
|
||||
system_helpers::run_program(game_path.unwrap(), Some(game_args))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if args.value_of("server")? && config.grasscutter_path.is_some() && config.java_path.is_some() {
|
||||
let server_jar = config.grasscutter_path.unwrap();
|
||||
let mut server_path = server_jar.clone();
|
||||
// Strip jar name from path
|
||||
if server_path.contains('/') {
|
||||
// Can never panic because of if
|
||||
let len = server_jar.rfind('/').unwrap();
|
||||
server_path.truncate(len);
|
||||
} else if server_path.contains('\\') {
|
||||
let len = server_jar.rfind('\\').unwrap();
|
||||
server_path.truncate(len);
|
||||
}
|
||||
let java_path = config.java_path.unwrap();
|
||||
|
||||
system_helpers::run_jar(server_jar, server_path.to_string(), java_path);
|
||||
}
|
||||
|
||||
if args.value_of::<String>("host").is_ok() && !args.value_of::<String>("host")?.is_empty() {
|
||||
let host = args.value_of::<String>("host")?;
|
||||
set_proxy_addr(host);
|
||||
}
|
||||
|
||||
if args.value_of("proxy")? {
|
||||
println!("Starting proxy server...");
|
||||
let mut pathbuf = dirs::config_dir().unwrap();
|
||||
pathbuf.push("cultivation");
|
||||
pathbuf.push("ca");
|
||||
|
||||
if args.value_of("other-redirects")? {
|
||||
// proxy::set_redirect_more(); // Unused
|
||||
}
|
||||
|
||||
connect(8035, pathbuf.to_str().unwrap().to_string()).await;
|
||||
}
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let parsed_args = block_on(parse_args(&args)).unwrap();
|
||||
#[cfg(target_os = "windows")]
|
||||
if !is_elevated() && !parsed_args.has_value("no-admin") {
|
||||
println!("===============================================================================");
|
||||
println!("You running as a non-elevated user. Some stuff will almost definitely not work.");
|
||||
println!("===============================================================================");
|
||||
|
||||
reopen_as_admin();
|
||||
}
|
||||
let mut exe_path = std::env::current_exe().unwrap();
|
||||
exe_path.pop();
|
||||
std::env::set_current_dir(&exe_path).unwrap();
|
||||
ctrlc::set_handler(|| {
|
||||
disconnect();
|
||||
block_on(patch::unpatch_game());
|
||||
std::process::exit(0);
|
||||
})
|
||||
.unwrap_or(());
|
||||
if parsed_args.has_value("no-gui") {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
enable_process_watcher,
|
||||
enable_grasscutter_watcher,
|
||||
connect,
|
||||
disconnect,
|
||||
req_get,
|
||||
is_game_running,
|
||||
is_grasscutter_running,
|
||||
restart_grasscutter,
|
||||
get_theme_list,
|
||||
get_profile_list,
|
||||
system_helpers::run_command,
|
||||
system_helpers::run_program,
|
||||
system_helpers::run_program_args,
|
||||
system_helpers::run_program_relative,
|
||||
system_helpers::start_service,
|
||||
system_helpers::service_status,
|
||||
system_helpers::stop_service,
|
||||
system_helpers::run_jar,
|
||||
system_helpers::run_jar_root,
|
||||
system_helpers::open_in_browser,
|
||||
system_helpers::install_location,
|
||||
system_helpers::is_elevated,
|
||||
system_helpers::set_migoto_delay,
|
||||
system_helpers::wipe_registry,
|
||||
system_helpers::get_platform,
|
||||
system_helpers::run_un_elevated,
|
||||
system_helpers::jvm_add_cap,
|
||||
system_helpers::jvm_remove_cap,
|
||||
patch::patch_game,
|
||||
patch::unpatch_game,
|
||||
proxy::set_proxy_addr,
|
||||
proxy::generate_ca_files,
|
||||
release::get_latest_release,
|
||||
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,
|
||||
file_helpers::does_file_exist,
|
||||
downloader::download_file,
|
||||
downloader::stop_download,
|
||||
lang::get_lang,
|
||||
lang::get_languages,
|
||||
web::valid_url,
|
||||
web::web_get,
|
||||
gamebanana::get_download_links,
|
||||
gamebanana::list_submissions,
|
||||
gamebanana::list_mods
|
||||
])
|
||||
.on_window_event(|_window, event| {
|
||||
if let tauri::WindowEvent::CloseRequested { .. } = event {
|
||||
// Ensure all proxy stuff is handled
|
||||
disconnect();
|
||||
}
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
} else {
|
||||
try_flush();
|
||||
println!("Press enter or CTRL-C twice to quit...");
|
||||
std::io::stdin().read_line(&mut String::new()).unwrap();
|
||||
}
|
||||
disconnect();
|
||||
block_on(patch::unpatch_game());
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn is_game_running() -> bool {
|
||||
// Grab the game process name
|
||||
let proc = WATCH_GAME_PROCESS.lock().unwrap().to_string();
|
||||
|
||||
!proc.is_empty()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[tauri::command]
|
||||
fn enable_process_watcher(window: tauri::Window, process: String) {
|
||||
*WATCH_GAME_PROCESS.lock().unwrap() = process;
|
||||
|
||||
window.listen("disable_process_watcher", |_e| {
|
||||
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
|
||||
});
|
||||
|
||||
println!("Starting process watcher...");
|
||||
|
||||
thread::spawn(move || {
|
||||
// Initial sleep for 8 seconds, since running 20 different injectors or whatever can take a while
|
||||
std::thread::sleep(std::time::Duration::from_secs(60));
|
||||
|
||||
let mut system = System::new_all();
|
||||
|
||||
loop {
|
||||
// Shorten loop timer to avoid user closing Cultivation before unpatching/proxy disconnecting
|
||||
thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
// Refresh system info
|
||||
system.refresh_all();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The library takes care of it
|
||||
#[cfg(target_os = "linux")]
|
||||
#[tauri::command]
|
||||
fn enable_process_watcher(window: tauri::Window, process: String) {
|
||||
drop(process);
|
||||
thread::spawn(move || {
|
||||
let end_time = Instant::now() + Duration::from_secs(90);
|
||||
let game_thread = loop {
|
||||
let mut lock = AAGL_THREAD.lock().unwrap();
|
||||
if lock.is_some() {
|
||||
break lock.take().unwrap();
|
||||
}
|
||||
drop(lock);
|
||||
if end_time < Instant::now() {
|
||||
// If more than 60 seconds pass something has gone wrong
|
||||
println!("Waiting for game thread timed out");
|
||||
return;
|
||||
}
|
||||
// Otherwhise wait in order to not use too many CPU cycles
|
||||
sleep(Duration::from_millis(128));
|
||||
};
|
||||
game_thread.join().unwrap();
|
||||
println!("Game closed");
|
||||
|
||||
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
|
||||
disconnect();
|
||||
|
||||
window.emit("game_closed", &()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[tauri::command]
|
||||
fn enable_process_watcher(window: tauri::Window, process: String) {}
|
||||
|
||||
#[tauri::command]
|
||||
fn is_grasscutter_running() -> bool {
|
||||
// Grab the grasscutter process name
|
||||
let proc = WATCH_GRASSCUTTER_PROCESS.lock().unwrap().to_string();
|
||||
|
||||
!proc.is_empty()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tauri::command]
|
||||
fn restart_grasscutter(window: tauri::Window) -> bool {
|
||||
let pid: usize = *GC_PID.lock().unwrap();
|
||||
let system = System::new_all();
|
||||
// Get the process
|
||||
if let Some(process) = system.process(Pid::from(pid)) {
|
||||
// Kill it
|
||||
if process.kill() {
|
||||
// Also kill the cmd it was open in
|
||||
if let Some(parent) = system.process(process.parent().unwrap()) {
|
||||
parent.kill();
|
||||
}
|
||||
for process_gc in system.processes_by_name("java") {
|
||||
if process_gc.cmd().last().unwrap().contains("grasscutter") {
|
||||
process_gc.kill();
|
||||
}
|
||||
}
|
||||
window.emit("disable_grasscutter_watcher", &()).unwrap();
|
||||
thread::sleep(std::time::Duration::from_secs(2));
|
||||
// Start again
|
||||
window.emit("start_grasscutter", &()).unwrap();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tauri::command]
|
||||
fn restart_grasscutter(_window: tauri::Window) {
|
||||
// Placeholder text for imports
|
||||
let s = System::new();
|
||||
if let Some(process) = s.process(Pid::from(1337)) {
|
||||
println!("{:?}", process.name());
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn enable_grasscutter_watcher(window: tauri::Window, process: String) {
|
||||
let grasscutter_name = process.clone();
|
||||
let mut gc_pid = Pid::from(696969);
|
||||
|
||||
*WATCH_GRASSCUTTER_PROCESS.lock().unwrap() = process;
|
||||
|
||||
window.listen("disable_grasscutter_watcher", |_e| {
|
||||
*WATCH_GRASSCUTTER_PROCESS.lock().unwrap() = "".to_string();
|
||||
});
|
||||
|
||||
println!("Starting grasscutter watcher...");
|
||||
|
||||
thread::spawn(move || {
|
||||
// Initial sleep for 1 second while Grasscutter opens
|
||||
std::thread::sleep(std::time::Duration::from_secs(3));
|
||||
|
||||
let mut system = System::new_all();
|
||||
|
||||
for process_gc in system.processes_by_name("java") {
|
||||
if process_gc.cmd().last().unwrap().contains(&grasscutter_name) {
|
||||
gc_pid = process_gc.pid();
|
||||
*GC_PID.lock().unwrap() = gc_pid.into();
|
||||
window
|
||||
.emit("grasscutter_started", gc_pid.to_string())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
// Shorten loop timer to avoid user closing Cultivation before automatic stuff
|
||||
thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
// Refresh system info
|
||||
system.refresh_all();
|
||||
|
||||
// Grab the grasscutter process name
|
||||
let proc = WATCH_GRASSCUTTER_PROCESS.lock().unwrap().to_string();
|
||||
|
||||
if !proc.is_empty() {
|
||||
let mut exists = true;
|
||||
|
||||
if system.process(gc_pid).is_none() {
|
||||
exists = false;
|
||||
}
|
||||
|
||||
// If the grasscutter process closes.
|
||||
if !exists {
|
||||
println!("Grasscutter closed");
|
||||
|
||||
*WATCH_GRASSCUTTER_PROCESS.lock().unwrap() = "".to_string();
|
||||
|
||||
window.emit("grasscutter_closed", &()).unwrap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn connect(port: u16, certificate_path: String) {
|
||||
// Log message to console.
|
||||
println!("Connecting to proxy...");
|
||||
|
||||
// Change proxy settings.
|
||||
proxy::connect_to_proxy(port);
|
||||
|
||||
// Create and start a proxy.
|
||||
proxy::create_proxy(port, certificate_path).await;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn disconnect() {
|
||||
// Log message to console.
|
||||
println!("Disconnecting from proxy...");
|
||||
|
||||
// Change proxy settings.
|
||||
proxy::disconnect_from_proxy();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn req_get(url: String) -> String {
|
||||
// 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]
|
||||
async fn get_theme_list(data_dir: String) -> Vec<HashMap<String, String>> {
|
||||
let theme_loc = format!("{}/themes", data_dir);
|
||||
|
||||
// Ensure folder exists
|
||||
if !std::path::Path::new(&theme_loc).exists() {
|
||||
std::fs::create_dir_all(&theme_loc).unwrap();
|
||||
}
|
||||
|
||||
// Read each index.json folder in each theme folder
|
||||
let mut themes = Vec::new();
|
||||
|
||||
for entry in std::fs::read_dir(&theme_loc).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_dir() {
|
||||
let index_path = format!("{}/index.json", path.to_str().unwrap());
|
||||
|
||||
if std::path::Path::new(&index_path).exists() {
|
||||
let theme_json = std::fs::read_to_string(&index_path).unwrap();
|
||||
|
||||
let mut map = HashMap::new();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
themes
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_profile_list(data_dir: String) -> Vec<String> {
|
||||
let profile_loc = format!("{}/profiles", data_dir);
|
||||
|
||||
// Ensure folder exists
|
||||
if !std::path::Path::new(&profile_loc).exists() {
|
||||
std::fs::create_dir_all(&profile_loc).unwrap();
|
||||
}
|
||||
|
||||
let mut p_list = Vec::new();
|
||||
for entry in std::fs::read_dir(&profile_loc).unwrap() {
|
||||
p_list.push(entry.unwrap().file_name().into_string().unwrap());
|
||||
}
|
||||
|
||||
p_list
|
||||
}
|
||||
@@ -1,538 +1,6 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use args::{Args, ArgsError};
|
||||
use file_helpers::dir_exists;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use proxy::set_proxy_addr;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
use tauri::api::path::data_dir;
|
||||
use tauri::async_runtime::block_on;
|
||||
|
||||
use std::thread;
|
||||
use sysinfo::{Pid, ProcessExt, System, SystemExt};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use crate::admin::reopen_as_admin;
|
||||
#[cfg(target_os = "windows")]
|
||||
use system_helpers::is_elevated;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::{
|
||||
thread::{sleep, JoinHandle},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
mod admin;
|
||||
mod config;
|
||||
mod downloader;
|
||||
mod file_helpers;
|
||||
mod gamebanana;
|
||||
mod lang;
|
||||
mod patch;
|
||||
mod proxy;
|
||||
mod release;
|
||||
mod system_helpers;
|
||||
mod unzip;
|
||||
mod web;
|
||||
|
||||
static WATCH_GAME_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
|
||||
static WATCH_GRASSCUTTER_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
|
||||
static GC_PID: std::sync::Mutex<usize> = Mutex::new(696969);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub static AAGL_THREAD: Lazy<Mutex<Option<JoinHandle<()>>>> = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
fn try_flush() {
|
||||
std::io::stdout().flush().unwrap_or(())
|
||||
}
|
||||
|
||||
async fn parse_args(inp: &Vec<String>) -> Result<Args, ArgsError> {
|
||||
let mut args = Args::new(
|
||||
"Cultivation",
|
||||
"Private server helper program for an Anime Game",
|
||||
);
|
||||
args.flag("h", "help", "Print various CLI args");
|
||||
args.flag("p", "proxy", "Start the proxy server");
|
||||
args.flag("G", "launch-game", "Launch the game");
|
||||
args.flag("o", "other-redirects", "Redirect other certain anime games");
|
||||
args.flag(
|
||||
"A",
|
||||
"no-admin",
|
||||
"Launch without requiring admin permissions",
|
||||
);
|
||||
args.flag(
|
||||
"g",
|
||||
"no-gui",
|
||||
"Run in CLI mode. Requires -A to be passed as well.",
|
||||
);
|
||||
args.flag("s", "server", "Launch the configured GC server");
|
||||
args.flag(
|
||||
"P",
|
||||
"patch",
|
||||
"Patch your game before launching, with whatever your game version needs",
|
||||
);
|
||||
args.flag(
|
||||
"N",
|
||||
"non-elevated-game",
|
||||
"Launch the game without admin permissions",
|
||||
);
|
||||
args.option(
|
||||
"H",
|
||||
"host",
|
||||
"Set host to connect to (eg. 'localhost:443' or 'my.awesomeserver.com:6969)",
|
||||
"SERVER_HOST",
|
||||
getopts::Occur::Optional,
|
||||
None,
|
||||
);
|
||||
args.option(
|
||||
"a",
|
||||
"game-args",
|
||||
"Arguments to pass to the game process, if launching it",
|
||||
r#""-opt-one -opt-two""#,
|
||||
getopts::Occur::Optional,
|
||||
None,
|
||||
);
|
||||
|
||||
args.parse(inp).unwrap();
|
||||
|
||||
let config = config::get_config();
|
||||
|
||||
if args.value_of("help")? {
|
||||
println!("{}", args.full_usage());
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
// Patch if needed
|
||||
if args.value_of("patch")? {
|
||||
patch::patch_game(false, 0.to_string()).await;
|
||||
}
|
||||
|
||||
if args.value_of("launch-game")? {
|
||||
let game_path = config.game_install_path;
|
||||
let game_args: String = args.value_of("game-args").unwrap_or_default();
|
||||
|
||||
if game_path.is_some() {
|
||||
if args.value_of("non-elevated-game")? {
|
||||
system_helpers::run_un_elevated(game_path.unwrap(), Some(game_args))
|
||||
} else {
|
||||
system_helpers::run_program(game_path.unwrap(), Some(game_args))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if args.value_of("server")? && config.grasscutter_path.is_some() && config.java_path.is_some() {
|
||||
let server_jar = config.grasscutter_path.unwrap();
|
||||
let mut server_path = server_jar.clone();
|
||||
// Strip jar name from path
|
||||
if server_path.contains('/') {
|
||||
// Can never panic because of if
|
||||
let len = server_jar.rfind('/').unwrap();
|
||||
server_path.truncate(len);
|
||||
} else if server_path.contains('\\') {
|
||||
let len = server_jar.rfind('\\').unwrap();
|
||||
server_path.truncate(len);
|
||||
}
|
||||
let java_path = config.java_path.unwrap();
|
||||
|
||||
system_helpers::run_jar(server_jar, server_path.to_string(), java_path);
|
||||
}
|
||||
|
||||
if args.value_of::<String>("host").is_ok() && !args.value_of::<String>("host")?.is_empty() {
|
||||
let host = args.value_of::<String>("host")?;
|
||||
set_proxy_addr(host);
|
||||
}
|
||||
|
||||
if args.value_of("proxy")? {
|
||||
println!("Starting proxy server...");
|
||||
let mut pathbuf = tauri::api::path::data_dir().unwrap();
|
||||
pathbuf.push("cultivation");
|
||||
pathbuf.push("ca");
|
||||
|
||||
if args.value_of("other-redirects")? {
|
||||
// proxy::set_redirect_more(); // Unused
|
||||
}
|
||||
|
||||
connect(8035, pathbuf.to_str().unwrap().to_string()).await;
|
||||
}
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
fn main() -> Result<(), ArgsError> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let parsed_args = block_on(parse_args(&args)).unwrap();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if !is_elevated() && !parsed_args.value_of("no-admin")? {
|
||||
println!("===============================================================================");
|
||||
println!("You running as a non-elevated user. Some stuff will almost definitely not work.");
|
||||
println!("===============================================================================");
|
||||
|
||||
reopen_as_admin();
|
||||
}
|
||||
|
||||
// Setup datadir/cultivation just in case something went funky and it wasn't made
|
||||
if !dir_exists(data_dir().unwrap().join("cultivation").to_str().unwrap()) {
|
||||
fs::create_dir_all(data_dir().unwrap().join("cultivation")).unwrap();
|
||||
}
|
||||
|
||||
// Always set CWD to the location of the executable.
|
||||
let mut exe_path = std::env::current_exe().unwrap();
|
||||
exe_path.pop();
|
||||
std::env::set_current_dir(&exe_path).unwrap();
|
||||
|
||||
// For disabled GUI
|
||||
ctrlc::set_handler(|| {
|
||||
disconnect();
|
||||
block_on(patch::unpatch_game());
|
||||
std::process::exit(0);
|
||||
})
|
||||
.unwrap_or(());
|
||||
|
||||
if !parsed_args.value_of("no-gui")? {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
enable_process_watcher,
|
||||
enable_grasscutter_watcher,
|
||||
connect,
|
||||
disconnect,
|
||||
req_get,
|
||||
is_game_running,
|
||||
is_grasscutter_running,
|
||||
restart_grasscutter,
|
||||
get_theme_list,
|
||||
system_helpers::run_command,
|
||||
system_helpers::run_program,
|
||||
system_helpers::run_program_args,
|
||||
system_helpers::run_program_relative,
|
||||
system_helpers::start_service,
|
||||
system_helpers::service_status,
|
||||
system_helpers::stop_service,
|
||||
system_helpers::run_jar,
|
||||
system_helpers::run_jar_root,
|
||||
system_helpers::open_in_browser,
|
||||
system_helpers::install_location,
|
||||
system_helpers::is_elevated,
|
||||
system_helpers::set_migoto_delay,
|
||||
system_helpers::wipe_registry,
|
||||
system_helpers::get_platform,
|
||||
system_helpers::run_un_elevated,
|
||||
system_helpers::jvm_add_cap,
|
||||
system_helpers::jvm_remove_cap,
|
||||
patch::patch_game,
|
||||
patch::unpatch_game,
|
||||
proxy::set_proxy_addr,
|
||||
proxy::generate_ca_files,
|
||||
release::get_latest_release,
|
||||
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,
|
||||
file_helpers::does_file_exist,
|
||||
downloader::download_file,
|
||||
downloader::stop_download,
|
||||
lang::get_lang,
|
||||
lang::get_languages,
|
||||
web::valid_url,
|
||||
web::web_get,
|
||||
gamebanana::get_download_links,
|
||||
gamebanana::list_submissions,
|
||||
gamebanana::list_mods
|
||||
])
|
||||
.on_window_event(|event| {
|
||||
if let tauri::WindowEvent::CloseRequested { .. } = event.event() {
|
||||
// Ensure all proxy stuff is handled
|
||||
disconnect();
|
||||
}
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
} else {
|
||||
try_flush();
|
||||
println!("Press enter or CTRL-C twice to quit...");
|
||||
std::io::stdin().read_line(&mut String::new()).unwrap();
|
||||
}
|
||||
|
||||
// Always disconnect upon closing the program
|
||||
disconnect();
|
||||
|
||||
// Always unpatch game upon closing the program
|
||||
block_on(patch::unpatch_game());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn is_game_running() -> bool {
|
||||
// Grab the game process name
|
||||
let proc = WATCH_GAME_PROCESS.lock().unwrap().to_string();
|
||||
|
||||
!proc.is_empty()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[tauri::command]
|
||||
fn enable_process_watcher(window: tauri::Window, process: String) {
|
||||
*WATCH_GAME_PROCESS.lock().unwrap() = process;
|
||||
|
||||
window.listen("disable_process_watcher", |_e| {
|
||||
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
|
||||
});
|
||||
|
||||
println!("Starting process watcher...");
|
||||
|
||||
thread::spawn(move || {
|
||||
// Initial sleep for 8 seconds, since running 20 different injectors or whatever can take a while
|
||||
std::thread::sleep(std::time::Duration::from_secs(60));
|
||||
|
||||
let mut system = System::new_all();
|
||||
|
||||
loop {
|
||||
// Shorten loop timer to avoid user closing Cultivation before unpatching/proxy disconnecting
|
||||
thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
// Refresh system info
|
||||
system.refresh_all();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The library takes care of it
|
||||
#[cfg(target_os = "linux")]
|
||||
#[tauri::command]
|
||||
fn enable_process_watcher(window: tauri::Window, process: String) {
|
||||
drop(process);
|
||||
thread::spawn(move || {
|
||||
let end_time = Instant::now() + Duration::from_secs(90);
|
||||
let game_thread = loop {
|
||||
let mut lock = AAGL_THREAD.lock().unwrap();
|
||||
if lock.is_some() {
|
||||
break lock.take().unwrap();
|
||||
}
|
||||
drop(lock);
|
||||
if end_time < Instant::now() {
|
||||
// If more than 60 seconds pass something has gone wrong
|
||||
println!("Waiting for game thread timed out");
|
||||
return;
|
||||
}
|
||||
// Otherwhise wait in order to not use too many CPU cycles
|
||||
sleep(Duration::from_millis(128));
|
||||
};
|
||||
game_thread.join().unwrap();
|
||||
println!("Game closed");
|
||||
|
||||
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
|
||||
disconnect();
|
||||
|
||||
window.emit("game_closed", &()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[tauri::command]
|
||||
fn enable_process_watcher(window: tauri::Window, process: String) {}
|
||||
|
||||
#[tauri::command]
|
||||
fn is_grasscutter_running() -> bool {
|
||||
// Grab the grasscutter process name
|
||||
let proc = WATCH_GRASSCUTTER_PROCESS.lock().unwrap().to_string();
|
||||
|
||||
!proc.is_empty()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tauri::command]
|
||||
fn restart_grasscutter(window: tauri::Window) -> bool {
|
||||
let pid: usize = *GC_PID.lock().unwrap();
|
||||
let system = System::new_all();
|
||||
// Get the process
|
||||
if let Some(process) = system.process(Pid::from(pid)) {
|
||||
// Kill it
|
||||
if process.kill() {
|
||||
// Also kill the cmd it was open in
|
||||
if let Some(parent) = system.process(process.parent().unwrap()) {
|
||||
parent.kill();
|
||||
}
|
||||
for process_gc in system.processes_by_name("java") {
|
||||
if process_gc.cmd().last().unwrap().contains("grasscutter") {
|
||||
process_gc.kill();
|
||||
}
|
||||
}
|
||||
window.emit("disable_grasscutter_watcher", &()).unwrap();
|
||||
thread::sleep(std::time::Duration::from_secs(2));
|
||||
// Start again
|
||||
window.emit("start_grasscutter", &()).unwrap();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tauri::command]
|
||||
fn restart_grasscutter(_window: tauri::Window) {
|
||||
// Placeholder text for imports
|
||||
let s = System::new();
|
||||
if let Some(process) = s.process(Pid::from(1337)) {
|
||||
println!("{}", process.name());
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn enable_grasscutter_watcher(window: tauri::Window, process: String) {
|
||||
let grasscutter_name = process.clone();
|
||||
let mut gc_pid = Pid::from(696969);
|
||||
|
||||
*WATCH_GRASSCUTTER_PROCESS.lock().unwrap() = process;
|
||||
|
||||
window.listen("disable_grasscutter_watcher", |_e| {
|
||||
*WATCH_GRASSCUTTER_PROCESS.lock().unwrap() = "".to_string();
|
||||
});
|
||||
|
||||
println!("Starting grasscutter watcher...");
|
||||
|
||||
thread::spawn(move || {
|
||||
// Initial sleep for 1 second while Grasscutter opens
|
||||
std::thread::sleep(std::time::Duration::from_secs(3));
|
||||
|
||||
let mut system = System::new_all();
|
||||
|
||||
for process_gc in system.processes_by_name("java") {
|
||||
if process_gc.cmd().last().unwrap().contains(&grasscutter_name) {
|
||||
gc_pid = process_gc.pid();
|
||||
*GC_PID.lock().unwrap() = gc_pid.into();
|
||||
window
|
||||
.emit("grasscutter_started", gc_pid.to_string())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
// Shorten loop timer to avoid user closing Cultivation before automatic stuff
|
||||
thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
// Refresh system info
|
||||
system.refresh_all();
|
||||
|
||||
// Grab the grasscutter process name
|
||||
let proc = WATCH_GRASSCUTTER_PROCESS.lock().unwrap().to_string();
|
||||
|
||||
if !proc.is_empty() {
|
||||
let mut exists = true;
|
||||
|
||||
if system.process(gc_pid).is_none() {
|
||||
exists = false;
|
||||
}
|
||||
|
||||
// If the grasscutter process closes.
|
||||
if !exists {
|
||||
println!("Grasscutter closed");
|
||||
|
||||
*WATCH_GRASSCUTTER_PROCESS.lock().unwrap() = "".to_string();
|
||||
|
||||
window.emit("grasscutter_closed", &()).unwrap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn connect(port: u16, certificate_path: String) {
|
||||
// Log message to console.
|
||||
println!("Connecting to proxy...");
|
||||
|
||||
// Change proxy settings.
|
||||
proxy::connect_to_proxy(port);
|
||||
|
||||
// Create and start a proxy.
|
||||
proxy::create_proxy(port, certificate_path).await;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn disconnect() {
|
||||
// Log message to console.
|
||||
println!("Disconnecting from proxy...");
|
||||
|
||||
// Change proxy settings.
|
||||
proxy::disconnect_from_proxy();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn req_get(url: String) -> String {
|
||||
// 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]
|
||||
async fn get_theme_list(data_dir: String) -> Vec<HashMap<String, String>> {
|
||||
let theme_loc = format!("{}/themes", data_dir);
|
||||
|
||||
// Ensure folder exists
|
||||
if !std::path::Path::new(&theme_loc).exists() {
|
||||
std::fs::create_dir_all(&theme_loc).unwrap();
|
||||
}
|
||||
|
||||
// Read each index.json folder in each theme folder
|
||||
let mut themes = Vec::new();
|
||||
|
||||
for entry in std::fs::read_dir(&theme_loc).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_dir() {
|
||||
let index_path = format!("{}/index.json", path.to_str().unwrap());
|
||||
|
||||
if std::path::Path::new(&index_path).exists() {
|
||||
let theme_json = std::fs::read_to_string(&index_path).unwrap();
|
||||
|
||||
let mut map = HashMap::new();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
themes
|
||||
fn main() {
|
||||
cultivation_lib::run()
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ pub async fn unpatch_game() -> bool {
|
||||
}
|
||||
|
||||
pub async fn get_game_rsa_path() -> Option<String> {
|
||||
let config = config::get_config();
|
||||
let config = config::get_config(String::from("default"));
|
||||
|
||||
config.game_install_path.as_ref()?;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{path::PathBuf, str::FromStr, sync::Mutex};
|
||||
|
||||
use hudsucker::{
|
||||
async_trait::async_trait,
|
||||
certificate_authority::RcgenAuthority,
|
||||
@@ -19,7 +18,6 @@ use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
|
||||
use rustls_pemfile as pemfile;
|
||||
use tauri::{api::path::data_dir, http::Uri};
|
||||
|
||||
#[cfg(windows)]
|
||||
use registry::{Data, Hive, Security};
|
||||
@@ -59,7 +57,7 @@ impl HttpHandler for ProxyHandler {
|
||||
async fn handle_request(
|
||||
&mut self,
|
||||
_ctx: &HttpContext,
|
||||
mut req: Request<Body>,
|
||||
mut req: Request<Body>
|
||||
) -> RequestOrResponse {
|
||||
let uri = req.uri().to_string();
|
||||
|
||||
@@ -85,11 +83,9 @@ impl HttpHandler for ProxyHandler {
|
||||
} else {
|
||||
let uri_path_and_query = req.uri().path_and_query().unwrap().as_str();
|
||||
// Create new URI.
|
||||
let new_uri =
|
||||
Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path_and_query).as_str())
|
||||
*req.uri_mut() =
|
||||
hudsucker::hyper::Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path_and_query).as_str())
|
||||
.unwrap();
|
||||
// Set request URI to the new one.
|
||||
*req.uri_mut() = new_uri;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +128,7 @@ pub async fn create_proxy(proxy_port: u16, certificate_path: String) {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
println!("Encountered {}. Regenerating CA cert and retrying...", e);
|
||||
generate_ca_files(&data_dir().unwrap().join("cultivation"));
|
||||
generate_ca_files(&dirs::config_dir().unwrap().join("cultivation"));
|
||||
|
||||
fs::read(&pk_path).expect("Could not read private key")
|
||||
}
|
||||
@@ -143,7 +139,7 @@ pub async fn create_proxy(proxy_port: u16, certificate_path: String) {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
println!("Encountered {}. Regenerating CA cert and retrying...", e);
|
||||
generate_ca_files(&data_dir().unwrap().join("cultivation"));
|
||||
generate_ca_files(&dirs::config_dir().unwrap().join("cultivation"));
|
||||
|
||||
fs::read(&ca_path).expect("Could not read certificate")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use ini::Ini;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
@@ -18,6 +17,7 @@ use anime_launcher_sdk::{
|
||||
config::ConfigExt, genshin::config::Config, genshin::game, genshin::states::LauncherState,
|
||||
wincompatlib::prelude::*,
|
||||
};
|
||||
use ini::Ini;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::{path::Path, process::Stdio, thread};
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -296,11 +296,7 @@ fn aagl_wine_run<P: AsRef<Path>>(path: P, args: Option<String>) -> Command {
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||
.collect();
|
||||
use anime_launcher_sdk::components::wine::UnifiedWine::*;
|
||||
let wined = match wine_run {
|
||||
Default(wine) => wine,
|
||||
Proton(proton) => proton.wine().clone(),
|
||||
};
|
||||
let wined = wine_run;
|
||||
let mut cmd = Command::new(&wined.binary);
|
||||
cmd.arg(path.as_ref()).envs(wined.get_envs()).envs(env);
|
||||
if let Some(args) = args {
|
||||
@@ -426,7 +422,7 @@ pub fn set_migoto_delay(migoto_path: String) -> bool {
|
||||
conf.with_section(Some("Loader")).set("delay", "20");
|
||||
conf
|
||||
.with_section(Some("Include"))
|
||||
.set("include", "ShaderFixes\\help.ini");
|
||||
.set("include", "ShaderFixes/help.ini");
|
||||
|
||||
// Write file
|
||||
match conf.write_to_file_opt(
|
||||
@@ -434,6 +430,7 @@ pub fn set_migoto_delay(migoto_path: String) -> bool {
|
||||
ini::WriteOption {
|
||||
escape_policy: (ini::EscapePolicy::Nothing),
|
||||
line_separator: (ini::LineSeparator::SystemDefault),
|
||||
kv_separator: "=",
|
||||
},
|
||||
) {
|
||||
Ok(_) => {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::fs::{read_dir, File};
|
||||
use std::path;
|
||||
use std::path::{self, PathBuf};
|
||||
use std::thread;
|
||||
use unrar::archive::Archive;
|
||||
use tauri::Emitter;
|
||||
use unrar::Archive;
|
||||
use simple_zip::zip::Decompress;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn unzip(
|
||||
@@ -70,22 +72,22 @@ pub fn unzip(
|
||||
if zipfile.ends_with(".rar") {
|
||||
success = extract_rar(&zipfile, &f, &full_path, top_level.unwrap_or(true));
|
||||
|
||||
let archive = Archive::new(zipfile.clone());
|
||||
name = archive.list().unwrap().next().unwrap().unwrap().filename;
|
||||
let archive = Archive::new(&zipfile);
|
||||
name = archive.open_for_listing().unwrap().next().unwrap().unwrap().filename;
|
||||
} else if zipfile.ends_with(".7z") {
|
||||
success = extract_7z(&zipfile, &f, &full_path, top_level.unwrap_or(true));
|
||||
success = Ok(extract_7z(&zipfile, &f, &full_path, top_level.unwrap_or(true)));
|
||||
|
||||
name = String::from("banana");
|
||||
name = PathBuf::from("banana");
|
||||
} else {
|
||||
success = extract_zip(&zipfile, &f, &full_path, top_level.unwrap_or(true));
|
||||
success = Ok(extract_zip(&full_path));
|
||||
|
||||
// Get the name of the inenr file in the zip file
|
||||
let mut zip = zip::ZipArchive::new(&f).unwrap();
|
||||
let file = zip.by_index(0).unwrap();
|
||||
name = file.name().to_string();
|
||||
name = PathBuf::from(file.name());
|
||||
}
|
||||
|
||||
if !success {
|
||||
if success.unwrap_or(false) {
|
||||
let mut res_hash = std::collections::HashMap::new();
|
||||
|
||||
res_hash.insert("path".to_string(), zipfile.to_string());
|
||||
@@ -94,9 +96,9 @@ pub fn unzip(
|
||||
}
|
||||
|
||||
// If the contents is a jar file, emit that we have extracted a new jar file
|
||||
if name.ends_with(".jar") {
|
||||
if name.to_str().unwrap().ends_with(".jar") {
|
||||
window
|
||||
.emit("jar_extracted", destpath.to_string() + name.as_str())
|
||||
.emit("jar_extracted", destpath.to_string() + name.to_str().unwrap())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -150,44 +152,25 @@ pub fn unzip(
|
||||
});
|
||||
}
|
||||
|
||||
fn extract_rar(rarfile: &str, _f: &File, full_path: &path::Path, _top_level: bool) -> bool {
|
||||
let archive = Archive::new(rarfile.to_string());
|
||||
fn extract_rar(rarfile: &str, _f: &File, full_path: &path::Path, _top_level: bool) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let archive = Archive::new(&rarfile);
|
||||
|
||||
let mut open_archive = archive
|
||||
.extract_to(full_path.to_str().unwrap().to_string())
|
||||
.unwrap();
|
||||
|
||||
match open_archive.process() {
|
||||
Ok(_) => {
|
||||
println!(
|
||||
"Extracted rar file to: {}",
|
||||
full_path.to_str().unwrap_or("Error")
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to extract rar file: {}", e);
|
||||
false
|
||||
.open_for_processing().unwrap();
|
||||
while let Some(header) = open_archive.read_header()? {
|
||||
open_archive = if header.entry().is_directory() || header.entry().is_file() {
|
||||
header.extract_to(full_path.to_str().unwrap().to_string())?
|
||||
} else {
|
||||
header.skip()?
|
||||
}
|
||||
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn extract_zip(_zipfile: &str, f: &File, full_path: &path::Path, top_level: bool) -> bool {
|
||||
match zip_extract::extract(f, full_path, top_level) {
|
||||
Ok(_) => {
|
||||
println!(
|
||||
"Extracted zip file to: {}",
|
||||
full_path.to_str().unwrap_or("Error")
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to extract zip file: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
fn extract_zip(full_path: &path::Path) -> bool {
|
||||
Decompress::local_buffer(full_path);
|
||||
true
|
||||
}
|
||||
|
||||
fn extract_7z(sevenzfile: &str, _f: &File, full_path: &path::Path, _top_level: bool) -> bool {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use http::header;
|
||||
use tauri::http::header;
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::header::{CONTENT_TYPE, USER_AGENT};
|
||||
static CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
|
||||
|
||||
@@ -1,80 +1,36 @@
|
||||
{
|
||||
"$schema": "..\\node_modules/@tauri-apps/cli\\schema.json",
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "cultivation",
|
||||
"version": "2.0.0",
|
||||
"identifier": "io.grasscutter.cultivation",
|
||||
"build": {
|
||||
"beforeDevCommand": "yarn start",
|
||||
"devPath": "http://localhost:3000",
|
||||
"distDir": "../build"
|
||||
"beforeDevCommand": "yarn dev",
|
||||
"devUrl": "http://localhost:1420",
|
||||
"beforeBuildCommand": "yarn build",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"package": {
|
||||
"productName": "Cultivation",
|
||||
"version": "1.6.1"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"fs": {
|
||||
"scope": ["$DATA", "$DATA/cultivation", "$DATA/cultivation/**"]
|
||||
},
|
||||
"protocol": {
|
||||
"all": true,
|
||||
"asset": true,
|
||||
"assetScope": ["$DATA", "$DATA/cultivation", "$DATA/cultivation/**"]
|
||||
},
|
||||
"all": true
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"category": "Game",
|
||||
"copyright": "",
|
||||
"deb": {
|
||||
"depends": []
|
||||
},
|
||||
"externalBin": [],
|
||||
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
|
||||
"identifier": "io.grasscutter",
|
||||
"shortDescription": "A game launcher.",
|
||||
"longDescription": "A launcher for a certain anime game that proxies all related game traffic to external servers.",
|
||||
"macOS": {
|
||||
"entitlements": null,
|
||||
"exceptionDomain": "",
|
||||
"frameworks": [],
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null
|
||||
},
|
||||
"resources": ["lang/*.json", "keys/*", "patch/*"],
|
||||
"targets": "all",
|
||||
"windows": {
|
||||
"allowDowngrades": false,
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "",
|
||||
"wix": {
|
||||
"fragmentPaths": ["resources/wix.wxs"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"csp": "default-src 'self'; img-src 'self' https://* asset: https://asset.localhost tauri://localhost; media-src https://* asset: https://asset.localhost tauri://localhost; style-src-elem https://* asset: https://asset.localhost tauri://localhost; script-src-elem https://* asset: https://asset.localhost tauri://localhost;"
|
||||
},
|
||||
"updater": {
|
||||
"active": false,
|
||||
"dialog": true,
|
||||
"endpoints": [
|
||||
"https://api.grasscutter.io/cultivation/updater?version={{current_version}}",
|
||||
"https://api.grasscutters.xyz/cultivation/updater?version={{current_version}}"
|
||||
],
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEIyM0MzMzk0NkM4OTQzNQpSV1ExbE1oR09jTWpDNFJ4NURsaS9mdHpDWmpBM3JNVHkrdER4bS9KdHFCQnNVSWs0ZjZTU295WAo="
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"fullscreen": false,
|
||||
"transparent": true,
|
||||
"height": 730,
|
||||
"resizable": true,
|
||||
"title": "Cultivation",
|
||||
"width": 1280,
|
||||
"decorations": false,
|
||||
"height": 730,
|
||||
"center": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -16,6 +16,3 @@ let isDebug = false
|
||||
}
|
||||
|
||||
root.render(<React.StrictMode>{isDebug ? <Debug /> : <App />}</React.StrictMode>)
|
||||
|
||||
import reportWebVitals from './utils/reportWebVitals'
|
||||
isDebug && reportWebVitals(console.log)
|
||||
1
src/react-app-env.d.ts
vendored
@@ -1 +0,0 @@
|
||||
/// <reference types="react-scripts" />
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -4,7 +4,7 @@ import './App.css'
|
||||
import DownloadHandler from '../utils/download'
|
||||
import { getConfigOption } from '../utils/configuration'
|
||||
import { getTheme, loadTheme } from '../utils/themes'
|
||||
import { convertFileSrc, invoke } from '@tauri-apps/api/tauri'
|
||||
import { convertFileSrc, invoke } from '@tauri-apps/api/core'
|
||||
import { Main } from './Main'
|
||||
import { Mods } from './Mods'
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import './App.css'
|
||||
|
||||
import TopBar from './components/TopBar'
|
||||
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import TextInput from './components/common/TextInput'
|
||||
|
||||
@@ -15,7 +15,7 @@ async function setProxyAddress(address: string) {
|
||||
}
|
||||
|
||||
async function startProxy() {
|
||||
await invoke('connect', { port: 2222, certificatePath: (await dataDir()) + '\\cultivation\\ca' })
|
||||
await invoke('connect', { port: 2222, certificatePath: (await dataDir()) + '/cultivation/ca' })
|
||||
await invoke('open_in_browser', { url: 'https://hoyoverse.com' })
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ async function stopProxy() {
|
||||
}
|
||||
|
||||
async function generateCertificates() {
|
||||
await invoke('generate_ca_files', { path: (await dataDir()) + '\\cultivation' })
|
||||
await invoke('generate_ca_files', { path: (await dataDir()) + '/cultivation' })
|
||||
}
|
||||
|
||||
async function generateInfo() {
|
||||
console.log({
|
||||
certificatePath: (await dataDir()) + '\\cultivation\\ca',
|
||||
certificatePath: (await dataDir()) + '/cultivation/ca',
|
||||
isAdmin: await invoke('is_elevated'),
|
||||
connectingTo: proxyAddress,
|
||||
})
|
||||
|
||||
@@ -16,11 +16,11 @@ import Notification from './components/common/Notification'
|
||||
import GamePathNotify from './components/menu/GamePathNotify'
|
||||
|
||||
import { getConfig, getConfigOption, setConfigOption } from '../utils/configuration'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { appWindow } from '@tauri-apps/api/window'
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { unpatchGame } from '../utils/rsa'
|
||||
import DownloadHandler from '../utils/download'
|
||||
|
||||
@@ -28,6 +28,7 @@ import DownloadHandler from '../utils/download'
|
||||
import cogBtn from '../resources/icons/cog.svg'
|
||||
import downBtn from '../resources/icons/download.svg'
|
||||
import wrenchBtn from '../resources/icons/wrench.svg'
|
||||
const appWindow = getCurrentWebviewWindow()
|
||||
|
||||
interface IProps {
|
||||
downloadHandler: DownloadHandler
|
||||
@@ -163,13 +164,15 @@ export class Main extends React.Component<IProps, IState> {
|
||||
if (!cert_generated) {
|
||||
// Generate the certificate
|
||||
await invoke('generate_ca_files', {
|
||||
path: (await dataDir()) + 'cultivation',
|
||||
path: (await dataDir()) + '/cultivation',
|
||||
})
|
||||
|
||||
await setConfigOption('cert_generated', true)
|
||||
}
|
||||
|
||||
// Ensure old configs are updated to use RSA
|
||||
const updatedProfile = await getConfigOption('profile')
|
||||
await setConfigOption('profile', updatedProfile)
|
||||
const updatedConfig = await getConfigOption('patch_rsa')
|
||||
await setConfigOption('patch_rsa', updatedConfig)
|
||||
|
||||
@@ -222,7 +225,7 @@ export class Main extends React.Component<IProps, IState> {
|
||||
})
|
||||
}
|
||||
|
||||
async componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>) {
|
||||
async componentDidUpdate(_prevProps: Readonly<IProps>, prevState: Readonly<IState>) {
|
||||
const game_path = await getConfigOption('game_install_path')
|
||||
|
||||
// Check if game exists at set location
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import React from 'react'
|
||||
import DownloadHandler from '../utils/download'
|
||||
import { getModDownload, ModData } from '../utils/gamebanana'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import React from 'react'
|
||||
|
||||
import Discord from '../../resources/icons/discord.svg'
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
|
||||
#playButton > div {
|
||||
margin-bottom: 6px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
#playButton .BigButton {
|
||||
@@ -26,10 +26,27 @@
|
||||
|
||||
#serverControls {
|
||||
color: white;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
text-shadow: 1px 1px 8px black;
|
||||
}
|
||||
|
||||
#menuOptionsContainerProfiles {
|
||||
justify-self: right;
|
||||
align-self: right;
|
||||
padding-left: 8px;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
#serverControls select {
|
||||
width: 150px;
|
||||
height: 30px;
|
||||
border: none;
|
||||
border-bottom: 2px solid #cecece;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.BottomSection .CheckboxDisplay {
|
||||
border-color: #c5c5c5;
|
||||
background: #fff;
|
||||
|
||||
@@ -3,9 +3,9 @@ import Checkbox from './common/Checkbox'
|
||||
import BigButton from './common/BigButton'
|
||||
import TextInput from './common/TextInput'
|
||||
import HelpButton from './common/HelpButton'
|
||||
import { getConfig, saveConfig, setConfigOption } from '../../utils/configuration'
|
||||
import { getConfig, saveConfig, setConfigOption, setProfileOption } from '../../utils/configuration'
|
||||
import { translate } from '../../utils/language'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
import Server from '../../resources/icons/server.svg'
|
||||
import Plus from '../../resources/icons/plus.svg'
|
||||
@@ -16,7 +16,7 @@ import { GrasscutterElevation } from './menu/Options'
|
||||
import { getGameExecutable, getGameVersion, getGrasscutterJar } from '../../utils/game'
|
||||
import { patchGame, unpatchGame } from '../../utils/rsa'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { confirm } from '@tauri-apps/api/dialog'
|
||||
import { confirm } from '@tauri-apps/plugin-dialog'
|
||||
import DownloadHandler from '../../utils/download'
|
||||
|
||||
interface IProps {
|
||||
@@ -43,6 +43,8 @@ interface IState {
|
||||
migotoSet: boolean
|
||||
|
||||
unElevated: boolean
|
||||
profile: string
|
||||
profiles: string[]
|
||||
}
|
||||
|
||||
export default class ServerLaunchSection extends React.Component<IProps, IState> {
|
||||
@@ -66,6 +68,8 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
akebiSet: false,
|
||||
migotoSet: false,
|
||||
unElevated: false,
|
||||
profile: 'default',
|
||||
profiles: ['default'],
|
||||
}
|
||||
|
||||
this.toggleGrasscutter = this.toggleGrasscutter.bind(this)
|
||||
@@ -75,6 +79,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
this.toggleHttps = this.toggleHttps.bind(this)
|
||||
this.launchServer = this.launchServer.bind(this)
|
||||
this.setButtonLabel = this.setButtonLabel.bind(this)
|
||||
this.setProfile = this.setProfile.bind(this)
|
||||
|
||||
listen('start_grasscutter', async () => {
|
||||
this.launchServer()
|
||||
@@ -102,6 +107,8 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
akebiSet: config.akebi_path !== '',
|
||||
migotoSet: config.migoto_path !== '',
|
||||
unElevated: config.un_elevated || false,
|
||||
profile: config.profile || 'default',
|
||||
profiles: (await this.getProfileList()).map((t) => t),
|
||||
})
|
||||
|
||||
this.setButtonLabel()
|
||||
@@ -134,7 +141,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
if (
|
||||
await confirm(
|
||||
"Oops! HTTPS is enabled but you're connecting to localhost! \nHTTPS MUST be disabled for localhost. \n\nWould you like to disable HTTPS and continue?",
|
||||
{ title: 'WARNING!!', type: 'warning' }
|
||||
{ title: 'WARNING!!', kind: 'warning' }
|
||||
)
|
||||
) {
|
||||
this.toggleHttps()
|
||||
@@ -245,7 +252,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
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' })
|
||||
await invoke('connect', { port: 8365, certificatePath: (await dataDir()) + '/cultivation/ca' })
|
||||
}
|
||||
|
||||
// Open server as well if the options are set
|
||||
@@ -393,6 +400,25 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
}
|
||||
}
|
||||
|
||||
async setProfile(value: string) {
|
||||
this.setState({ profile: value })
|
||||
await setProfileOption('profile', value)
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
async getProfileList() {
|
||||
const profiles: string[] = await invoke('get_profile_list', {
|
||||
dataDir: `${await dataDir()}/cultivation`,
|
||||
})
|
||||
const list = ['default']
|
||||
|
||||
profiles.forEach((t) => {
|
||||
list.push(t.split('.json')[0])
|
||||
})
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="playButton">
|
||||
@@ -403,6 +429,23 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
onChange={this.toggleGrasscutter}
|
||||
checked={this.state.grasscutterEnabled}
|
||||
/>
|
||||
<div className="OptionSection" id="menuOptionsContainerProfiles">
|
||||
<div className="OptionValue" id="menuOptionsSelectProfiles">
|
||||
<select
|
||||
value={this.state.profile}
|
||||
id="menuOptionsSelectMenuProfiles"
|
||||
onChange={(event) => {
|
||||
this.setProfile(event.target.value)
|
||||
}}
|
||||
>
|
||||
{this.state.profiles.map((t) => (
|
||||
<option key={t} value={t}>
|
||||
{t}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.state.grasscutterEnabled && (
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import React from 'react'
|
||||
import { app, invoke } from '@tauri-apps/api'
|
||||
import { appWindow } from '@tauri-apps/api/window'
|
||||
import { app } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { getConfig, setConfigOption } from '../../utils/configuration'
|
||||
import Tr from '../../utils/language'
|
||||
import { confirm } from '@tauri-apps/api/dialog'
|
||||
import { confirm } from '@tauri-apps/plugin-dialog'
|
||||
|
||||
import './TopBar.css'
|
||||
import closeIcon from '../../resources/icons/close.svg'
|
||||
import minIcon from '../../resources/icons/min.svg'
|
||||
import { unpatchGame } from '../../utils/rsa'
|
||||
const appWindow = getCurrentWebviewWindow()
|
||||
|
||||
interface IProps {
|
||||
children?: React.ReactNode | React.ReactNode[]
|
||||
@@ -17,7 +19,7 @@ interface IProps {
|
||||
interface IState {
|
||||
version: string
|
||||
clicks: number
|
||||
intv: NodeJS.Timeout | null
|
||||
intv: number
|
||||
}
|
||||
|
||||
export default class TopBar extends React.Component<IProps, IState> {
|
||||
@@ -27,7 +29,7 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
this.state = {
|
||||
version: '0.0.0',
|
||||
clicks: 0,
|
||||
intv: null,
|
||||
intv: 0,
|
||||
}
|
||||
|
||||
this.activateClick = this.activateClick.bind(this)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { translate } from '../../../utils/language'
|
||||
import TextInput from './TextInput'
|
||||
import File from '../../../resources/icons/folder.svg'
|
||||
|
||||
@@ -9,14 +9,14 @@ import { dataDir } from '@tauri-apps/api/path'
|
||||
import './Downloads.css'
|
||||
import Divider from './Divider'
|
||||
import { getConfigOption, setConfigOption } from '../../../utils/configuration'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import HelpButton from '../common/HelpButton'
|
||||
import { ask } from '@tauri-apps/api/dialog'
|
||||
import { ask } from '@tauri-apps/plugin-dialog'
|
||||
|
||||
const FULL_BUILD_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterCulti.zip' // Change to link that can be updated without modifying here
|
||||
const FULL_QUEST_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterQuests.zip'
|
||||
const FULL_50_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterLunaGC60.zip' // https://github.com/pmagixc/LunaGC_6.0.0
|
||||
const FULL_50_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterLunaGC61.zip' // https://github.com/pmagixc/LunaGC_6.0.0
|
||||
const STABLE_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/stable.zip'
|
||||
const DEV_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/development.zip'
|
||||
const UNSTABLE_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/unstable/Grasscutter.zip'
|
||||
@@ -104,15 +104,15 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
return
|
||||
}
|
||||
|
||||
const path = gc_path.substring(0, gc_path.lastIndexOf('\\'))
|
||||
const path = gc_path.substring(0, gc_path.lastIndexOf('/'))
|
||||
|
||||
if (gc_path) {
|
||||
const resources_exist: boolean =
|
||||
((await invoke('dir_exists', {
|
||||
path: path + '\\resources',
|
||||
path: path + '/resources',
|
||||
})) as boolean) &&
|
||||
(!(await invoke('dir_is_empty', {
|
||||
path: path + '\\resources',
|
||||
path: path + '/resources',
|
||||
})) as boolean)
|
||||
|
||||
this.setState({
|
||||
@@ -129,7 +129,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
// Set to default if not set
|
||||
if (!path || path === '') {
|
||||
const appdata = await dataDir()
|
||||
folderPath = appdata + 'cultivation\\grasscutter'
|
||||
folderPath = appdata + '/cultivation/grasscutter'
|
||||
|
||||
// Early return since its formatted properly
|
||||
return folderPath
|
||||
@@ -145,15 +145,15 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
async getCultivationFolder() {
|
||||
const folderPath = (await dataDir()) + 'cultivation'
|
||||
const folderPath = (await dataDir()) + '/cultivation'
|
||||
|
||||
return folderPath
|
||||
}
|
||||
|
||||
async downloadGrasscutterFullBuild() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(FULL_BUILD_DOWNLOAD, folder + '\\GrasscutterCulti.zip', async () => {
|
||||
await unzip(folder + '\\GrasscutterCulti.zip', folder + '\\', true)
|
||||
this.props.downloadManager.addDownload(FULL_BUILD_DOWNLOAD, folder + '/GrasscutterCulti.zip', async () => {
|
||||
await unzip(folder + '/GrasscutterCulti.zip', folder + '/', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
@@ -162,8 +162,8 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterFullQuest() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(FULL_QUEST_DOWNLOAD, folder + '\\GrasscutterQuests.zip', async () => {
|
||||
await unzip(folder + '\\GrasscutterQuests.zip', folder + '\\', true)
|
||||
this.props.downloadManager.addDownload(FULL_QUEST_DOWNLOAD, folder + '/GrasscutterQuests.zip', async () => {
|
||||
await unzip(folder + '/GrasscutterQuests.zip', folder + '/', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
@@ -172,8 +172,8 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterFull50() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(FULL_50_DOWNLOAD, folder + '\\Grasscutter50.zip', async () => {
|
||||
await unzip(folder + '\\Grasscutter50.zip', folder + '\\', true)
|
||||
this.props.downloadManager.addDownload(FULL_50_DOWNLOAD, folder + '/Grasscutter50.zip', async () => {
|
||||
await unzip(folder + '/Grasscutter50.zip', folder + '/', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
@@ -182,8 +182,8 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterStableRepo() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
|
||||
await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
|
||||
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '/grasscutter_repo.zip', async () => {
|
||||
await unzip(folder + '/grasscutter_repo.zip', folder + '/', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
@@ -192,8 +192,8 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterDevRepo() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
|
||||
await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
|
||||
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '/grasscutter_repo.zip', async () => {
|
||||
await unzip(folder + '/grasscutter_repo.zip', folder + '/', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
@@ -202,8 +202,8 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterUnstable() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(UNSTABLE_DOWNLOAD, folder + '\\grasscutter.zip', async () => {
|
||||
await unzip(folder + '\\grasscutter.zip', folder + '\\', true)
|
||||
this.props.downloadManager.addDownload(UNSTABLE_DOWNLOAD, folder + '/grasscutter.zip', async () => {
|
||||
await unzip(folder + '/grasscutter.zip', folder + '/', true)
|
||||
this.toggleButtons
|
||||
})
|
||||
|
||||
@@ -212,8 +212,8 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterLatest() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', async () => {
|
||||
await unzip(folder + '\\grasscutter.zip', folder + '\\', true)
|
||||
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '/grasscutter.zip', async () => {
|
||||
await unzip(folder + '/grasscutter.zip', folder + '/', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
@@ -240,22 +240,22 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
)
|
||||
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '\\resources.zip', async () => {
|
||||
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '/resources.zip', async () => {
|
||||
// Delete the existing folder if it exists
|
||||
if (
|
||||
await invoke('dir_exists', {
|
||||
path: folder + '\\resources',
|
||||
path: folder + '/resources',
|
||||
})
|
||||
) {
|
||||
await invoke('dir_delete', {
|
||||
path: folder + '\\resources',
|
||||
path: folder + '/resources',
|
||||
})
|
||||
}
|
||||
|
||||
await unzip(folder + '\\resources.zip', folder + '\\', true)
|
||||
await unzip(folder + '/resources.zip', folder + '/', true)
|
||||
// Rename folder to resources
|
||||
invoke('rename', {
|
||||
path: folder + '\\Resources',
|
||||
path: folder + '/Resources',
|
||||
newName: 'resources',
|
||||
})
|
||||
|
||||
@@ -274,8 +274,8 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
const folder = await this.getCultivationFolder()
|
||||
|
||||
this.props.downloadManager.addDownload(MIGOTO_DOWNLOAD, folder + '\\GIMI.zip', async () => {
|
||||
await unzip(folder + '\\GIMI.zip', folder + '\\', true, true)
|
||||
this.props.downloadManager.addDownload(MIGOTO_DOWNLOAD, folder + '/GIMI.zip', async () => {
|
||||
await unzip(folder + '/GIMI.zip', folder + '/', true, true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
@@ -285,8 +285,8 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
async downloadMigotoFallback() {
|
||||
const folder = await this.getCultivationFolder()
|
||||
|
||||
this.props.downloadManager.addDownload(MIGOTO_FALLBACK, folder + '\\GIMI7.zip', async () => {
|
||||
await unzip(folder + '\\GIMI7.zip', folder + '\\', true, true)
|
||||
this.props.downloadManager.addDownload(MIGOTO_FALLBACK, folder + '/GIMI7.zip', async () => {
|
||||
await unzip(folder + '/GIMI7.zip', folder + '/', true, true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import Menu from './Menu'
|
||||
|
||||
import './ExtrasMenu.css'
|
||||
import BigButton from '../common/BigButton'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import Tr from '../../../utils/language'
|
||||
import { getGameExecutable } from '../../../utils/game'
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGame() {
|
||||
const folder = this.state.gameDownloadFolder
|
||||
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', async () => {
|
||||
await unzip(folder + '\\game.zip', folder + '\\', true)
|
||||
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '/game.zip', async () => {
|
||||
await unzip(folder + '/game.zip', folder + '/', true)
|
||||
this.setState({
|
||||
gameDownloading: false,
|
||||
})
|
||||
|
||||
@@ -20,3 +20,8 @@
|
||||
.OptionSection .HelpButton img {
|
||||
filter: invert(0%) sepia(91%) saturate(7464%) hue-rotate(101deg) brightness(0%) contrast(107%);
|
||||
}
|
||||
|
||||
input#profile_name {
|
||||
height: 25px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import React from 'react'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import DirInput from '../common/DirInput'
|
||||
import Menu from './Menu'
|
||||
import Tr, { getLanguages } from '../../../utils/language'
|
||||
import { setConfigOption, getConfig, getConfigOption, Configuration } from '../../../utils/configuration'
|
||||
import {
|
||||
setConfigOption,
|
||||
getConfig,
|
||||
getConfigOption,
|
||||
Configuration,
|
||||
saveNewProfileConfig,
|
||||
setProfileOption,
|
||||
} from '../../../utils/configuration'
|
||||
import Checkbox from '../common/Checkbox'
|
||||
import Divider from './Divider'
|
||||
import { getThemeList } from '../../../utils/themes'
|
||||
@@ -16,7 +23,7 @@ import DownloadHandler from '../../../utils/download'
|
||||
import * as meta from '../../../utils/rsa'
|
||||
import HelpButton from '../common/HelpButton'
|
||||
import SmallButton from '../common/SmallButton'
|
||||
import { ask, confirm } from '@tauri-apps/api/dialog'
|
||||
import { ask, confirm } from '@tauri-apps/plugin-dialog'
|
||||
import TextInput from '../common/TextInput'
|
||||
import { unzip } from '../../../utils/zipUtils'
|
||||
import { getGameExecutable } from '../../../utils/game'
|
||||
@@ -57,6 +64,8 @@ interface IState {
|
||||
launch_args: string
|
||||
offline_mode: boolean
|
||||
newer_game: boolean
|
||||
show_version: boolean
|
||||
profile_name: string
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: string
|
||||
@@ -95,6 +104,8 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
launch_args: '',
|
||||
offline_mode: false,
|
||||
newer_game: false,
|
||||
show_version: true,
|
||||
profile_name: '',
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: GrasscutterElevation.None,
|
||||
@@ -118,6 +129,9 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
this.addMigotoDelay = this.addMigotoDelay.bind(this)
|
||||
this.toggleUnElevatedGame = this.toggleUnElevatedGame.bind(this)
|
||||
this.setLaunchArgs = this.setLaunchArgs.bind(this)
|
||||
this.toggleShowVersion = this.toggleShowVersion.bind(this)
|
||||
this.setProfileName = this.setProfileName.bind(this)
|
||||
this.saveProfile = this.saveProfile.bind(this)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
@@ -156,6 +170,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
launch_args: config.launch_args,
|
||||
offline_mode: config.offline_mode || false,
|
||||
newer_game: config.newer_game || false,
|
||||
show_version: config.show_version || false,
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: config.grasscutter_elevation || GrasscutterElevation.None,
|
||||
@@ -350,6 +365,17 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
})
|
||||
}
|
||||
|
||||
async toggleShowVersion() {
|
||||
const changedVal = !(await getConfigOption('show_version'))
|
||||
await setConfigOption('show_version', changedVal)
|
||||
|
||||
this.setState({
|
||||
show_version: changedVal,
|
||||
})
|
||||
|
||||
emit('set_config', { show_version: changedVal })
|
||||
}
|
||||
|
||||
async setGCElevation(value: string) {
|
||||
setConfigOption('grasscutter_elevation', value)
|
||||
|
||||
@@ -366,7 +392,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
if (
|
||||
!(await ask(
|
||||
'Set delay for 3dmigoto loader? This is specifically made for GIMI v6 and earlier. Using it on latest GIMI or SRMI will cause issues!!! \n\nWould you like to continue?',
|
||||
{ title: 'GIMI Delay', type: 'warning' }
|
||||
{ title: 'GIMI Delay', kind: 'warning' }
|
||||
))
|
||||
) {
|
||||
return
|
||||
@@ -379,7 +405,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
|
||||
async installCert() {
|
||||
await invoke('generate_ca_files', {
|
||||
path: (await dataDir()) + 'cultivation',
|
||||
path: (await dataDir()) + '/cultivation',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -419,7 +445,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
// Set to default if not set
|
||||
if (!path || path === '') {
|
||||
const appdata = await dataDir()
|
||||
folderPath = appdata + 'cultivation\\grasscutter'
|
||||
folderPath = appdata + '/cultivation/grasscutter'
|
||||
}
|
||||
|
||||
if (path.includes('/')) {
|
||||
@@ -437,7 +463,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
// Check if resources zip exists
|
||||
if (
|
||||
!(await invoke('dir_exists', {
|
||||
path: folderPath + '\\GC-Resources-4.0.zip',
|
||||
path: folderPath + '/GC-Resources-4.0.zip',
|
||||
}))
|
||||
) {
|
||||
alert('Resources are already unzipped or do not exist! Ensure your resources zip is named "GC-Resources-4.0.zip"')
|
||||
@@ -449,10 +475,10 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
)
|
||||
|
||||
// Unzip resources
|
||||
await unzip(folderPath + '\\GC-Resources-4.0.zip', folderPath + '\\', true)
|
||||
await unzip(folderPath + '/GC-Resources-4.0.zip', folderPath + '/', true)
|
||||
// Rename folder to resources
|
||||
invoke('rename', {
|
||||
path: folderPath + '\\Resources',
|
||||
path: folderPath + '/Resources',
|
||||
newName: 'resources',
|
||||
})
|
||||
|
||||
@@ -487,6 +513,28 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
})
|
||||
}
|
||||
|
||||
setProfileName(text: string) {
|
||||
this.setState({
|
||||
profile_name: text,
|
||||
})
|
||||
}
|
||||
|
||||
async saveProfile() {
|
||||
if (this.state.profile_name == '') {
|
||||
alert('No name set')
|
||||
return
|
||||
}
|
||||
|
||||
const config = await getConfig()
|
||||
await saveNewProfileConfig(config, this.state.profile_name)
|
||||
await setProfileOption('profile', this.state.profile_name)
|
||||
this.setState({
|
||||
profile_name: '',
|
||||
})
|
||||
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Menu closeFn={this.props.closeFn} className="Options" heading="Options">
|
||||
@@ -659,6 +707,24 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className="OptionSection" id="profileConfigContainer">
|
||||
<div className="OptionLabel" id="menuOptionsLabelProfile">
|
||||
<Tr text="options.save_profile" />
|
||||
</div>
|
||||
<TextInput
|
||||
id="profile_name"
|
||||
key="profile_name"
|
||||
placeholder={'Profile name...'}
|
||||
onChange={this.setProfileName}
|
||||
initalValue={''}
|
||||
/>
|
||||
<BigButton onClick={this.saveProfile} id="saveProfile">
|
||||
{'Save'}
|
||||
</BigButton>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className="OptionSection" id="menuOptionsContainerGCWGame">
|
||||
<div className="OptionLabel" id="menuOptionsLabelGCWDame">
|
||||
<Tr text="options.grasscutter_with_game" />
|
||||
@@ -711,19 +777,14 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div className="OptionSection" id="menuOptionsContainerNewerGame">
|
||||
<div className="OptionLabel" id="menuOptionsLabelNewerGame">
|
||||
<Tr text="Patch Mihoyonet" />
|
||||
<div className="OptionSection" id="menuOptionsContainerShowVer">
|
||||
<div className="OptionLabel" id="menuOptionsLabelShowVer">
|
||||
<Tr text="options.show_version" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxNewerGame">
|
||||
<Checkbox
|
||||
onChange={() => this.toggleOption('newer_game')}
|
||||
checked={this.state?.newer_game}
|
||||
id="newerGame"
|
||||
/>
|
||||
<div className="OptionValue" id="menuOptionsButtonShowVer">
|
||||
<Checkbox onChange={() => this.toggleShowVersion()} checked={this.state.show_version} id="showVer" />
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
|
||||
@@ -7,9 +7,10 @@ import Like from '../../../resources/icons/like.svg'
|
||||
import Eye from '../../../resources/icons/eye.svg'
|
||||
import Download from '../../../resources/icons/download.svg'
|
||||
import Folder from '../../../resources/icons/folder.svg'
|
||||
import { shell } from '@tauri-apps/api'
|
||||
import {} from '@tauri-apps/api'
|
||||
import Checkbox from '../common/Checkbox'
|
||||
import { disableMod, enableMod, modIsEnabled } from '../../../utils/mods'
|
||||
import * as shell from '@tauri-apps/plugin-shell'
|
||||
|
||||
interface IProps {
|
||||
mod: ModData | PartialModData
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable indent */
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import React from 'react'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import React, { JSX } from 'react'
|
||||
import Tr from '../../../utils/language'
|
||||
import { getConfig, getConfigOption } from '../../../utils/configuration'
|
||||
|
||||
@@ -143,7 +143,7 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
<tr>
|
||||
<td>
|
||||
Work in progress area! These numbers may be outdated, so please do not use them as reference. Latest
|
||||
version: Grasscutter 1.7.4 - Cultivation 1.6.1
|
||||
version: Grasscutter 1.7.4 (4.0) / Forks (6.1) - Cultivation 1.7.2
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { fs } from '@tauri-apps/api'
|
||||
import {} from '@tauri-apps/api'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import * as fs from '@tauri-apps/plugin-fs'
|
||||
|
||||
let configFilePath: string
|
||||
let defaultConfig: Configuration
|
||||
;(async () => {
|
||||
defaultConfig = {
|
||||
toggle_grasscutter: true,
|
||||
game_install_path: 'C:\\Program Files\\Genshin Impact\\Genshin Impact game\\GenshinImpact.exe',
|
||||
game_install_path: 'C:/Program Files/Genshin Impact/Genshin Impact game/GenshinImpact.exe',
|
||||
grasscutter_with_game: false,
|
||||
grasscutter_path: '',
|
||||
java_path: '',
|
||||
@@ -31,6 +32,8 @@ let defaultConfig: Configuration
|
||||
launch_args: '',
|
||||
offline_mode: false,
|
||||
newer_game: false,
|
||||
show_version: true,
|
||||
profile: 'default',
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: 'None',
|
||||
@@ -68,6 +71,8 @@ export interface Configuration {
|
||||
launch_args: string
|
||||
offline_mode: boolean
|
||||
newer_game: boolean
|
||||
show_version: boolean
|
||||
profile: string
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: string
|
||||
@@ -90,6 +95,15 @@ export async function setConfigOption<K extends keyof Configuration>(key: K, val
|
||||
await saveConfig(<Configuration>config)
|
||||
}
|
||||
|
||||
export async function setProfileOption<K extends keyof Configuration>(key: K, value: Configuration[K]): Promise<void> {
|
||||
const config = await getConfig()
|
||||
config[key] = value
|
||||
const defaultConfig = await getDefaultConfig()
|
||||
defaultConfig[key] = value
|
||||
|
||||
await saveProfileConfig(<Configuration>defaultConfig)
|
||||
}
|
||||
|
||||
export async function getConfigOption<K extends keyof Configuration>(key: K): Promise<Configuration[K]> {
|
||||
const config = await getConfig()
|
||||
const defaults = defaultConfig
|
||||
@@ -113,23 +127,79 @@ export async function getConfig() {
|
||||
return parsed
|
||||
}
|
||||
|
||||
export async function getDefaultConfig() {
|
||||
const raw = await readDefaultConfigFile()
|
||||
let parsed: Configuration = defaultConfig
|
||||
|
||||
try {
|
||||
parsed = <Configuration>JSON.parse(raw)
|
||||
} catch (e) {
|
||||
// We could not open the file
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
export async function saveConfig(obj: Configuration) {
|
||||
const raw = JSON.stringify(obj)
|
||||
|
||||
await writeConfigFile(raw)
|
||||
}
|
||||
|
||||
export async function saveProfileConfig(obj: Configuration) {
|
||||
const local = await dataDir()
|
||||
const raw = JSON.stringify(obj)
|
||||
const prevPath = configFilePath
|
||||
configFilePath = local + '/cultivation/configuration.json'
|
||||
await writeConfigFile(raw)
|
||||
|
||||
configFilePath = prevPath
|
||||
}
|
||||
|
||||
export async function saveNewProfileConfig(obj: Configuration, prof: string) {
|
||||
obj['profile'] = prof
|
||||
const local = await dataDir()
|
||||
const raw = JSON.stringify(obj)
|
||||
configFilePath = local + '/cultivation/profiles/' + obj['profile'] + '.json'
|
||||
|
||||
await fs.writeTextFile(configFilePath, raw)
|
||||
}
|
||||
|
||||
async function readConfigFile() {
|
||||
const local = await dataDir()
|
||||
|
||||
if (!configFilePath) configFilePath = local + 'cultivation/configuration.json'
|
||||
if (!configFilePath) {
|
||||
configFilePath = local + '/cultivation/configuration.json'
|
||||
}
|
||||
|
||||
const dataFiles = await fs.readDir(local + '/cultivation')
|
||||
|
||||
// Ensure config exists
|
||||
if (!dataFiles.find((fileOrDir) => fileOrDir?.name === 'configuration.json')) {
|
||||
// Create config file
|
||||
await fs.writeTextFile(configFilePath, JSON.stringify(defaultConfig))
|
||||
}
|
||||
|
||||
// Read existing config to get profile name
|
||||
const raw = await fs.readTextFile(configFilePath)
|
||||
const cfg = <Configuration>JSON.parse(raw)
|
||||
// Switch file to config-specified profile
|
||||
let pf = cfg['profile']
|
||||
if (pf && pf != 'default') {
|
||||
const pff = pf
|
||||
pf = 'profiles/' + pff + '.json'
|
||||
} else {
|
||||
pf = 'configuration.json'
|
||||
}
|
||||
configFilePath = local + '/cultivation/' + pf
|
||||
|
||||
// Ensure Cultivation dir exists
|
||||
const dirs = await fs.readDir(local)
|
||||
|
||||
if (!dirs.find((fileOrDir) => fileOrDir?.name === 'cultivation')) {
|
||||
// Create dir
|
||||
await fs.createDir(local + 'cultivation').catch((e) => console.log(e))
|
||||
await fs.mkdir(local + '/cultivation').catch((e) => console.log(e))
|
||||
}
|
||||
|
||||
const innerDirs = await fs.readDir(local + '/cultivation')
|
||||
@@ -137,30 +207,22 @@ async function readConfigFile() {
|
||||
// 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))
|
||||
}
|
||||
|
||||
const dataFiles = await fs.readDir(local + 'cultivation')
|
||||
|
||||
// Ensure config exists
|
||||
if (!dataFiles.find((fileOrDir) => fileOrDir?.name === 'configuration.json')) {
|
||||
// Create config file
|
||||
const file: fs.FsTextFileOption = {
|
||||
path: configFilePath,
|
||||
contents: JSON.stringify(defaultConfig),
|
||||
}
|
||||
|
||||
await fs.writeFile(file)
|
||||
await fs.mkdir(local + '/cultivation/grasscutter').catch((e) => console.log(e))
|
||||
}
|
||||
|
||||
// Finally, read the file
|
||||
return await fs.readTextFile(configFilePath)
|
||||
}
|
||||
|
||||
async function readDefaultConfigFile() {
|
||||
const local = await dataDir()
|
||||
configFilePath = local + '/cultivation/configuration.json'
|
||||
return await fs.readTextFile(configFilePath)
|
||||
}
|
||||
|
||||
async function writeConfigFile(raw: string) {
|
||||
// All external config functions call readConfigFile, which ensure files exists
|
||||
await fs.writeFile({
|
||||
path: configFilePath,
|
||||
contents: raw,
|
||||
})
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(raw)
|
||||
await fs.writeFile(configFilePath, data)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { byteToString } from './string'
|
||||
|
||||
@@ -57,7 +57,6 @@ export default class DownloadHandler {
|
||||
|
||||
// Call onFinish callback
|
||||
if (this.downloads[index]?.onFinish) {
|
||||
// @ts-expect-error onFinish is checked for existence before being called
|
||||
this.downloads[index]?.onFinish()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,110 +1,34 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { getConfig } from './configuration'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { basename, dirname, join } from '@tauri-apps/api/path'
|
||||
import { getConfigOption } from './configuration'
|
||||
|
||||
export const getGrasscutterJar = () => getConfigOption('grasscutter_path')
|
||||
|
||||
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 getGrasscutterJar() {
|
||||
const config = await getConfig()
|
||||
|
||||
if (!config.grasscutter_path) {
|
||||
return null
|
||||
}
|
||||
|
||||
const pathArr = config.grasscutter_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
|
||||
}
|
||||
|
||||
export async function getGameDataFolder() {
|
||||
const gameExec = await getGameExecutable()
|
||||
|
||||
if (!gameExec) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (await getGameFolder()) + '\\' + gameExec.replace('.exe', '_Data')
|
||||
const exe_path = await getConfigOption('game_install_path')
|
||||
return await basename(exe_path)
|
||||
}
|
||||
|
||||
export async function getGameVersion() {
|
||||
const GameData = await getGameDataFolder()
|
||||
const platform = await invoke('get_platform')
|
||||
|
||||
if (!GameData) {
|
||||
return null
|
||||
}
|
||||
|
||||
let hasAsb = await invoke('dir_exists', {
|
||||
path: GameData + '\\StreamingAssets\\asb_settings.json',
|
||||
})
|
||||
|
||||
if (platform != 'windows') {
|
||||
hasAsb = await invoke('dir_exists', {
|
||||
path: GameData + '/StreamingAssets/asb_settings.json',
|
||||
})
|
||||
}
|
||||
const execPath = await getConfigOption('game_install_path')
|
||||
const rootPath = await dirname(execPath)
|
||||
const baseName = await basename(execPath, '.exe')
|
||||
const datapath = await join(rootPath, `${baseName}_Data`)
|
||||
const asbPath = await join(datapath, 'StreamingAssets', 'asb_settings.json')
|
||||
const hasAsb = await invoke<boolean>('dir_exists', { path: asbPath })
|
||||
|
||||
if (!hasAsb) {
|
||||
// For games that cannot determine game version
|
||||
let otherGameVer: string = await invoke('read_file', {
|
||||
path: GameData + '\\StreamingAssets\\BinaryVersion.bytes',
|
||||
})
|
||||
const versionFile = await join(datapath, 'StreamingAssets', 'BinaryVersion.bytes')
|
||||
const rawVersion = await invoke<string>('read_file', { path: versionFile })
|
||||
if (!rawVersion) return null
|
||||
|
||||
if (platform != 'windows') {
|
||||
otherGameVer = await invoke('read_file', {
|
||||
path: GameData + '/StreamingAssets/BinaryVersion.bytes',
|
||||
})
|
||||
}
|
||||
|
||||
const versionRaw = otherGameVer.split('.')
|
||||
const version = {
|
||||
major: parseInt(versionRaw[0]),
|
||||
minor: parseInt(versionRaw[1]),
|
||||
// This will probably never matter, just use major/minor. If needed, full version values are near EOF
|
||||
release: 0,
|
||||
}
|
||||
|
||||
if (otherGameVer == null || otherGameVer.length < 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
return version
|
||||
const [major, minor] = rawVersion.split('.').map(Number)
|
||||
return { major, minor, release: 0 }
|
||||
}
|
||||
|
||||
const settings = JSON.parse(
|
||||
await invoke('read_file', {
|
||||
path: GameData + '\\StreamingAssets\\asb_settings.json',
|
||||
})
|
||||
)
|
||||
const settings = JSON.parse(await invoke<string>('read_file', { path: asbPath }))
|
||||
const [major, minorRelease] = settings.variance.split('.')
|
||||
const [minor, release] = minorRelease.split('_').map(Number)
|
||||
|
||||
const versionRaw = settings.variance.split('.')
|
||||
const version = {
|
||||
major: parseInt(versionRaw[0]),
|
||||
minor: parseInt(versionRaw[1].split('_')[0]),
|
||||
release: parseInt(versionRaw[1].split('_')[1]),
|
||||
}
|
||||
|
||||
return version
|
||||
return { major: parseInt(major), minor, release }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { getConfigOption } from './configuration'
|
||||
|
||||
// Generated with https://transform.tools/json-to-typescript I'm lazy cry about it
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import React from 'react'
|
||||
import { getConfigOption } from './configuration'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { getConfigOption } from './configuration'
|
||||
|
||||
export async function getModsFolder() {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { ReportHandler } from 'web-vitals'
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry)
|
||||
getFID(onPerfEntry)
|
||||
getFCP(onPerfEntry)
|
||||
getLCP(onPerfEntry)
|
||||
getTTFB(onPerfEntry)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default reportWebVitals
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
// Patch file from: https://github.com/34736384/RSAPatch/
|
||||
|
||||
export async function patchGame(newerGame: boolean, version: string) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
export async function toggleEncryption(path: string) {
|
||||
let serverConf
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { getConfig, setConfigOption } from './configuration'
|
||||
|
||||
interface Theme {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
|
||||
interface UnzipPayload {
|
||||
|
||||
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -1,20 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
10
tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
32
vite.config.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
// @ts-expect-error process is a nodejs global
|
||||
const host = process.env.TAURI_DEV_HOST;
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(async () => ({
|
||||
plugins: [react()],
|
||||
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
//
|
||||
// 1. prevent Vite from obscuring rust errors
|
||||
clearScreen: false,
|
||||
// 2. tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 1420,
|
||||
strictPort: true,
|
||||
host: host || false,
|
||||
hmr: host
|
||||
? {
|
||||
protocol: "ws",
|
||||
host,
|
||||
port: 1421,
|
||||
}
|
||||
: undefined,
|
||||
watch: {
|
||||
// 3. tell Vite to ignore watching `src-tauri`
|
||||
ignored: ["**/src-tauri/**"],
|
||||
},
|
||||
},
|
||||
}));
|
||||