Compare commits

...

419 Commits

Author SHA1 Message Date
SpikeHD
9f0567da6a Merge pull request #146 from NotThorny/MoreFixes
More fixes
2023-04-09 12:20:30 -07:00
SpikeHD
56453ff55b Merge pull request #147 from thecutefinder/patch-2
GNU-ify
2023-04-09 12:20:12 -07:00
Colleiflower
61c5cc8d57 GNU-ify
Maintains consistency with other Grasscutter projects, e.g. https://github.com/Grasscutters/Grasscutter/pull/2080
2023-04-09 07:06:30 +00:00
SpikeHD
7bbc7e3c10 Update README.md 2023-04-08 09:46:15 -07:00
SpikeHD
5b7c1307d9 Update README.md 2023-04-08 09:40:17 -07:00
Thoronium
096992572c Add 7z support 2023-04-07 13:12:41 -06:00
Thoronium
68f0cce154 Run prettier 2023-04-07 11:23:35 -06:00
Thoronium
19db69646f Remove whitespace 2023-04-07 10:45:05 -06:00
Thoronium
82f40186fe Translations for new help text 2023-04-07 10:45:05 -06:00
Thoronium
d7b2aa25cc Add mods download 2023-04-07 10:32:53 -06:00
Thoronium
3356bddb42 Fix horny blur on installed mods 2023-04-07 10:32:53 -06:00
Thoronium
db11cf7907 Change encryption to checkbox 2023-04-07 10:32:53 -06:00
Thoronium
891dbb41aa Add delay button for 3dmigoto 2023-04-07 10:32:53 -06:00
Thoronium
cf6ec3da82 Test permission change for setting proxy 2023-04-07 10:32:41 -06:00
SpikeHD
b2d6f390fb Merge pull request #144 from NotThorny/IssuesFixes
Bugfixes
2023-04-06 11:42:09 -07:00
Thoronium
a920d70e53 Run prettier 2023-04-05 22:06:54 -06:00
nana
d2e7759eec Create pt-br.json
Adds a brazillian portuguese translation file.
2023-04-05 22:05:21 -06:00
Thoronium
77380e357c Show wrench instantly on setting 3dmigoto 2023-04-05 22:02:50 -06:00
Thoronium
33ddc36741 Only intercept game traffic
Bump version
2023-04-05 22:02:38 -06:00
SpikeHD
bbd18d33e9 Merge pull request #143 from ariqpradipa/src-tauri/src/lang
Refactor path concatenation and make error handling src-tauri/src/lang.rs
2023-04-04 08:29:21 -07:00
Ariq Pradipa Santoso
b66f01232b revert: lang path 2023-04-04 12:16:33 +07:00
Ariq Pradipa Santoso
b6a2e4dbc0 fix: improve error handling in get_languages() 2023-04-04 01:09:54 +07:00
Ariq Pradipa Santoso
45ee8fbb23 refactor: optimze path concatenation in get_lang() 2023-04-04 01:08:34 +07:00
SpikeHD
78c2a8d0c8 Merge pull request #142 from NotThorny/AutoMongo
Automation & Bugfix
2023-04-03 09:13:24 -07:00
Thoronium
fe420e9837 Translations for MongoDB setting 2023-04-02 22:49:45 -06:00
Thoronium
57576f399b Change AIO zip name 2023-04-02 22:36:09 -06:00
Thoronium
8d45b3d11d Log when a service isn't found 2023-04-02 22:01:03 -06:00
Thoronium
1ff96f9c7e Merge branch 'AutoMongo' of https://github.com/NotThorny/Cultivation into AutoMongo 2023-04-02 21:15:33 -06:00
Thoronium
e834022ca7 Automation
- MongoDB tied to Grasscutter rather than game
- Grasscutter restarts when changing encryption if it is running
- AIO link updates without needing manual edits
2023-04-02 21:15:25 -06:00
Thoronium
c72e502603 Update tauri.conf.json 2023-04-02 13:35:51 -06:00
Thoronium
5d6bd72083 Fix 3dmigoto path setting
Apparently broken since 1.0.21 whoops
2023-04-02 13:34:56 -06:00
Thoronium
ef3ba2a045 Update Grasscutter versions
Add auto MongoDB option
2023-04-01 12:41:50 -06:00
SpikeHD
90b86b42d0 codefix 2023-03-23 08:53:42 -07:00
SpikeHD
01d12178ba Merge pull request #138 from NotThorny/CultivationP
Ease-of-use & QoL Additions, Updates, & Fixes
2023-03-23 08:28:40 -07:00
Thoronium
b67e0992f1 Add back frontend-checks 2023-03-23 01:02:00 -06:00
Thoronium
a949754dce Update patch version 2023-03-23 00:54:13 -06:00
Thoronium
cf536b853a De-Thornify 2023-03-23 00:31:44 -06:00
Thoronium
19c8537e91 Bug fix 2023-03-22 23:11:10 -06:00
Thoronium
8a62a5a8c7 Notify on encryption change 2023-03-09 16:30:54 -07:00
Thoronium
d56c6bc069 Bump version 2023-03-05 19:38:40 -07:00
Thoronium
ea73feb8e9 Full offline compatibility 2023-03-05 19:13:09 -07:00
Thoronium
abf7a428f6 Update README.md
Update info about issues page
2023-03-04 13:21:08 -07:00
Thoronium
7bd3493366 Merge branch 'FixedCultivation' of https://github.com/NotThorny/Cultivation into FixedCultivation 2023-03-01 21:05:23 -07:00
Thoronium
20a80e5625 Fix could not patch on very first run 2023-03-01 21:05:16 -07:00
Thoronium
4a8db8eb0f Update README.md 2023-03-01 15:30:30 -07:00
Thoronium
f44dfeb79d Merge branch 'FixedCultivation' of https://github.com/NotThorny/Cultivation into FixedCultivation 2023-02-28 10:28:08 -07:00
Thoronium
098ba63066 Small UI fix 2023-02-28 10:28:02 -07:00
Thoronium
b8255eb5bc Update README.md 2023-02-27 21:15:16 -07:00
Thoronium
812aa64c5e Reduce fallback bg size 2023-02-27 21:05:42 -07:00
Thoronium
b3aaf5c5f7 Auto RSA patching
Add fallback bg for when api is down
2023-02-27 20:58:17 -07:00
Thoronium
9e6029faf3 Fix patch paths 2023-02-27 19:26:04 -07:00
Thoronium
b18f7a0cf6 Quick fix for rsa.ts 2023-02-27 17:29:19 -07:00
Thoronium
62a97d86cb Automatically apply RSA patch 2023-02-27 17:15:21 -07:00
Thoronium
55fa0a8d3c Update README.md
Update images
2023-02-27 00:00:40 -07:00
Thoronium
2aac7fc022 Update README_zh-CN.md
Update images
2023-02-27 00:00:30 -07:00
Thoronium
ddaf24590c Update README_zh-TW.md
Update images
2023-02-27 00:00:23 -07:00
Thoronium
44f5cac2bf Update README_zh-TW.md
機器翻譯,如有錯誤請見諒
2023-02-26 23:50:56 -07:00
Thoronium
e1a519cc5f Update README_zh-CN.md 2023-02-26 23:45:12 -07:00
Thoronium
3dd1ec2674 Replace AIO link 2023-02-26 19:46:39 -07:00
Thoronium
438596f4e2 Update README.md 2023-02-26 19:31:06 -07:00
SpikeHD
af5c9229d4 Merge pull request #135 from NotThorny/3dmigotoPathFix
Fix 3dmigoto Path Setting
2023-02-26 17:44:51 -08:00
SpikeHD
fb842ccc86 Merge pull request #130 from NotThorny/Korean-Lang
Add Korean Translation by @koreako12
2023-02-26 17:44:39 -08:00
Thoronium
27d30a183c Update languages
Small fixes
2023-02-26 16:54:36 -07:00
Thoronium
182943fd69 Syntax 2023-02-26 15:26:10 -07:00
Thoronium
b352b16dc9 Fix syntax 2023-02-26 15:12:45 -07:00
Thoronium
b1b18c75a1 Fix encryption button on jar set 2023-02-26 15:01:16 -07:00
Thoronium
7c53669fc4 Update encryption button on jar set
Update README
2023-02-26 14:37:13 -07:00
Thoronium
249f07927d Fix check 2023-02-26 12:40:14 -07:00
Thoronium
090e1a888d Fix jar setting from full build 2023-02-26 12:02:21 -07:00
Thoronium
5dfa98c9ea Update languages
Format downloads menu
2023-02-26 03:32:25 -07:00
Thoronium
088d007816 Revert handle and add logging
Server.ts isn't actually printing the error to the user anyways so there was no point in trying to fix it
2023-02-26 03:02:52 -07:00
Thoronium
d49fdacf2a Error handle fix 2023-02-26 02:10:55 -07:00
Thoronium
8efd601eda Try to fix err handle 2023-02-26 01:58:00 -07:00
Thoronium
f2e3c69c0f Bug fixes 2023-02-26 01:44:01 -07:00
Thoronium
85a16e3407 Undo unnecessary extra check 2023-02-26 01:20:29 -07:00
Thoronium
596c5b85b1 Correct error message 2023-02-26 01:12:57 -07:00
Thoronium
1115283df8 Fix another typo 2023-02-26 01:09:43 -07:00
Thoronium
696a8a1dec Fix typo 2023-02-26 00:56:58 -07:00
Thoronium
1be06b0a6c Try to fix css 2023-02-26 00:29:15 -07:00
Thoronium
7f71549831 Add download menu translations
Update download menu formatting
Better error logging
2023-02-26 00:15:42 -07:00
Thoronium
08d9db05a5 Remove unused workflow 2023-02-25 22:43:47 -07:00
Thoronium
cd2b8aee12 Correct menu text 2023-02-25 21:36:10 -07:00
Thoronium
ec611f9d7d News testing 2023-02-25 21:00:57 -07:00
Thoronium
d6b1c7a613 Update downloads menu 2023-02-25 20:17:37 -07:00
Thoronium
9b1d3594ed Small fixes 2023-02-25 19:21:40 -07:00
Thoronium
3848c39743 Small fixes
Remove stable download buttons
2023-02-25 18:37:36 -07:00
Thoronium
7831bd48dc Add full build download to menu 2023-02-25 18:12:08 -07:00
Thoronium
86d15cd335 Try extraction message fix
Change version to distinguish from official release
Slightly change metadata message
2023-02-25 16:53:18 -07:00
Thoronium
bf880ac297 Better default config
Resources update
Try to actually show news on startup
2023-02-25 16:32:49 -07:00
Thoronium
e1637cebef Merge pull request #1 from NotThorny/Korean-Lang
Upload Korean TL
2023-02-25 15:13:13 -07:00
Thoronium
d7f3218657 Fix path setting 2023-02-25 15:07:45 -07:00
Thoronium
0dede9b17c Fix path setting 2023-02-22 15:06:05 -07:00
Thoronium
ec7b0904d2 Upload Korean TL 2023-01-30 19:00:17 -07:00
Thoronium
937faf85e2 Update resources download 2023-01-30 18:45:13 -07:00
dependabot[bot]
6c40044931 Bump tauri from 1.0.6 to 1.0.7 in /src-tauri
Bumps [tauri](https://github.com/tauri-apps/tauri) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-v1.0.6...tauri-v1.0.7)

---
updated-dependencies:
- dependency-name: tauri
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-14 19:21:49 -08:00
dependabot[bot]
3d2425fb3d Bump loader-utils from 2.0.2 to 2.0.4
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.2 to 2.0.4.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.2...v2.0.4)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-20 12:13:37 -05:00
ChapterII
f7c8059f4c Updated Simplified Chinese translations 2022-11-19 18:08:39 -05:00
SpikeHD
6206b6762a cleanup 2022-10-01 16:12:32 -07:00
SpikeHD
258c656815 Merge pull request #97 from pfyy/main
make passwordKey.txt use CRLF
2022-10-01 15:55:02 -07:00
pfyy
68404f5e39 make passwordKey.txt use CRLF 2022-10-01 11:28:00 +08:00
SpikeHD
8c75541939 Merge pull request #95 from pfyy/main
fix proxy uri (add query)
2022-09-30 14:36:33 -07:00
pfyy
aa32782179 fix proxy uri (add query) 2022-09-27 19:36:39 +08:00
Magix
2cda891f02 THE GREATEST CULTIVATION UPDATE OF ALL TIME
part one of removing deprecated code
2022-09-24 00:08:12 -04:00
SpikeHD
9c8ed852d1 Merge branch 'main' of github.com:Grasscutters/Cultivation 2022-09-18 17:54:20 -07:00
SpikeHD
e75f65ab56 --no-admin and fix migoto setting 2022-09-18 17:54:05 -07:00
SpikeHD
bdeda36043 Merge pull request #92 from Grasscutters/dependabot/cargo/src-tauri/tauri-1.0.6
Bump tauri from 1.0.4 to 1.0.6 in /src-tauri
2022-09-16 23:57:04 -07:00
dependabot[bot]
151e7c3919 Bump tauri from 1.0.4 to 1.0.6 in /src-tauri
Bumps [tauri](https://github.com/tauri-apps/tauri) from 1.0.4 to 1.0.6.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-v1.0.4...tauri-v1.0.6)

---
updated-dependencies:
- dependency-name: tauri
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-16 19:33:19 +00:00
SpikeHD
e6365b5592 Merge pull request #91 from trollerr/patch-1
Vietnamese Translations Update
2022-09-12 09:14:46 -07:00
Trollerr
ed76a086b6 Vietnamese Translations Update 2022-09-12 13:09:15 +07:00
SpikeHD
c4526e2ff6 version bump 2022-09-09 17:02:43 -07:00
SpikeHD
80c28669e1 conditionally wipe reg 2022-09-09 16:58:53 -07:00
SpikeHD
5e48a4772e update resources url 2022-09-09 16:58:11 -07:00
SpikeHD
10234bed5a upload MSI bundle 2022-09-04 18:55:13 -07:00
SpikeHD
22d8a23386 version bump 2022-09-04 18:35:38 -07:00
SpikeHD
6ef3e86820 build conditions 2022-09-04 18:13:57 -07:00
SpikeHD
0087801f83 fix linux check 2022-09-04 18:12:31 -07:00
SpikeHD
55be3ebc2b remove cmd requirement 2022-09-04 18:08:02 -07:00
SpikeHD
7cf1b198ff open as admin (windows only) 2022-09-04 18:05:19 -07:00
SpikeHD
fb231acaa6 easy-zip is not easy 2022-09-04 16:55:38 -07:00
SpikeHD
827b8942c9 action fixes 2022-09-04 15:24:37 -07:00
SpikeHD
0fd4376e0d change artifact names 2022-09-04 14:59:05 -07:00
SpikeHD
d6c5463619 include appimage 2022-09-04 14:40:56 -07:00
SpikeHD
9ade56dc6f fix file copy linux 2022-09-04 14:38:52 -07:00
SpikeHD
484cd36565 format 2022-09-04 14:20:36 -07:00
SpikeHD
1c27ae172e lint fixes 2022-09-04 14:18:18 -07:00
SpikeHD
541352c3fd Merge pull request #83 from 4Benj/main
Fix some filesystem related bugs
2022-09-04 14:09:03 -07:00
SpikeHD
bfbf3e77a2 prettier fix 2022-09-04 14:05:24 -07:00
SpikeHD
404e946e7c change path/command option based on platform 2022-09-04 14:00:27 -07:00
Benj
8077285e79 Game version checking 2022-09-04 20:15:05 +08:00
SpikeHD
94c1bfd104 build on push/pr 2022-09-03 22:29:31 -07:00
SpikeHD
ed473ad659 run_command for ubuntu build 2022-09-03 22:22:01 -07:00
SpikeHD
b624ef693e sudo 2022-09-03 19:11:53 -07:00
SpikeHD
c10a5cd82f get libs ubuntu 2022-09-03 19:01:21 -07:00
SpikeHD
25c6a70dc0 action name 2022-09-03 18:47:03 -07:00
SpikeHD
86c595abda ubuntu build 2022-09-03 18:46:13 -07:00
SpikeHD
35e6144733 use node 16 2022-09-03 17:57:56 -07:00
SpikeHD
565f229dac fix toolchain install 2022-09-03 17:55:01 -07:00
SpikeHD
9bd4b9ccaf clippy stuff 2022-09-03 17:52:57 -07:00
SpikeHD
6750787bf9 build action 2022-09-03 17:51:47 -07:00
SpikeHD
c0770606ae lint fixes 2022-09-03 17:22:00 -07:00
SpikeHD
4e03fec2a0 Merge branch 'main' of github.com:Grasscutters/Cultivation 2022-09-02 20:21:24 -07:00
SpikeHD
e7809be97c only show horny mode when swag mode is set 2022-09-02 20:20:49 -07:00
SpikeHD
782e350ae5 unblur when horny mode is on 2022-09-02 20:17:48 -07:00
SpikeHD
6eab66032b horny mode option 2022-09-02 20:11:14 -07:00
SpikeHD
c1842722b4 make toggles less dogshit 2022-09-02 19:56:33 -07:00
SpikeHD
31aef02d5f Update README.md 2022-08-31 11:06:46 -07:00
SpikeHD
bcdbb2ba06 Update README.md 2022-08-31 11:06:21 -07:00
Benj
ff8f35c52a Stop popping the directory when we need it later 2022-08-31 18:28:28 +08:00
Benj
a7188828fa Fix os error 123 while reading key files 2022-08-31 17:18:53 +08:00
jseniuk
db6917df5d fix merge conflict 2022-08-30 19:06:49 -07:00
jseniuk
8268c127a9 linux proxyworking probably 2022-08-30 19:02:20 -07:00
jseniuk
8fd5b895af 1-time cert retry 2022-08-30 18:02:48 -07:00
SpikeHD
10b9141815 fix compile errors 2022-08-28 21:59:40 -07:00
SpikeHD
d7783c5936 fix compile issues 2022-08-28 21:57:35 -07:00
SpikeHD
27122cd399 Merge branch 'main' of github.com:Grasscutters/Cultivation 2022-08-28 21:08:09 -07:00
SpikeHD
49740470ac wip linux proxy stuff 2022-08-28 21:07:22 -07:00
SpikeHD
7b693d7758 Update README.md 2022-08-28 19:15:59 -07:00
SpikeHD
03439c3757 launcher.exe alert 2022-08-27 19:47:28 -07:00
SpikeHD
e83ae64714 webview disclaimer for win7 users 2022-08-27 14:47:23 -07:00
SpikeHD
3ecd13d1c3 Merge branch 'main' of github.com:Grasscutters/Cultivation 2022-08-27 14:45:18 -07:00
SpikeHD
28701ba007 delete backed up meta when patch is unsuccessful against it 2022-08-27 14:43:39 -07:00
SpikeHD
3a84825cf9 Merge pull request #60 from Kawaa-qwq/main
Translation languages are supported in the README file and all matched files use Prettier code style!
2022-08-27 01:01:14 -07:00
SpikeHD
4de8a43c3a begin CLI options 2022-08-27 00:54:14 -07:00
SpikeHD
d28e0a1bc8 Merge branch 'main' of github.com:Grasscutters/Cultivation 2022-08-26 17:54:26 -07:00
SpikeHD
bf8de40caa wipe registry option 2022-08-26 17:53:45 -07:00
SpikeHD
8e7d6ee420 Merge pull request #75 from Pikachubolk/patch-1
Dutch Cultivation Translation
2022-08-26 12:08:53 -07:00
Pikachubolk
d6e9bb100b Rename nl.json to src-tauri/lang/nl.json 2022-08-26 20:55:25 +02:00
Pikachubolk
374c6abbfc Create nl.json 2022-08-26 20:51:10 +02:00
SpikeHD
a728e8ba2c Merge pull request #74 from Arikatsu/main
Fix "visual bug" (again)
2022-08-26 10:32:56 -07:00
Scald
4371804429 Update download.ts 2022-08-26 21:15:04 +05:30
SpikeHD
fa38a22117 version bump 2022-08-25 20:19:44 -07:00
SpikeHD
a7914406b4 get ALL mods at once 2022-08-25 20:18:58 -07:00
SpikeHD
57c1a7800c write game path to migoto config autmatically 2022-08-25 20:08:44 -07:00
SpikeHD
9426937a62 cleanup 2022-08-25 19:05:51 -07:00
SpikeHD
96d69d9ff6 bump initial wait for game to 10 seconds 2022-08-25 19:04:08 -07:00
SpikeHD
defc1b43bd fix all sorts of zip stuff 2022-08-25 18:57:24 -07:00
SpikeHD
01fce477ef Merge branch 'main' of github.com:Grasscutters/Cultivation 2022-08-23 23:52:21 -07:00
SpikeHD
2f610ce0af version bump 2022-08-23 23:52:11 -07:00
SpikeHD
d9772d9ffb resource link update 2022-08-23 23:47:52 -07:00
Kawaa-qwq
569c465a50 Update 2022-08-22 13:51:22 +08:00
Kawaa
8a5e66be48 更新日志 2022-08-06 10:47:32 +08:00
Kawaa
e633f7dad7 Updated instructions 2022-08-05 21:38:10 +08:00
Kawaa
bffd089192 Updated instructions 2022-08-05 21:29:47 +08:00
Kawaa
bf3d892af5 Updated instructions 2022-08-05 21:28:15 +08:00
Kawaa
424dd4ff1c Updated instructions 2022-08-05 21:22:40 +08:00
SpikeHD
93097db5cc Merge pull request #58 from Untitled/main
Chinese Simplified Translations Update
2022-08-04 10:55:53 -07:00
SpikeHD
c06aa9f885 Merge pull request #57 from Kimi898246/patch-3
Traditional Chinese | Translation Patches
2022-08-04 10:55:21 -07:00
ChapterII
f7bcad0a90 Update chs.json
Updated translations
2022-08-04 15:04:27 +08:00
Kimi
7120f846f7 Traditional Chinese | Translation Patches
hi
2022-08-04 02:18:21 +08:00
SpikeHD
f6ed37d2e4 Merge pull request #53 from DasIschBims/main
Update de.json
2022-08-02 17:21:54 -07:00
DasIschBims
99b210b3be Update de.json 2022-07-30 13:49:13 +02:00
SpikeHD
7d52766c07 Merge pull request #51 from TotalyNotOndre/patch-1
Update ru.json
2022-07-28 12:18:13 -07:00
Abdul
212d974ac6 Fixed missing separators 2022-07-28 20:23:55 +03:00
SpikeHD
a6e06e3005 revert to 50 per page 2022-07-27 21:52:26 -07:00
Abdul
56c1f2dcc2 Update ru.json
- Added and translated missing localization strings
2022-07-28 03:33:22 +03:00
SpikeHD
a843888cb8 cleanup 2022-07-27 17:01:55 -07:00
SpikeHD
281bfb5cea cheeky key path fix 2022-07-26 22:30:25 -07:00
SpikeHD
79891238b6 fix reshade 2022-07-26 20:59:33 -07:00
SpikeHD
0971f5b826 progress bar 'fix' 2022-07-26 20:49:20 -07:00
SpikeHD
7e5f3be4fa cleanup 2022-07-26 20:41:55 -07:00
SpikeHD
e29e269c4c Merge branch 'mod_management' 2022-07-26 20:39:54 -07:00
SpikeHD
8f8e37aff3 close menu after launch 2022-07-26 20:39:02 -07:00
SpikeHD
6257a2e68c non-blocking commands for reshade 2022-07-26 20:37:39 -07:00
SpikeHD
203bd40e8f reshade setting and enabling 2022-07-26 20:16:20 -07:00
SpikeHD
c64cdababa save options 2022-07-26 20:01:10 -07:00
SpikeHD
1ecd38ee9f extras menu for conditional extras 2022-07-26 19:58:16 -07:00
SpikeHD
26292984a2 download selection 2022-07-25 22:46:43 -07:00
SpikeHD
cffbcdae96 handle rar files, add back button svg 2022-07-25 22:09:24 -07:00
SpikeHD
2026e2f896 fix styling 2022-07-25 20:29:52 -07:00
SpikeHD
9566beaf29 mod enabling and disabling 2022-07-25 20:25:43 -07:00
SpikeHD
c99080168c fixes 2022-07-25 19:30:13 -07:00
SpikeHD
9fa3351747 visuals for enabling/disableing 2022-07-25 19:23:05 -07:00
SpikeHD
75f1eef587 help text for some options 2022-07-25 18:45:48 -07:00
SpikeHD
43a6348b7a cleanup 2022-07-25 18:28:06 -07:00
SpikeHD
cc74107dfe move help buttons to alerts 2022-07-25 18:26:04 -07:00
SpikeHD
afa40f437f english fallback when using other languages 2022-07-25 18:13:37 -07:00
SpikeHD
95282a3f36 remove unused 2022-07-24 21:06:31 -07:00
SpikeHD
0331bb5faf log cleanup 2022-07-24 20:59:45 -07:00
SpikeHD
0551f3e6a0 solution for loose file mods 2022-07-24 20:59:08 -07:00
SpikeHD
412acdd317 remoe download list logging 2022-07-24 20:13:08 -07:00
SpikeHD
c42c708db5 fix opening and some other stuff 2022-07-24 20:12:30 -07:00
SpikeHD
552d612e7c write modinfo.json after installing mod 2022-07-24 20:03:16 -07:00
SpikeHD
5077c19fdc show installed mods 2022-07-24 19:28:05 -07:00
SpikeHD
36c2302f1b downloading and extracting 2022-07-24 18:14:18 -07:00
SpikeHD
a06a8af7df Merge branch 'main' of github.com:Grasscutters/Cultivation 2022-07-24 16:43:59 -07:00
SpikeHD
975b04fd0e fix download icon lineup 2022-07-24 00:30:00 -07:00
SpikeHD
811f437238 potentially gather installed mods 2022-07-24 00:24:10 -07:00
SpikeHD
a25645ef77 fix category setting 2022-07-23 23:44:04 -07:00
SpikeHD
ac8fd3af45 fixes 2022-07-23 23:23:48 -07:00
SpikeHD
4f3952aeb1 downloading (almost) and only show when migoto is set 2022-07-23 23:23:48 -07:00
SpikeHD
c0740417e3 better styling, download overlay on hover 2022-07-23 23:23:48 -07:00
SpikeHD
8700a77ba0 more styling 2022-07-23 23:23:48 -07:00
SpikeHD
95267720a4 mod tiles and nsfw blurring 2022-07-23 23:23:48 -07:00
SpikeHD
d97e5c192f interepret API response 2022-07-23 23:23:48 -07:00
SpikeHD
a9d9d361e1 proper heading binding 2022-07-23 23:23:48 -07:00
SpikeHD
b78d9c28c9 loading anim 2022-07-23 23:23:48 -07:00
SpikeHD
f946cedb4d mod list colors and such 2022-07-23 23:23:48 -07:00
SpikeHD
c659979851 remove thread stuff, set cwd properly when openening migoto 2022-07-23 23:23:47 -07:00
SpikeHD
940943b106 fix migoto launch btn 2022-07-23 23:23:47 -07:00
SpikeHD
7ad1c4649c page switching with custom event 2022-07-23 23:23:47 -07:00
SpikeHD
011b15c8d9 split main launcher into seperate file 2022-07-23 23:23:47 -07:00
SpikeHD
d28af907ec better button management 2022-07-23 23:23:47 -07:00
SpikeHD
1b076ccea9 open with cwd, restore akebi option stuff 2022-07-23 23:23:47 -07:00
SpikeHD
818896c734 Merge pull request #42 from Seeker14491/cpp-fix
Don't throw C++ exceptions into Rust
2022-07-23 17:12:52 -07:00
SpikeHD
3b6225d5f0 Merge pull request #43 from trollerr/patch-1
Update Vietnamese translation
2022-07-23 15:10:27 -07:00
Trollerr
041a6cb768 typo 2022-07-23 22:03:44 +07:00
Trollerr
acb6de85ad Update Vietnamese translation 2022-07-23 22:00:31 +07:00
SpikeHD
cd628b4f3d open with cwd, restore akebi option stuff 2022-07-21 19:49:51 -07:00
SpikeHD
19d939a074 remove migoto for now 2022-07-21 19:41:00 -07:00
Brian Bowman
aeaa7ef76c Don't throw C++ exceptions into Rust 2022-07-21 18:03:43 -05:00
Brian Bowman
7659e9831a Remove unused metadata-related things 2022-07-21 17:43:30 -05:00
SpikeHD
22a416ebd8 Fix elementIds.md 2022-07-21 09:55:44 -07:00
SpikeHD
dcc9749967 Merge pull request #40 from Seeker14491/prettier
Add prettier formatter
2022-07-21 09:50:21 -07:00
Brian Bowman
7f9ba66e38 Add lint:fix script 2022-07-19 20:47:11 -05:00
Brian Bowman
f260379fa2 Add formatting pre-commit hook 2022-07-19 20:47:11 -05:00
Brian Bowman
544c39168b Add prettier check to CI 2022-07-19 20:47:11 -05:00
Brian Bowman
eb9aa34323 Run prettier formatter 2022-07-19 20:47:11 -05:00
Brian Bowman
e9df0f17db Tweak .editorconfig and line endings
- Use `lf` line endings everywhere. This works fine even on Windows, and is more common for git repos. It also matches prettier's default.

For .editorconfig:

- Remove `insert_final_newline = false`: Omitting the final newline isn't supported by prettier or rustfmt.
- Remove `trim_trailing_whitespace = false`: The formatters will remove trailing whitespace anyways. I can't think of a good reason to want to keep trailing whitespace.
- Remove other redundant options.
2022-07-19 20:42:43 -05:00
Brian Bowman
bf2ae51fb0 Add prettier formatter 2022-07-19 20:42:43 -05:00
SpikeHD
dc20fe5916 set proxy addr only when launching with proxy 2022-07-19 17:52:17 -07:00
SpikeHD
1a6ed38f8f only unpatch when game was patched automatically 2022-07-19 17:49:40 -07:00
SpikeHD
d23d5e3806 version bump! 2022-07-19 17:45:13 -07:00
SpikeHD
365a4f2888 spanish translation 2022-07-19 17:43:31 -07:00
SpikeHD
e270c886db 3dm support that doesn't actually work I just don't wanna fix it 2022-07-19 17:40:22 -07:00
SpikeHD
18a1b0e94c Merge branch 'main' of github.com:Grasscutters/Cultivation 2022-07-19 17:14:59 -07:00
SpikeHD
3799ec648d make proxy optional 2022-07-19 17:14:55 -07:00
SpikeHD
8ff06f6d29 make metadata optional 2022-07-19 17:07:10 -07:00
SpikeHD
6ff1ef932c Merge pull request #35 from ffauzan/patch-1
Fix toggle encryption
2022-07-19 09:11:59 -07:00
SpikeHD
9e29135376 Merge pull request #38 from ffauzan/patch-2
Fix patched metadata checking
2022-07-19 09:10:09 -07:00
ffauzan
109f98db66 Fix patched metadata checking
Issue: The function checked the wrong directory for global-metadata-patched.dat
2022-07-19 16:41:25 +07:00
ffauzan
69201bc8b1 Update file_helpers.rs 2022-07-19 11:52:12 +07:00
ffauzan
656fa2cfe3 Revert "fix conflict"
This reverts commit 1588bee5a3, reversing
changes made to 75b79d0202.
2022-07-19 11:47:38 +07:00
Ffauzan
1588bee5a3 fix conflict 2022-07-19 11:27:28 +07:00
Ffauzan
75b79d0202 Merge branch 'Grasscutters-main' into patch-1 2022-07-19 11:04:03 +07:00
Ffauzan
6d9f1af134 Merge branch 'main' of https://github.com/Grasscutters/Cultivation into Grasscutters-main 2022-07-19 11:03:51 +07:00
SpikeHD
d38459bb8a Merge pull request #37 from Seeker14491/patch-1
Fix lints, update deps, clean readme
2022-07-18 18:06:24 -07:00
ffauzan
c7954d2294 Update file_helpers.rs 2022-07-19 06:39:29 +07:00
Brian Bowman
adbb8e380d Clean up readme 2022-07-18 18:01:08 -05:00
Brian Bowman
4ff9e88185 Update dependencies 2022-07-18 18:01:08 -05:00
Brian Bowman
27a10c58ca Fix lints 2022-07-18 18:01:08 -05:00
ffauzan
44b148f2a4 Fix toggle encryption
Open the config file in write mode so the toggle encryption button is actually working
2022-07-18 22:03:01 +07:00
SpikeHD
6434814d1d fix decryption 2022-07-17 00:08:31 -07:00
SpikeHD
043f3e7ce4 simplify metadata processes 2022-07-16 23:34:55 -07:00
SpikeHD
61ac332cee Log paths on copy error 2022-07-16 22:56:01 -07:00
SpikeHD
a1284cc2ff path_bufs for file helpers 2022-07-16 22:06:26 -07:00
SpikeHD
cf82e9e892 use path bufs for copying files 2022-07-16 22:02:21 -07:00
SpikeHD
bb1874d64a Merge branch 'main' of https://github.com/Grasscutters/Cultivation 2022-07-16 21:26:07 -07:00
SpikeHD
85c0e2473b 2.8 resources download 2022-07-16 21:26:00 -07:00
SpikeHD
9e3b584608 Update README.md 2022-07-16 21:23:28 -07:00
SpikeHD
5568183821 Update README.md 2022-07-16 21:20:56 -07:00
SpikeHD
d64186777f Merge pull request #31 from Grasscutters/patching
CLIENT PATCHING LETS GOOOOOOO
2022-07-16 21:16:01 -07:00
SpikeHD
f2d45d2359 version bump (omg what could this mean) 2022-07-16 21:13:34 -07:00
SpikeHD
d46a2bdc44 Merge branch 'main' into patching 2022-07-16 17:03:36 -07:00
SpikeHD
bb383c5c0a manual proxy cert install 2022-07-16 16:59:16 -07:00
SpikeHD
4b42d0f8b8 reorg 2022-07-16 16:42:20 -07:00
SpikeHD
3d0f8a3ff6 remove unused fs 2022-07-16 16:39:26 -07:00
SpikeHD
56b0c78661 Merge branch 'main' into patching 2022-07-16 16:38:54 -07:00
SpikeHD
ffc37cc203 read and write server config via backend 2022-07-16 16:36:05 -07:00
SpikeHD
8faaba2849 Update README.md 2022-07-16 14:43:29 -07:00
SpikeHD
9d86d9c9ff lint 2022-07-15 21:27:50 -07:00
SpikeHD
8ff1a47fff Merge branch 'main' into patching 2022-07-15 21:14:16 -07:00
SpikeHD
12da09596d Merge pull request #23 from Seeker14491/ci
Set up GitHub Actions lints and checks
2022-07-15 21:11:06 -07:00
SpikeHD
bd54a78e4b emergency metadata recovery 2022-07-15 19:35:57 -07:00
SpikeHD
bb74553bee Update README.md 2022-07-15 12:43:32 -07:00
SpikeHD
d373f46615 Update README.md 2022-07-15 12:43:00 -07:00
SpikeHD
a1b0fec871 Merge branch 'main' into patching 2022-07-14 20:54:02 -07:00
SpikeHD
bae193050f window reload 2022-07-14 20:13:51 -07:00
SpikeHD
fc5ffae1e2 another normal feature 2022-07-14 20:05:11 -07:00
SpikeHD
ab0e05ffe1 scrolling with no scrollbars 2022-07-14 18:52:12 -07:00
SpikeHD
88a1740b91 normal feature 2022-07-14 18:49:49 -07:00
SpikeHD
f24f3af377 just another normal everyday feature, nothing to see here 2022-07-14 18:05:52 -07:00
SpikeHD
9bdb18d4d6 fix metadata restoration 2022-07-13 21:23:41 -07:00
SpikeHD
7cbb600a7f always unpatch game on close 2022-07-13 18:55:06 -07:00
SpikeHD
33c733ce97 Merge branch 'main' into patching 2022-07-13 18:49:24 -07:00
SpikeHD
abafc94379 rework process watcher to be not terrible 2022-07-13 18:49:06 -07:00
SpikeHD
411e11dd8d Merge branch 'main' into patching 2022-07-13 18:26:17 -07:00
SpikeHD
b2453e7c4d fix crash on denying UAC prompt 2022-07-13 18:26:06 -07:00
SpikeHD
30476a86ad Merge main 2022-07-13 18:22:10 -07:00
SpikeHD
375e15e947 fix funky paths 2022-07-13 18:19:55 -07:00
SpikeHD
fd87adc1f6 remove redundant option 2022-07-13 18:15:32 -07:00
SpikeHD
b903c27a22 BIG FAT CLEANUP PART ONE 2022-07-13 18:10:41 -07:00
Brian Bowman
5bf7019482 Also list npm command for running formatting and linting scripts 2022-07-12 23:30:43 -05:00
Brian Bowman
be633eeea5 Document how to run formatting and linting scripts 2022-07-12 23:19:59 -05:00
Brian Bowman
58e683c669 Add format and lint scripts 2022-07-12 20:10:05 -05:00
Brian Bowman
cd5c2985e5 Add rustfmt CI check 2022-07-12 20:10:05 -05:00
Brian Bowman
e41a89b26c Configure and run rustfmt 2022-07-12 20:10:05 -05:00
Brian Bowman
4d05063b61 Set up CI for frontend 2022-07-12 20:10:05 -05:00
Brian Bowman
53e2b0cbed Fix tsc and ESLint warnings 2022-07-12 20:10:05 -05:00
Brian Bowman
51d00add22 Set up CI for backend 2022-07-12 20:10:05 -05:00
SpikeHD
4fc90ee333 cleanup and yarn update 2022-07-12 17:21:03 -07:00
SpikeHD
0ec8782f48 use thread properly lol 2022-07-12 17:15:32 -07:00
SpikeHD
33c67eef06 re-threadify program launching 2022-07-12 12:28:31 -07:00
Benj
a703843eed Move things to file_helpers
These were meant to be here anyway but I didn't put it here because other similar methods were in system_helpers
2022-07-12 14:28:08 +08:00
Benj
ba2a8b7fec Merge tag 'v1.0.2-alpha' into patcher-2.0
# Conflicts:
#	src-tauri/Cargo.lock
#	src-tauri/src/file_helpers.rs
#	src-tauri/src/main.rs
2022-07-12 13:37:44 +08:00
SpikeHD
b3585927ca Merge branch 'main' of github.com:Grasscutters/Cultivation 2022-07-11 19:45:29 -07:00
Benj
d4e284663e Removed package-lock.json 2022-07-11 14:23:58 +08:00
SpikeHD
4c9dad49c4 version bump 2022-07-09 23:14:48 -07:00
SpikeHD
960fcae647 Merge branch 'main' of https://github.com/Grasscutters/Cultivation 2022-07-09 22:52:04 -07:00
SpikeHD
4172ee9106 move copy_file to file_helpers 2022-07-09 22:50:41 -07:00
SpikeHD
8566e7f35e remove legacy bg folder creation 2022-07-09 22:49:37 -07:00
Benj
065043bbe9 Whoops 2022-07-10 11:57:50 +08:00
Benj
2c3a23e841 No more repeated hardcoded strings
and some debug stuff
2022-07-10 11:39:16 +08:00
SpikeHD
0b2296f918 Merge pull request #20 from Seeker14491/cleanup
Update backend deps & Fix clippy lints
2022-07-09 10:35:03 -07:00
Brian Bowman
6f997a38d3 Remove unneeded thread spawn
As of `open` v3.0.0, this function never blocks, so we don't need to spawn a thread.
2022-07-09 07:11:31 -05:00
Brian Bowman
cd128741b4 Update dependencies 2022-07-09 07:11:31 -05:00
Brian Bowman
174a7b5163 Fix clippy lints 2022-07-09 07:11:31 -05:00
Brian Bowman
e75474fde7 Replace lazy_static with once_cell
`once_cell` is a simpler macroless alternative that will be added to the standard library.
2022-07-09 07:05:38 -05:00
Brian Bowman
c3119ce7a7 Remove unused runas dependency 2022-07-09 05:54:04 -05:00
SpikeHD
6c4b546de2 Update README.md 2022-07-09 01:57:41 -07:00
SpikeHD
30388eb6a9 Update README.md 2022-07-09 01:57:08 -07:00
Benj
f35b596eb2 Some globalisation stuff
Game executable now selectable in settings. Translations need updating for "Set Game Path".
2022-07-09 15:11:19 +08:00
SpikeHD
3008f50e1f Merge branch 'main' of github.com:Grasscutters/Cultivation 2022-07-08 22:21:41 -07:00
SpikeHD
99293ad7cf disable encryption toggle when no jar set 2022-07-06 17:57:17 -07:00
Benj
99687f0550 Autopatching on game launch
Plus adding some non-functional options for later
Need to add support for Chinese version of the game
2022-07-07 01:25:54 +08:00
lilmayofuksu
6124d6949c Fix gcc build for real this time 2022-07-06 20:11:44 +03:00
SpikeHD
afdbc5ad80 Merge pull request #16 from phuchptty/main
add vietnamese language file
2022-07-06 09:26:00 -07:00
lilmayofuksu
dd56af8fcb Actually fix g++ builds 2022-07-06 19:04:27 +03:00
ayy lmao
487b36a37e Fix build for g++ 2022-07-06 19:04:27 +03:00
ayy lmao
850b282b70 Build and link mhycrypto statically 2022-07-06 19:04:27 +03:00
Benj
e0272aa38a Re-encrypt Metadata
We in the end game now bois (Time for UI, refactoring, and better functionality)
2022-07-06 19:04:27 +03:00
Benj
27d7c32a73 Key replacement 2022-07-06 19:04:27 +03:00
Benj
936c533ff8 Starting work on brand new patcher (Wooo)
Oh god my sanity is fading again. Please help. oh god. oh god. oh god. Please help me.
2022-07-06 19:04:27 +03:00
phuchptty
1c0edd2bcd feat: add vietnamese language file 2022-07-06 22:38:59 +07:00
SpikeHD
8ac4d063a4 Merge pull request #14 from Seeker14491/unix-fix
Fix build errors on Unix
2022-07-05 21:50:33 -07:00
Brian Bowman
bb9a044e05 Fix reading config on Unix 2022-07-05 22:58:56 -05:00
Brian Bowman
4f63e55a28 Fix build errors on Unix 2022-07-05 19:39:48 -05:00
SpikeHD
a3e1898d82 Update README.md 2022-07-05 12:26:14 -07:00
SpikeHD
5bca75629c Merge pull request #15 from TukanDev/main
Documentation for classes used for react components under /common and some small fixes
2022-07-05 11:49:53 -07:00
TukanDev
0c1fb3e25e remove hover and disabled state docs for bigbutton class 2022-07-05 20:48:34 +02:00
SpikeHD
9558fb0eaf Update THEMES.md 2022-07-05 11:46:18 -07:00
TukanDev
f0d08f5e67 Fix index.json example not having customBackgroundFile key 2022-07-05 20:31:14 +02:00
TukanDev
2af33ccc33 Added documentation for react components under /common and fix for some leftover element id's on commonly used components that I missed. 2022-07-05 20:23:20 +02:00
SpikeHD
8f6ae91a8a Merge pull request #13 from timius100/main
Added latvian language file
2022-07-04 18:54:51 -07:00
SpikeHD
49e77366d1 Merge pull request #12 from yann48gmx/main
French language file
2022-07-04 18:54:10 -07:00
Timur
a826c8e835 Added latvian language file 2022-07-05 01:47:21 +03:00
yann48gmx
bdc7c28ba7 Update fr.json 2022-07-04 22:07:26 +02:00
yann48gmx
1fb8a49a20 French language file 2022-07-04 22:06:29 +02:00
SpikeHD
c57a4ac56d Merge pull request #11 from timius100/main
Added russian language file
2022-07-04 09:33:39 -07:00
Timur
b72dfb8802 Added russian language file 2022-07-04 15:28:31 +03:00
SpikeHD
b00325ca68 Merge pull request #9 from Seeker14491/fix-news
Fix fetching commits from API
2022-07-03 19:23:43 -07:00
SpikeHD
8eaab56787 Merge pull request #10 from TukanDev/main
Fix reusable react components duplicate id...
2022-07-03 19:21:38 -07:00
TukanDev
9ea92a23ee Fix reusable react components duplicate id... dumbass 2022-07-04 04:14:35 +02:00
Brian Bowman
5d2f803f3c Fix fetching commits from API 2022-07-03 17:36:45 -05:00
SpikeHD
29716c43b6 Merge pull request #8 from TukanDev/main
Add element id's and their documentation
2022-07-03 15:15:45 -07:00
SpikeHD
2590082041 reword 2022-07-03 15:15:20 -07:00
SpikeHD
0ec9ef92ee cleanup 2022-07-03 15:10:49 -07:00
TukanDev
98f530eb0c Add element id's and their documentation 2022-07-03 23:46:02 +02:00
SpikeHD
d9416b4fb6 Merge pull request #6 from DasIschBims/main
Added german language file
2022-07-03 11:48:07 -07:00
DasIschBims
73a7d08b8e Added german language file 2022-07-03 17:01:25 +02:00
SpikeHD
3931fa25d6 Update THEMES.md 2022-07-02 21:12:54 -07:00
SpikeHD
84c3645ae2 theming documentation 2022-07-02 21:06:14 -07:00
SpikeHD
54b20a93ef Merge pull request #5 from Kimi898246/patch-2
minor edits cause i fucked up again
2022-07-02 20:48:40 -07:00
Kimi
20130061ab minor edits cause i fucked up again
oops
2022-07-03 11:40:56 +08:00
SpikeHD
e2393d7900 Merge pull request #4 from Kimi898246/patch-1
Traditional Chinese | Translation Patches
2022-07-02 20:30:05 -07:00
Kimi
a42718f6c5 Traditional Chinese | Translation Patches 2022-07-03 11:29:29 +08:00
SpikeHD
de81f9bcac version bump 2022-07-02 20:10:04 -07:00
SpikeHD
3aeacad28c fix theme backgrounds 2022-07-02 20:03:05 -07:00
SpikeHD
27cbd4705b set new icon 2022-07-02 15:01:46 -07:00
SpikeHD
24c578b411 fix button text not updating 2022-07-02 14:46:55 -07:00
SpikeHD
a6a33a9886 button styling 2022-07-02 14:24:18 -07:00
SpikeHD
17ea745e23 toggle encryption in options 2022-07-02 14:16:20 -07:00
SpikeHD
98a948d377 default updater to off 2022-07-02 13:29:13 -07:00
KingRainbow44
eaa3a4299b Add instruction to turn off useInRouting 2022-07-02 15:57:09 -04:00
KingRainbow44
bbf4c7033a Add common issue with the launcher not starting 2022-07-02 15:40:51 -04:00
Magix
cbd05d2c42 Fix startup crash & file loading
Merge pull request #3 from Seeker14491/fix-windows
2022-07-02 11:31:07 -04:00
Brian Bowman
f6b80e1fac Fix Windows path issues 2022-07-02 02:12:12 -05:00
KingRainbow44
241b2b07bd Merge remote-tracking branch 'origin/main' 2022-07-01 16:26:22 -04:00
KingRainbow44
ec2f0f3ff8 Add debug mode as a config setting 2022-07-01 16:26:06 -04:00
KingRainbow44
578d17764c Change logic for command invoking 2022-07-01 16:25:52 -04:00
KingRainbow44
63f2a3a573 Add allowed http URLs 2022-07-01 16:25:38 -04:00
KingRainbow44
b034c73c72 Change to api.grasscutter.io 2022-07-01 16:25:18 -04:00
KingRainbow44
3b99b9137a Add duct as a dependency 2022-07-01 16:24:02 -04:00
Magix
39376aed8c Add the 'zh-CN'
Add Simplified Chinese translation
2022-06-30 12:41:50 -04:00
Tesutarin
4e1053761f Update chs.json 2022-06-30 16:47:55 +08:00
Tesutarin
8f648c16da Add Simplified Chinese translation 2022-06-30 10:56:25 +08:00
SpikeHD
68fb7dd36a Merge pull request #1 from Kimi898246/patch-1
Traditional Chinese | Translation JSON
2022-06-29 18:07:06 -07:00
Kimi
1327c077a7 Update cht.json 2022-06-30 05:37:59 +08:00
Kimi
12305c0dc9 Update cht.json 2022-06-30 04:48:24 +08:00
Kimi
15d53273d0 Traditional Chinese | Translation JSON
I think the title is self-explanatory enough..
2022-06-30 04:32:13 +08:00
154 changed files with 11515 additions and 4718 deletions

View File

@@ -2,20 +2,9 @@ root = true
[*]
charset = utf-8
end_of_line = crlf
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = false
max_line_length = 120
tab_width = 2
trim_trailing_whitespace = false
[*.rs]
max_line_length = 100
indent_size = 2
[{*.ats,*.cts,*.mts,*.ts}]
indent_size = 2
[*.json]
indent_size = 2
trim_trailing_whitespace = true

View File

@@ -1,38 +1,40 @@
{
"env": {
"browser": true,
"es2021": true,
"node": true
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", "prettier"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["react", "@typescript-eslint"],
"rules": {
"@typescript-eslint/ban-types": [
"warn",
{
"extendDefaults": true,
"types": {
"{}": false
}
}
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
"indent": [
"error",
2
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"never"
]
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
]
},
"settings": {
"react": {
"version": "detect"
}
}
}

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
* text=auto eol=lf
src-tauri/keys/* binary

61
.github/workflows/backend-checks.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
name: Check backend
on:
push:
paths:
- '.github/workflows/backend-checks.yml'
- 'src-tauri/**'
pull_request:
paths:
- '.github/workflows/backend-checks.yml'
- 'src-tauri/**'
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
jobs:
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path ./src-tauri/Cargo.toml --all -- --check
clippy:
runs-on: ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
platform: [windows-latest, ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v3
- name: Install Linux dependencies
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.0-dev \
build-essential \
curl \
wget \
libssl-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev
- uses: Swatinem/rust-cache@v1
with:
working-directory: src-tauri
- uses: actions-rs/clippy-check@v1
with:
name: clippy (${{ runner.os }})
token: ${{ secrets.GITHUB_TOKEN }}
args: --manifest-path ./src-tauri/Cargo.toml --no-default-features -- -D warnings

85
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: Build
on:
push:
paths:
- '.github/workflows/build.yml'
- 'src-tauri/**/*'
- 'src/**/*'
pull_request:
paths:
- '.github/workflows/build.yml'
- 'src-tauri/**/*'
- 'src/**/*'
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
jobs:
build-win:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: setup node
uses: actions/setup-node@v1
with:
node-version: 16
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install deps and build
run: yarn && yarn build --debug
- name: Compress build
uses: vimtor/action-zip@v1
with:
files: src-tauri/target/debug/lang/ src-tauri/target/debug/keys/ src-tauri/target/debug/patch/ src-tauri/target/debug/Cultivation.exe src-tauri/target/debug/bundle/msi/
recursive: true
dest: Cultivation.zip
- name: Upload build
uses: actions/upload-artifact@v3
with:
name: CultivationWin
path: Cultivation.zip
build-ubuntu:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: setup node
uses: actions/setup-node@v1
with:
node-version: 16
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install libraries
run: sudo apt install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
- name: Install deps and build
run: yarn && yarn build --debug
- name: Compress build
uses: vimtor/action-zip@v1
with:
files: src-tauri/target/debug/lang/ src-tauri/target/debug/keys/ src-tauri/target/debug/patch/ src-tauri/target/debug/cultivation
recursive: true
dest: Cultivation.zip
- name: Upload build
uses: actions/upload-artifact@v3
with:
name: CultivationLinux
path: Cultivation.zip

32
.github/workflows/frontend-checks.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Check frontend
on:
push:
paths-ignore:
- '**.lock'
- '**.rs'
- '**.toml'
pull_request:
paths-ignore:
- '**.lock'
- '**.rs'
- '**.toml'
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
jobs:
prettier-tsc-eslint-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install modules
run: yarn
- name: Run prettier
run: yarn prettier --check .
- name: Run tsc
run: yarn tsc --noEmit
- name: Run ESLint
run: yarn eslint src

4
.gitignore vendored
View File

@@ -17,10 +17,12 @@
.env.development.local
.env.test.local
.env.production.local
.vs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# moved lang files
/lang
/lang
package-lock.json

4
.husky/pre-commit Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn lint-staged --allow-empty

View File

@@ -3,6 +3,38 @@
<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>

4
.lintstagedrc.json Normal file
View File

@@ -0,0 +1,4 @@
{
"src-tauri/**/*.rs": "rustfmt --edition 2021",
"*": "yarn prettier --write --ignore-unknown"
}

4
.prettierignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
src-tauri/resources/
src-tauri/target/
src-tauri/WixTools/

4
.prettierrc.json Normal file
View File

@@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}

146
README.md
View File

@@ -1,61 +1,129 @@
# NOTICE
Yes! The Cultivation repository is **open**. This does **not** mean it has released.\
Cultivation will be releasing at some point after opening this repo.
**This also means you will not be provided explicit support for Cultivation.**\
Consider Cultivation to be the bleeding-edge version of GrassClipper.
During this open-beta testing period, **helpful issues are appreciated**, while unhelpful ones will be closed.
## Fair Warning
Cultivation is **VERY MUCH IN BETA** and a majority of features do not work.\
There are **no official releases of Cultivation**. You are **required** to build the application from **scratch**.\
Please do **NOT install, download, or use pre-compiled versions of Cultivation**. Only use releases from this GitHub repository.
EN | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) |
# Cultivation
A game launcher designed to easily proxy traffic from anime game to private servers.
# Table Of Contents
* [Download](#download)
* [Developer Quick-start](#developer-quickstart)
* [Setup](#setup)
* [Building](#building)
* [Troubleshooting](#troubleshooting)
- [Client Patching Notice](#client-patching-notice)
- [Download](#download)
- [Setup](#setup)
- [Troubleshooting](#troubleshooting)
- [Developer Quick-start](#developer-quickstart)
- [Setup](#setup)
- [Building](#building)
- [Code Formatting and Linting](#code-formatting-and-linting)
- [Generating Update Artifacts](#generating-update-artifacts)
- [Theming](#theming)
- [Screenshots](#screenshots)
- [Credits](#credits)
# Client Patching Notice - RSA
For game versions 3.1 and above, Cultivation automatically makes a small patch to your game client when launching using Grasscutter, and restores it upon closing the game. In theory, you should still be totally safe, however it would be dishonest to not explicitly state that **modifying the game client could, theoretically, lead to a ban if you connect to official servers with it**. It is extremely unlikely AND there are no instances known of it happening, but the possibility exists.
# Download
[Find release builds here!](https://github.com/Grasscutters/Cultivation/releases)
Once downloaded, extract somewhere and open as administrator.
Download and open the MSI, and once installed, run Cultivation as administrator. Refer below for more [detailed setup instructions](#setup).
**Windows 7 Users:** You will need to download [WebView](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section) manually, and download the `.zip` instead of the `.msi`.
# Setup
5-minute video for those who don't like to/cannot read: https://youtu.be/e0irOYbQe7I
- Download Cultivation
- If you are on Windows 10 or 11, use the MSI
- If you are on Windows 7, or the MSI doesn't work, use the zip and download [WebView](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)
- If you are on GNU/Linux or MacOS, [help us port Windows-specific system calls to GNU/Linux and MacOS!](https://github.com/Grasscutters/Cultivation/issues/7)
- Install or extract Cultivation
- Open Cultivation **_as administrator_**
- Before clicking randomly on stuff, in options (top right cog icon), set your Game Install Path.
- If you are using an existing server installation from somewhere else, you can set the `.jar` file in settings as well. All downloads made through Culti will automatically use that path, no additional config needed.
- If you use multiple Java versions, you can set the Java path to your Java 17 installation (only required if you are running your own server)
- Decide if you want to download your own server, or just join a public one
- If joining a public one, you're done. Just click "Connect with Grasscutter" and input the address and port. You do not have to continue these instructions.
- If you are getting System Error, or 4214, ask the [Discord support channels](https://discord.gg/grasscutter)
- Open the "Downloads" menu (top right)
- Download "Grasscutter All-in-One" (top of the list)
- Once that is done, click the icon next to "Launch"
- To play on your new server:
- Click "Connect with Grasscutter"
- Input `localhost` as the address, and `443` as the port
- Ensure HTTPS is disabled
- Any generic "I am getting XYZ error!" should go in the [Discord support channels](https://discord.gg/grasscutter)
- Any specific Cultivation issues should go in [the issues section](/issues)
- Any Grasscutter server related issues should go in [the Grasscutter issues section](https://github.com/Grasscutters/Grasscutter)
# Troubleshooting
### White screen, insta-crash or something similar
- First try [running in Windows 8 compatibility mode](https://www.lifewire.com/run-older-programs-with-windows-10-compatibility-mode-4587064).
- If that doesn't work, fully uninstall and reinstall [WebView2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section).
- If you are having trouble uninstalling it, try deleting this registry folder and uninstalling again `Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}`
- You can also try [uninstalling from the Command Prompt](https://superuser.com/a/1743626)
### Internet not working after use
Please allow the Cultivation window to pop back up once you have quit out of the game. This tells you that it knows you closed the game, and that it has reverted your proxy settings. If you have closed Cultivation before this happens, or have had some other issue with your internet, go to your [proxy settings in Windows](https://techviral.net/check-proxy-server-settings-in-windows/) and disable the "Manual proxy setup".
# Developer Quickstart
### Setup
* Install [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) & [Rust compiler](https://www.rust-lang.org/tools/install)
* `npm install` or `yarn install`
* `npm run start:dev` or `yarn start:dev`
- Install [NodeJS >12](https://nodejs.org/en/)
- Install [yarn](https://classic.yarnpkg.com/lang/en/docs/install) (cry about it `npm` lovers)
- Install [Rust](https://www.rust-lang.org/tools/install)
- `yarn install`
- `yarn start:dev`
### Building
`npm run build` or `yarn build`
Add `--release` or `--debug` depending on what release you are creating. This defaults to `--release`
For a release build,
### Updating
* Add the `TAURI_PRIVATE_KEY` as an environment variable with a path to your private key.
* Add the `TAURI_KEY_PASSWORD` as an environment variable with the password for your private key.
* Run `npm run update` or `yarn build`
* The update will be in `src-tauri/target/(release|debug)/msi/Cultivation_X.X.X_x64_xx-XX.msi.zip`
- `yarn build`
# Troubleshooting
TODO. Collect common issues before updating.
For a debug build,
- `yarn build --debug`
### Code Formatting and Linting
Formatting:
- `yarn format`
Check Lints, fix (some) lints:
- `yarn lint`, `yarn lint:fix`
### Generating Update Artifacts
- Add the `TAURI_PRIVATE_KEY` as an environment variable with a path to your private key.
- Add the `TAURI_KEY_PASSWORD` as an environment variable with the password for your private key.
- `yarn build`
The update will be at `src-tauri/target/(release|debug)/msi/Cultivation_X.X.X_x64_xx-XX.msi.zip`
# Theming
A full theming reference can be found [here!](/THEMES.md)
# Screenshots
![image](https://user-images.githubusercontent.com/25207995/173211603-e5e85df7-7fd3-430b-9246-749ebbc1e483.png)
![image](https://user-images.githubusercontent.com/25207995/173211543-b7e88943-cfd2-418b-ac48-7f856868129b.png)
![image](https://user-images.githubusercontent.com/25207995/173211561-a1778fdc-5cfe-4687-9a00-44500d29e470.png)
![image](https://user-images.githubusercontent.com/25207995/173211573-8cedfa9a-51c9-4670-a4f7-a334a2fabec5.png)
![image](https://user-images.githubusercontent.com/25207995/173211590-6a2242b5-1e8f-4db9-a5c7-06284688b131.png)
![image](https://user-images.githubusercontent.com/107363768/221495236-ca1e2f2e-0f85-4765-a5f3-8bdcea299612.png)
![image](https://user-images.githubusercontent.com/107363768/221495246-ea309640-f866-4f50-bda8-f9d916380f92.png)
![image](https://user-images.githubusercontent.com/107363768/221495249-5a1aac39-9e8a-4244-9642-72c2e7be8a69.png)
![image](https://user-images.githubusercontent.com/107363768/221495254-ffbfc24e-ef5d-4e72-9068-a02132381dcc.png)
## Credits
* [SpikeHD](https://github.com/SpikeHD): For originally creating **GrassClipper** and creating the amazing UI of Cultivation.
* [KingRainbow44](https://github.com/KingRainbow44): For building a proxy daemon from scratch and integrating it with Cultivation.
* [Tauri](https://tauri.app): For providing an amazing, efficient, and simple desktop application framework/library.
- [SpikeHD](https://github.com/SpikeHD): For originally creating **GrassClipper** and creating the amazing UI of Cultivation.
- [KingRainbow44](https://github.com/KingRainbow44): For building a proxy daemon from scratch and integrating it with Cultivation.
- [Benj](https://github.com/4Benj): For assistance in client patching.
- [lilmayofuksu](https://github.com/lilmayofuksu): For assistance in client patching.
- [Tauri](https://tauri.app): For providing an amazing, efficient, and simple desktop application framework/library.

87
README_zh-CN.md Normal file
View File

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

87
README_zh-TW.md Normal file
View File

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

111
THEMES.md Normal file
View File

@@ -0,0 +1,111 @@
# Downloading/Installing Themes
1. Download your favorite theme! (You can find some in the `#themes` channel on Discord)
2. Place the unzipped theme folder inside of `%appdata%/cultivation/themes` (The path should look something like this: `cultivation/themes/theme_name/index.json`)
3. Enable within Cultivation!
# Creating your own theme
Themes support entirely custom JS and CSS, enabling you to potentially change every single thing about Cultivation with relative ease.
You can refer to the example theme [found here.](https://cdn.discordapp.com/attachments/992943872479084614/992993575652565002/Example.zip)
You will need CSS and JS experience if you want to do anything cool.
## Creating index.json
`index.json` is where you tell Cultivation which files and images to include. It supports the following properties:
| Property | Description |
| :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | The name of the theme. |
| `version` | Not shown anywhere, the version of the theme. |
| `description` | Not shown anywhere, the description of the theme. |
| `includes` | The files and folders to include. |
| `includes.css` | Array of CSS files to include. Example: `css: ["index.css"]` |
| `includes.js` | Array of JS files to includes. Example `js: ["index.js"]` |
| `customBackgroundURL` | A custom image URL to set as the background. Backgrounds that users set in their config supercede this. Example: `"https://website.com/image.png"` |
| `customBackgroundFile` | Path to a custom background image file. Backgrounds that users set in their config supercede this. Example: `"/image.png"` |
A full, complete `index.json` will look something like this:
```json
{
"name": "Example",
"version": "1.0.0",
"description": "This is an example theme. Made by SpikeHD.",
"includes": {
"css": ["/index.css"],
"js": ["/index.js"]
},
"customBackgroundURL": "https://website.com/image.png",
"customBackgroundFile": "/image.png"
}
```
**Important Note:**
All paths are relative to the theme folder. This means you only need to do `"/index.css"` to include `index.css` from the same folder `index.json` is located.
## Writing your CSS
You are welcome to use the DevTools to debug your theme, or view what properties an element has already, etc.
Below are some small examples of what you can do:
```css
/* Change the font */
body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif !important;
}
```
```css
/* Remove the news section */
.NewsSection {
display: none;
}
```
```css
/* Change the right bar width */
.RightBar {
width: 300px;
}
```
## How can I change XYZ element?
Every element is documented and describe [here](/docs/elementIds.md). Every\* single DOM element is assigned an ID to allow for easy and hyper-specific editing.
## Writing your JS
There are no limitations to what you can do with JS. It is safe, as it is sandboxed within the webpage, so there is no possibility of it being able to do anything more than what any regular webpage can do.
Below are some examples of what you can do:
```js
/* Change the version number every 500ms */
setInterval(() => {
document.getElementById('version').innerHTML = 'v' + Math.floor(Math.random() * 100)
}, 500)
```
```js
/* Load a custom font */
const head = document.head
const link = document.createElement('link')
link.href = 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap'
link.rel = 'stylesheet'
link.type = 'text/css'
head.appendChild(link)
```
```js
/* Create a new button that does nothing */
const newButton = document.createElement('button')
newButton.innerHTML = 'New Button'
document.body.appendChild(newButton)
```

135
docs/elementIds.md Normal file
View File

@@ -0,0 +1,135 @@
# Documentation of Element ID's and Classes for custom theming
## IDs
This does not include commonly used components (buttons, divider lines, commit author and message, etc...) for accessing and modifying those elements, please check `Classes` section bellow.
| #ID | Description |
| ------------------------------------ | --------------------------------------------------------------- |
| `#miniDialogContainer` | Main container of MiniDialog |
| `#miniDialogContainerTop` | Affects only top section of MiniDialog |
| `#miniDialogButtonClose` | Close button (SVG) of MiniDialog |
| `#miniDialogContent` | MiniDialog content |
| `#rightBarContainer` | Main container of RightBar |
| `#rightBarContent` | RightBar content |
| `#rightBarButtonDiscord` | Discord button on the RightBar |
| `#rightBarButtonGithub` | Github button on the RightBar |
| `#playButton` | Main container for whole launch buttons section |
| `#serverControls` | Container of "play on grasscutter" checkbox |
| `#enableGC` | "play on grasscutter" checkbox |
| `#ip` | Server ip input if play on grasscutter is enabled |
| `#port` | Server port input if play on grasscutter is enabled |
| `#httpsEnable` | "Enable https" checkbox if play on grasscutter is enabled |
| `#officialPlay` | Launch button |
| `#serverLaunch` | Launch server button |
| `#serverlaunchIcon` | Icon (SVG) of server launch button |
| `#serverConfigContainer` | Main container of server configuration section |
| `#serverLaunchContainer` | Main container of launch buttons (includes launch server) |
| `#topBarContainer` | Main container of launcher TopBar (minimize, exit, settings...) |
| `#title` | Title of the TopBar |
| `#version` | Version of the launcher in TopBar |
| `#topBarButtonContainer` | Container of launcher TopBar buttons only |
| `#closeBtn` | Exit launcher button |
| `#minBtn` | Minimize launcher button |
| `#settingsBtn` | Settings button |
| `#downloadsBtn` | Downloads button (grasscutter resources, grasscutter...) |
| `#newsContainer` | Main container of the news section |
| `#newsTabsContainer` | Container for news tabs |
| `#commits` | News tabs container commits button |
| `#latest_version` | News tabs for latest version button |
| `#newsContent` | Content section of news container |
| `#newsCommitsTable` | Commits table of news section |
| `#downloadMenuContainerGCStable` | Grasscutter stable update container |
| `#downloadMenuLabelGCStable` | Label for stable update button |
| `#downloadMenuButtonGCStable` | Button container for stable update button |
| `#grasscutterStableBtn` | "Update grasscutter stable" button |
| `#downloadMenuContainerGCDev` | Grasscutter development update container |
| `#downloadMenuLabelGCDev` | Label for latest update button |
| `#downloadMenuButtonGCDev` | Button container for latest update button |
| `grasscutterLatestBtn` | "Update grasscutter latest" button |
| `#downloadMenuContainerGCStableData` | Grasscutter stable data update container |
| `#downloadMenuLabelGCStableData` | Label for stable data update |
| `#downloadMenuButtonGCStableData` | Button container for stable data update button |
| `#grasscutterStableRepo` | "Update grasscutter stable data" button |
| `#downloadMenuContainerGCDevData` | Grasscutter latest data update container |
| `#downloadMenuLabelGCDevData` | Label for latest data update |
| `#downloadMenuButtonGCDevData` | Button container for latest data update button |
| `#grasscutterDevRepo` | "Update grasscutter latest data" button |
| `#downloadMenuContainerResources` | Container for grasscutter resources download |
| `#downloadMenuLabelResources` | label for resources download |
| `#downloadMenuButtonResources` | Button container for resources download button |
| `#resourcesBtn` | "Download grasscutter resources" button |
| `#menuContainer` | Generic Popup modal like menu container |
| `#menuContainerTop` | Top section of menu container |
| `#menuHeading` | Menu title |
| `#menuButtonCloseContainer` | Container for menu close button |
| `#menuButtonCloseIcon` | Menu close icon (SVG) |
| `#menuContent` | Content section of the menu |
| `#menuOptionsContainerGameExec` | Container for game executable option section |
| `#menuOptionsLabelGameExec` | Label for game executable option |
| `#menuOptionsDirGameExec` | Set game executable file browser |
| `#menuOptionsContainerGCJar` | Container for grasscutter jar option |
| `#menuOptionsLabelGCJar` | Label for grasscutter jar option |
| `#menuOptionsDirGCJar` | Set grasscutter jar file browser |
| `#menuOptionsContainerToggleEnc` | Container for toggle encryption option |
| `#menuOptionsLabelToggleEnc` | Label for toggle encryption option |
| `#menuOptionsButtonToggleEnc` | Toggle encryption button container |
| `#toggleEnc` | Toggle encryption button |
| `#menuOptionsContainerGCWGame` | Container for "grasscutter with game" option |
| `#menuOptionsLabelGCWDame` | Label for "grasscutter with game" option |
| `#menuOptionsCheckboxGCWGame` | Container for "grasscutter with game" option checkbox |
| `#gcWithGame` | Grasscutter with game checkbox |
| `#menuOptionsContainerThemes` | Container for themes section |
| `#menuOptionsLabelThemes` | Label for set themes option |
| `#menuOptionsSelectThemes` | Container for themes select menu |
| `#menuOptionsSelectMenuThemes` | Set theme select menu |
| `#menuOptionsContainerJavaPath` | Container for Java Path option |
| `#menuOptionsLabelJavaPath` | Label for Java path option |
| `#menuOptionsDirJavaPath` | Container for java path file browser |
| `#menuOptionsContainerBG` | Container for Background option |
| `#menuOptionsLabelBG` | Label for background option |
| `#menuOptionsDirBG` | Container for background url/local path option |
| `#menuOptionsContainerLang` | Container for language change option |
| `#menuOptionsLabelLang` | Label for language change option |
| `#menuOptionsSelectLang` | Container for language change select menu |
| `#menuOptionsSelectMenuLang` | Language select menu |
| `#DownloadProgress` | Download progress container |
| `#bottomSectionContainer` | Bottom section container |
| `#miniDownloadContainer` | Container for mini download |
## Classes
This is not full list of all classes, rather its list of classes for commonly used components that can not be accessed using element id system.
| .Class | Description |
| ------------------------- | ------------------------------------------------------- |
| `.BigButton` | Class for all buttons |
| `.BigButtonText` | Text inside a button |
| `.Checkbox` | Checkbox container |
| `.CheckboxDisplay` | Content of checkbox |
| `.DirInput` | Container for DirInput |
| `.FileSelectIcon` | Icon of DirInput |
| `.DownloadList` | List of all downloads |
| `.DownloadSection` | Container for each download |
| `.DownloadTitle` | Contains file download path and current status |
| `.DownloadPath` | Path of a download |
| `.DownloadStatus` | Status of a download |
| `.DownloadSectionInner` | Contains progressbar of the download section |
| `.HelpSection` | Container for help "?" circle button |
| `.HelpButton` | HelpButton itself |
| `.HelpContents` | Content of help button once expanded |
| `.MainProgressBarWrapper` | Container for MainProgressBar |
| `.ProgressBar` | ProgressBar (creativity left the brain) |
| `.InnerProgress` | ProgressBar percentage |
| `.MainProgressText` | Text for MainProgressBar |
| `.ProgressBarWrapper` | Container for ProgressBar |
| `.DownloadControls` | DownloadControls of ProgressBar |
| `.downloadStop` | Container for download stop icon (SVG) |
| `.ProgressText` | Text of the ProgressBar display current download status |
| `.TextInputWrapper` | Container for TextInput |
| `.TextClear` | Container for clear input content button |
| `.TextInputClear` | TextInput clear button icon (SVG) |
| `.Divider` | Container for line dividers |
| `.DividerLine` | Divider line itself |
| `.CommitAuthor` | Author of a commit |
| `.CommitMessage` | Message of a commit |

View File

@@ -1,10 +1,19 @@
# Troubleshooting
A guide dedicated for trying to troubleshoot Cultivation.
## The launcher doesn't appear to open.
Try running the launcher with **administrative privileges**.\
If this fixes your issue, you can force enable it in the **Compatability**\
tab for the launcher's executable.
## Unable to play on `localhost`.
Make sure your server is running with **encryption disabled**.\
Additionally, make sure Cultivation **is not using HTTPS**.
Make sure your server is running with **encryption disabled** and `useInRouting` to **false**.\
Additionally, make sure Cultivation **is set to not use HTTPS**.
## "I can't do anything requiring the internet after closing Cultivation!"
You probably didn't close Cultivation properly.\
Go to your *Windows Settings*, then *Network*, then *Proxy*, then disable it.
Go to your _Windows Settings_, then _Network_, then _Proxy_, then disable it.

View File

@@ -1,14 +1,14 @@
{
"name": "cultivation",
"version": "1.0.0",
"version": "1.0.25",
"private": true,
"dependencies": {
"@tauri-apps/api": "^1.0.0-rc.5",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@testing-library/user-event": "^14.2.6",
"@types/jest": "^28.1.6",
"@types/node": "^18.0.6",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"react": "^18.1.0",
@@ -20,14 +20,18 @@
"scripts": {
"start": "cross-env BROWSER=none react-scripts start",
"postbuild:windows": "xcopy /E /H /C /I /Y \".\\src-tauri\\lang\" \".\\src-tauri\\target\\release\\lang\"",
"postbuild:linux": "cp -r \".\\src-tauri\\lang\" \".\\lang\"",
"postbuild:linux": "cp -r \"./src-tauri/lang\" \"./lang\"",
"build:windows": "yarn tauri build",
"build:linux": "yarn tauri build",
"build": "react-scripts build && run-script-os",
"test": "react-scripts test",
"eject": "react-scripts eject",
"tauri": "tauri",
"start:dev": "tauri dev"
"start:dev": "tauri dev -- -- --no-admin",
"format": "cargo fmt --manifest-path ./src-tauri/Cargo.toml --all && yarn prettier --write --cache --loglevel warn .",
"lint": "cargo clippy --manifest-path ./src-tauri/Cargo.toml --no-default-features && yarn tsc --noEmit && yarn eslint src",
"lint:fix": "cargo clippy --manifest-path ./src-tauri/Cargo.toml --no-default-features --fix --allow-dirty && yarn tsc --noEmit && yarn eslint --fix src",
"prepare": "husky install"
},
"eslintConfig": {
"extends": [
@@ -53,7 +57,11 @@
"@typescript-eslint/parser": "^5.22.0",
"cross-env": "^7.0.3",
"eslint": "^8.15.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-react": "^7.29.4",
"husky": "^8.0.0",
"lint-staged": "^13.0.3",
"prettier": "^2.7.1",
"run-script-os": "^1.1.6"
}
}

View File

@@ -5,10 +5,7 @@
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Tauri-powered anime game launcher"
/>
<meta name="description" content="Tauri-powered anime game launcher" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Cultivation</title>

2070
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,38 +13,44 @@ rust-version = "1.57"
[build-dependencies]
tauri-build = { version = "1.0.0-rc.8", features = [] }
cc = "1.0"
[target.'cfg(windows)'.dependencies]
is_elevated = "0.1.2"
registry = "1.2.1"
[target.'cfg(unix)'.dependencies]
sudo = "0.6.0"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.0.0-rc.9", features = ["api-all", "updater"] }
tauri = { version = "1.0.7", features = ["api-all"] }
# Access system process info.
sysinfo = "0.23.12"
sysinfo = "0.24.6"
# 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.
lazy_static = "1.4.0"
# Access to the Windows Registry.
registry = "1.2.1"
once_cell = "1.13.0"
# Program opener.
open = "2.1.2"
open = "3.0.2"
duct = "0.13.5"
# Services
windows-service = "0.6.0"
# Serialization.
serde_json = "1"
base64 = "0.13.0"
# System process elevation.
is_elevated = "0.1.2"
runas = "0.2.1"
# Dependencies for the HTTP(S) proxy.
http = "0.2"
hudsucker = "0.17.2"
hudsucker = "0.19.1"
tracing = "0.1.21"
tokio-rustls = "0.23.0"
tokio-tungstenite = "0.17.0"
@@ -54,6 +60,14 @@ 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"
[features]
# by default Tauri runs in production mode
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL

View File

@@ -1,3 +1,16 @@
fn main() {
cc::Build::new()
.include("mhycrypto")
.cpp(true)
.file("mhycrypto/memecrypto.cpp")
.file("mhycrypto/metadata.cpp")
.file("mhycrypto/metadatastringdec.cpp")
.compile("mhycrypto");
cc::Build::new()
.include("mhycrypto")
.file("mhycrypto/aes.c")
.compile("mhycrypto-aes");
tauri_build::build()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 B

After

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 903 B

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

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

View File

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

93
src-tauri/lang/chs.json Normal file
View File

@@ -0,0 +1,93 @@
{
"lang_name": "简体中文",
"main": {
"title": "Cultivation",
"launch_button": "启动",
"gc_enable": "以 Grasscutter 模式连接",
"https_enable": "使用 HTTPS",
"ip_placeholder": "IP 地址...",
"port_placeholder": "端口...",
"files_downloading": "文件下载中:",
"files_extracting": "文件解压中:"
},
"options": {
"enabled": "已启用",
"disabled": "已禁用",
"game_path": "选择游戏安装路径",
"game_command": "启动游戏的命令行",
"game_executable": "选择游戏可执行文件",
"recover_rsa": "紧急情况下删除补丁文件",
"grasscutter_jar": "选择 Grasscutter JAR 文件",
"toggle_encryption": "启用加密",
"install_certificate": "安装代理证书",
"java_path": "选择自定义 Java 路径",
"grasscutter_with_game": "随游戏自动启动 Grasscutter",
"language": "选择语言",
"background": "设置自定义背景(链接或文件)",
"theme": "设置主题",
"patch_rsa": "自动修改RSA",
"use_proxy": "使用内置代理",
"wipe_login": "清除登录缓存",
"horny_mode": "Horny 模式",
"auto_mongodb": "自动启动 MongoDB"
},
"downloads": {
"grasscutter_fullbuild": "下载 Grasscutter 一体化",
"grasscutter_stable_data": "下载 Grasscutter 稳定版数据",
"grasscutter_latest_data": "下载 Grasscutter 开发版数据",
"grasscutter_stable_data_update": "更新 Grasscutter 稳定版数据",
"grasscutter_latest_data_update": "更新 Grasscutter 开发版数据",
"grasscutter_stable": "下载 Grasscutter 稳定版",
"grasscutter_latest": "下载 Grasscutter 开发版",
"grasscutter_stable_update": "更新 Grasscutter 稳定版",
"grasscutter_latest_update": "更新 Grasscutter 开发版",
"resources": "下载 Grasscutter 资源",
"game": "下载游戏",
"aio_header": "多合一下载:",
"individual_header": "个别部分下载:",
"mods_header": "Mods:",
"migoto": "下载 GIMI 3dmigoto"
},
"download_status": {
"downloading": "下载中",
"extracting": "解压中",
"error": "错误",
"finished": "已完成",
"stopped": "已暂停"
},
"components": {
"select_file": "选择文件或文件夹...",
"select_folder": "选择文件夹...",
"download": "下载",
"delete": "删除",
"install": "安装"
},
"news": {
"latest_commits": "最近提交",
"latest_version": "最新版本"
},
"help": {
"port_help_text": "确保这是 Dispatch 服务器端口,而不是游戏服务器端口。大部分服务器的端口都是 443。",
"game_help_text": "你不需要另外的游戏备份来使用 Grasscutter。这是给想要降级到 2.6 或没有安装游戏的人使用的。",
"gc_stable_jar": "下载当前的 Grasscutter 稳定版,包括 JAR 文件和数据。",
"gc_fullbuild": "下载完整的 Grasscutter 构建版本包括存储库、jar 和资源。 已完全设置,不需要从此菜单下载任何其他内容",
"gc_dev_jar": "下载最新的 Grasscutter 开发版,包括 JAR 文件和数据。",
"gc_stable_data": "下载当前的 Grasscutter 稳定版数据,不包括 JAR 文件。此选项在更新时有帮助。",
"gc_dev_data": "下载最新的 Grasscutter 开发版数据,不包括 JAR 文件。此选项在更新时有帮助。",
"encryption": "此项设置通常应该处于关闭状态。",
"resources": "资源文件在运行 Grasscutter 服务器时是必要的。此选项在已经存在资源文件时不可选。",
"emergency_rsa": "在出现意外情况时自动将 RSA 恢复到原始版本",
"use_proxy": "使用 Cultivation 的内置代理。除非你使用 Fiddler 等软件,否则应启用此项。",
"patch_rsa": "自动修改和恢复 RSA 补丁。 除非您玩的是旧版/非官方版本,或者您手动修改了 RSA否则应该启用此功能。",
"add_delay": "在 3dmigoto 加载程序中设置延迟! \n这应该可以解决加载问题但会在启动游戏时加载 3dmigoto 时增加一点延迟。 \n您现在可以再次使用 3dmigoto 启动。",
"migoto": "用于从 GameBanana 导入模型"
},
"swag": {
"akebi_name": "Akebi",
"migoto_name": "Migoto",
"reshade_name": "Reshade",
"akebi": "选择 Akebi 可执行文件",
"migoto": "选择 3DMigoto 可执行文件",
"reshade": "选择 Reshade 注入器"
}
}

93
src-tauri/lang/cht.json Normal file
View File

@@ -0,0 +1,93 @@
{
"lang_name": "繁體中文",
"main": {
"title": "Cultivation",
"launch_button": "啟動",
"gc_enable": "以Grasscutter模式連接",
"https_enable": "使用 HTTPS",
"ip_placeholder": "IP地址...",
"port_placeholder": "通訊埠...",
"files_downloading": "檔案下載中:",
"files_extracting": "檔案解壓縮中:"
},
"options": {
"enabled": "已啟用",
"disabled": "未啟用",
"game_path": "選擇遊戲安裝路徑",
"game_command": "遊戲啟動命令",
"game_executable": "選擇遊戲執行檔",
"recover_rsa": "緊急恢復RSA",
"grasscutter_jar": "選擇伺服器JAR檔案",
"toggle_encryption": "設定加密",
"install_certificate": "安裝代理憑證",
"java_path": "選擇自定義Java路徑",
"grasscutter_with_game": "伴隨遊戲一起啟動Grasscutter",
"language": "語言",
"background": "選擇自定義背景(網址或檔案)",
"theme": "選擇主題",
"patch_rsa": "自動修補RSA",
"use_proxy": "使用內建代理伺服器",
"wipe_login": "擦除登錄緩存",
"horny_mode": "Horny模式",
"auto_mongodb": "自動啟動 MongoDB"
},
"downloads": {
"grasscutter_fullbuild": "下載Grasscutter多合一下載",
"grasscutter_stable_data": "下載Grasscutter穩定版數據Data",
"grasscutter_latest_data": "下載Grasscutter開發板數據Data",
"grasscutter_stable_data_update": "更新Grasscutter穩定版數據Data",
"grasscutter_latest_data_update": "更新Grasscutter開發板數據Data",
"grasscutter_stable": "下載Grasscutter穩定版",
"grasscutter_latest": "下載Grasscutter開發板",
"grasscutter_stable_update": "更新Grasscutter穩定版",
"grasscutter_latest_update": "更新Grasscutter開發板",
"resources": "下載Grasscutter資源Resources",
"game": "下載遊戲",
"aio_header": "多合一下載:",
"individual_header": "個別部分下載:",
"mods_header": "Mods:",
"migoto": "下載GIMI 3dmigoto"
},
"download_status": {
"downloading": "下載中",
"extracting": "解壓縮中",
"error": "錯誤",
"finished": "已完成",
"stopped": "暫停"
},
"components": {
"select_file": "選擇檔案或資料夾...",
"select_folder": "選擇資料夾...",
"download": "下載",
"delete": "刪除",
"install": "安裝"
},
"news": {
"latest_commits": "最近的PR",
"latest_version": "最新版本"
},
"help": {
"port_help_text": "確保這是Dispatch伺服器端口不是遊戲伺服器端口。 大部分伺服器的端口都是443。",
"game_help_text": "您不需要另外一個遊戲備份來使用Grasscutter。這是給想要降級到2.6或者還沒安裝遊戲的人使用的。",
"gc_stable_jar": "下載當前的Grasscutter穩定版本包括JAR答案還有資料文件。",
"gc_fullbuild": "下載完整的 Grasscutter 構建版本包括存儲庫、jar 和資源。 已完全設置,不需要從此菜單下載任何其他內容",
"gc_dev_jar": "下載當前的Grasscutter穩定版本資料文件其中不會附帶JAR文件。這個選項在更新時很有用。",
"gc_stable_data": "下載當前最新的Grasscutter開發版本資料文件其中不會附帶JAR文件。這個選項在更新時很有用。",
"gc_dev_data": "下載當前最新的Grasscutter開發版本的資料文件其中不會附帶JAR文件。這個選項在更新時很有用。",
"encryption": "在正常情況下,此選項應該被關閉。",
"resources": "資源文件在架設一個Grasscutter伺服器時是必要的。 這個選項會在您已經有裡面有檔案的資源資料夾時不可選。",
"emergency_rsa": "一旦有東西出了問題此選項可以把您的rsa恢復成官方版本。",
"use_proxy": "使用Cultivation內建的代理伺服器。此選項應該被啟用除非你使用其他的代理伺服器。",
"patch_rsa": "自動修補和恢復RSA。除非您的遊戲版本是舊的或者是非官方的此選項應該被啟用。",
"add_delay": "在 3dmigoto 加載程序中設置延遲! \n這應該可以解決加載問題但會在啟動遊戲時加載 3dmigoto 時增加一點延遲。 \n您現在可以再次使用 3dmigoto 啟動。",
"migoto": "用於從 GameBanana 導入模型"
},
"swag": {
"akebi_name": "Akebi",
"migoto_name": "Migoto",
"reshade_name": "Reshade",
"akebi": "選擇Akebi執行檔",
"migoto": "選擇3DMigoto執行檔",
"reshade": "選擇Reshade注入器"
}
}

91
src-tauri/lang/de.json Normal file
View File

@@ -0,0 +1,91 @@
{
"lang_name": "Deutsch",
"main": {
"title": "Cultivation",
"launch_button": "Starten",
"gc_enable": "Über Grasscutter verbinden",
"https_enable": "HTTPS nutzen",
"ip_placeholder": "Server Adresse...",
"port_placeholder": "Port...",
"files_downloading": "Herunterladen von Dateien: ",
"files_extracting": "Extrahieren von Dateien: "
},
"options": {
"enabled": "Aktiviert",
"disabled": "Deaktiviert",
"game_path": "Spielpfad",
"game_executable": "Spiel Datei auswählen",
"recover_rsa": "Notfall Wiederherstellung der RSA",
"grasscutter_jar": "Grasscuter JAR auswählen",
"toggle_encryption": "Verschlüsselung umschalten",
"install_certificate": "Installeer proxy certificaat",
"java_path": "Benutzerdefinierten Java Pfad setzen",
"grasscutter_with_game": "Grasscutter automatisch mit dem Spiel starten",
"language": "Sprache auswählen",
"background": "Benutzerdefinierten Hintergrund festlegen (link oder bild)",
"theme": "Theme auswählen",
"patch_rsa": "RSA automatisch patchen",
"use_proxy": "Gebruik interne proxy",
"wipe_login": "Wis de inlogcache",
"horny_mode": "Geile modus",
"auto_mongodb": "Start automatisch MongoDB"
},
"downloads": {
"grasscutter_fullbuild": "Alles in Einem Grasscutter Daten herunterladen",
"grasscutter_stable_data": "Stabile Grasscutter Daten herunterladen",
"grasscutter_latest_data": "Aktuellste Grasscutter Daten herunterladen",
"grasscutter_stable_data_update": "Stabile Grasscutter Daten aktualisieren",
"grasscutter_latest_data_update": "Aktuellste Grasscutter Daten aktualisieren",
"grasscutter_stable": "Stabile Grasscutter Version herunterladen",
"grasscutter_latest": "Aktuellste Grasscutter Version herunterladen",
"grasscutter_stable_update": "Stabile Grasscutter Version aktualisieren",
"grasscutter_latest_update": "Aktuellste Grasscutter Version aktualisieren",
"resources": "Grasscutter Ressourcen herunterladen",
"game": "Spiel herunterladen",
"aio_header": "Alles in Einem herunterladen",
"individual_header": "Einzelne Teile herunterladen:",
"mods_header": "Mods:",
"migoto": "GIMI 3dmigoto herunterladen"
},
"download_status": {
"downloading": "Lädt herunter",
"extracting": "Extrahiert",
"error": "Fehler",
"finished": "Fertig",
"stopped": "Gestoppt"
},
"components": {
"select_file": "Datei oder Ordner auswählen...",
"select_folder": "Ordner auswählen...",
"download": "Herunterladen",
"delete": "Löschen",
"install": "Installieren"
},
"news": {
"latest_commits": "Letzte Commits",
"latest_version": "Letzte Version"
},
"help": {
"port_help_text": "Vergewissern Sie sich, dass es sich um den Port des Dispatch-Servers handelt, nicht um den Port des Spiel-Servers. Dieser ist fast immer '443'.",
"game_help_text": "Sie müssen keine separate Kopie verwenden, um mit Grasscutter zu spielen. Dies ist entweder für ein Downgrade auf die Version 2.6 oder wenn Sie das Spiel nicht installiert haben.",
"gc_stable_jar": "Laden Sie den aktuellen stabilen Grasscutter-Build herunter, der eine Jar-Datei und Datendateien enthält.",
"gc_fullbuild": "Download een volledige Grasscutter-build, inclusief repo, jar en bronnen. Is volledig ingesteld en vereist geen andere downloads uit dit menu",
"gc_dev_jar": "Laden Sie die neueste Grasscutter-Entwicklungsversion herunter, welche eine Jar-Datei und Datendateien enthält.",
"gc_stable_data": "Laden Sie die stabilen Grasscutter Daten herunter, welche keine Jar-Datei enthalten. Dies ist nützlich zum Aktualisieren.",
"gc_dev_data": "Laden Sie die neuesten Grasscutter-Entwicklungsdateien herunter, welche keine Jar-Datei enthält. Dies ist nützlich zum Aktualisieren.",
"resources": "Diese werden auch benötigt, um einen Grasscutter-Server auszuführen. Diese Schaltfläche ist grau, wenn Sie einen bestehenden Ressourcenordner mit Inhalten haben",
"emergency_rsa": "Im Fall, dass etwas schief laufen sollte, kannst du deine RSA auf die letzte offizielle Version zurücksetzen",
"use_proxy": "Nutze den internen Proxy von Cultivation. Du solltest dies aktivieren, es sei denn du nutzt Programme wie Fiddler",
"patch_rsa": "Patche und aktualisiere deine RSA automatisch. Solange du nicht mit einer alten/nicht offiziellen Version spielst oder deine RSA manuell gepatcht hast, sollte dies aktiviert sein.",
"add_delay": "Verzögerung im 3dmigoto-Lader einstellen! \nDies sollte die Ladeprobleme beheben, führt aber zu einer kleinen Verzögerung, wenn 3dmigoto beim Start des Spiels geladen wird. \nSie können nun wieder mit 3dmigoto starten.",
"migoto": "Zum Importieren von Modellen von GameBanana"
},
"swag": {
"akebi_name": "Akebi",
"migoto_name": "Migoto",
"reshade_name": "Reshade",
"akebi": "Akebi.exe festlegen",
"migoto": "Migoto.exe festlegen",
"reshade": "Reshade injector festlegen"
}
}

View File

@@ -3,7 +3,7 @@
"main": {
"title": "Cultivation",
"launch_button": "Launch",
"gc_enable": "Connect via Grasscutter",
"gc_enable": "Connect to Grasscutter",
"https_enable": "Use HTTPS",
"ip_placeholder": "Server Address...",
"port_placeholder": "Port...",
@@ -11,15 +11,28 @@
"files_extracting": "Files Extracting: "
},
"options": {
"game_exec": "Set Game Executable",
"enabled": "Enabled",
"disabled": "Disabled",
"game_path": "Set Game Install Path",
"game_command": "Game Launch Command",
"game_executable": "Set Game Executable",
"recover_rsa": "Emergency Delete RSA",
"grasscutter_jar": "Set Grasscutter JAR",
"toggle_encryption": "Toggle Encryption",
"install_certificate": "Install Proxy Certificate",
"java_path": "Set Custom Java Path",
"grasscutter_with_game": "Automatically launch Grasscutter with game",
"language": "Select Language",
"background": "Set Custom Background (link or image file)",
"theme": "Set Theme"
"theme": "Set Theme",
"patch_rsa": "Automatically Patch RSA",
"use_proxy": "Use Internal Proxy",
"wipe_login": "Wipe Login Cache",
"horny_mode": "Horny Mode",
"auto_mongodb": "Automatically Start MongoDB"
},
"downloads": {
"grasscutter_fullbuild": "Download Grasscutter All-in-One",
"grasscutter_stable_data": "Download Grasscutter Stable Data",
"grasscutter_latest_data": "Download Grasscutter Latest Data",
"grasscutter_stable_data_update": "Update Grasscutter Stable Data",
@@ -28,7 +41,12 @@
"grasscutter_latest": "Download Grasscutter Latest",
"grasscutter_stable_update": "Update Grasscutter Stable",
"grasscutter_latest_update": "Update Grasscutter Latest",
"resources": "Download Grasscutter Resources"
"resources": "Download Grasscutter Resources",
"game": "Download Game",
"aio_header": "All-in-One Downloads:",
"individual_header": "Individual downloads:",
"mods_header": "Mods:",
"migoto": "Download GIMI 3dmigoto"
},
"download_status": {
"downloading": "Downloading",
@@ -40,7 +58,9 @@
"components": {
"select_file": "Select file or folder...",
"select_folder": "Select folder...",
"download": "Download"
"download": "Download",
"delete": "Delete",
"install": "Install"
},
"news": {
"latest_commits": "Recent Commits",
@@ -50,9 +70,24 @@
"port_help_text": "Ensure this is the Dispatch server port, not the Game server port. This is almost always '443'.",
"game_help_text": "You do not need to use a separate copy to play with Grasscutter. This is for either downgrading to 2.6 or if you do not have the game installed.",
"gc_stable_jar": "Download the current stable Grasscutter build, which includes jar file and data files.",
"gc_fullbuild": "Download a full Grasscutter build, including repo, jar, and resources. Is fully set up and does not require any other downloads from this menu.",
"gc_dev_jar": "Download the latest development Grasscutter build, which includes jar file and data files.",
"gc_stable_data": "Download the current stable Grasscutter data files, which does not come with a jar file. This is useful for updating.",
"gc_dev_data": "Download the latest development Grasscutter data files, which does not come with a jar file. This is useful for updating.",
"resources": "These are also required to run a Grasscutter server. This button will be grey if you have an existing resources folder with contents inside"
"encryption": "This should usually be disabled.",
"resources": "These are also required to run a Grasscutter server. This button will be grey if you have an existing resources folder with contents inside",
"emergency_rsa": "In case something went wrong, force delete RSA patch.",
"use_proxy": "Use the Cultivation internal proxy. You should have this enabled unless you use something like Fiddler",
"patch_rsa": "Patch and unpatch your game RSA automatically. Unless playing with old/non-official versions (3.0 and older), this should be enabled.",
"add_delay": "Set delay in 3dmigoto loader! \nThis should fix loading issues, but will add a small delay to when 3dmigoto is loaded upon launching the game. \nYou can now launch with 3dmigoto again.",
"migoto": "For importing models from GameBanana"
},
"swag": {
"akebi_name": "Akebi",
"migoto_name": "Migoto",
"reshade_name": "Reshade",
"akebi": "Set Akebi/Acrepi Executable",
"migoto": "Set 3DMigoto Executable",
"reshade": "Set Reshade Injector"
}
}
}

84
src-tauri/lang/es.json Normal file
View File

@@ -0,0 +1,84 @@
{
"lang_name": "Español",
"main": {
"title": "Cultivation",
"launch_button": "Launch",
"gc_enable": "Conectar Via Grasscutter",
"https_enable": "Usar HTTPS",
"ip_placeholder": "Dirección del servidor...",
"port_placeholder": "Puerto...",
"files_downloading": "Archivos Descargandose: ",
"files_extracting": "Archivos Extrayendose: "
},
"options": {
"enabled": "Activado",
"disabled": "Desactivado",
"game_path": "Ruta de instalación del juego",
"game_command": "Comando de lanzamiento del juego",
"game_executable": "Establecer ejecutable del juego",
"recover_rsa": "Recuperación de RSA de Emergencia",
"grasscutter_jar": "Establecer JAR de Grasscutter",
"toggle_encryption": "Alternar Cifrado",
"install_certificate": "Instalar Certificado Proxie",
"java_path": "Establecer Ruta Personalizada de Java",
"grasscutter_with_game": "Iniciar automáticamente Grasscutter con el juego",
"language": "Seleccionar Idioma",
"background": "Establecer Fondo Personalizado (link o archivo de imagen)",
"patch_rsa": "Parchear RSA automáticamente",
"theme": "Establecer Tema",
"use_proxy": "Usar proxy interno",
"wipe_login": "Borrar caché de inicio de sesión",
"horny_mode": "Modo cachondo",
"auto_mongodb": "Iniciar automáticamente MongoDB"
},
"downloads": {
"grasscutter_fullbuild": "Descargar Datos todo en uno de Grasscutter",
"grasscutter_stable_data": "Descargar Datos Estables de Grasscutter",
"grasscutter_latest_data": "Descargar Datos más Recientes de Grasscutter",
"grasscutter_stable_data_update": "Actualizar Datos Estables de Grasscutter",
"grasscutter_latest_data_update": "Actualizar Datos más Recientes de Grasscutter",
"grasscutter_stable": "Descargar Grasscutter Estable",
"grasscutter_latest": "Descargar Grasscutter más reciente",
"grasscutter_stable_update": "Actualizar Grasscutter Estable",
"grasscutter_latest_update": "Actualizar Grasscutter más reciente",
"resources": "Descargar Recursos de Grasscutter",
"game": "Descarga el juego",
"aio_header": "Descargas todo en uno:",
"individual_header": "Descargas de piezas individuales:",
"mods_header": "Mods:",
"migoto": "Descargar GIMI 3dmigoto"
},
"download_status": {
"downloading": "Descargando",
"extracting": "Extrayendo",
"error": "Error",
"finished": "Finalizado",
"stopped": "Detenido"
},
"components": {
"select_file": "Seleccionar el archivo o carpeta...",
"select_folder": "Seleccionar la carpeta...",
"download": "Descargar",
"delete": "Borrar",
"install": "Instalar"
},
"news": {
"latest_commits": "Commits Recientes",
"latest_version": "Ultima versión"
},
"help": {
"port_help_text": "Asegúrese de que este sea el Dispatch server port, no el Game server port. Este es casi siempre '443'.",
"game_help_text": "No necesitas usar una copia separada para jugar con Grasscutter. Esto es para cambiar a 2.6 o si no tienes el juego instalado.",
"gc_stable_jar": "Descargue la versión Estable actual de Grasscutter, que incluye el archivo jar y los archivos de datos.",
"gc_fullbuild": "Descarga una compilación completa de Grasscutter, incluyendo repo, jar y recursos. Está totalmente configurado y no requiere ninguna otra descarga desde este menú.",
"gc_dev_jar": "Descargue la última versión de Desarrollo de Grasscutter, que incluye archivos jar y archivos de datos.",
"gc_stable_data": "Descargue los archivos de Datos Estables actuales de Grasscutter, que no vienen con un archivo jar. Esto es útil para actualizar.",
"gc_dev_data": "Descargue los últimos archivos de Datos de Desarrollo de Grasscutter, que no vienen con un archivo jar. Esto es útil para actualizar.",
"resources": "Estos también son necesarios para ejecutar un servidor Grasscutter. Este botón estará gris si tiene una carpeta de recursos existente con contenido dentro.",
"add_delay": "¡Retraso en el cargador de 3dmigoto! \nEsto debería solucionar los problemas de carga, pero añadirá un pequeño retraso cuando 3dmigoto se cargue al iniciar el juego. \nAhora puede iniciar con 3dmigoto de nuevo.",
"migoto": "Para importar modelos de GameBanana"
},
"swag": {
"akebi": "Establecer el ejecutable de Akebi"
}
}

80
src-tauri/lang/fr.json Normal file
View File

@@ -0,0 +1,80 @@
{
"lang_name": "Francais",
"main": {
"title": "Cultivation",
"launch_button": "Lancer",
"gc_enable": "Se connecter avec Grasscutter",
"https_enable": "Utiliser HTTPS",
"ip_placeholder": "Adresse du serveur...",
"port_placeholder": "Port...",
"files_downloading": "Fichiers en cours de telechargement: ",
"files_extracting": "Fichiers en cours d'extraction: "
},
"options": {
"enabled": "active",
"disabled": "desactiver",
"game_path": "Définir le chemin d'installation du jeu",
"game_command": "Commande de lancement du jeu",
"game_executable": "definir l'executable du jeu",
"recover_rsa": "Récupération d'urgence des RSA",
"grasscutter_jar": "definir le Jar Grasscutter",
"toggle_encryption": "activer l'encryption",
"install_certificate": "Installer le certificat du proxy",
"java_path": "definir un chemin java personnalise",
"grasscutter_with_game": "Lancer Grasscutter automatiquement avec le jeu",
"language": "Choisir la langue",
"background": "definir un arriere plan personnalise (lien ou fichier image)",
"theme": "definir un theme",
"patch_rsa": "Corriger automatiquement les RSA",
"use_proxy": "Utiliser un proxy interne",
"wipe_login": "Effacer le cache de connexion",
"horny_mode": "Mode excitation",
"auto_mongodb": "Démarrer automatiquement MongoDB"
},
"downloads": {
"grasscutter_fullbuild": "Telecharger Grasscutter tout-en-un",
"grasscutter_stable_data": "Telecharger les donnees de Grasscutter (version stable)",
"grasscutter_latest_data": "Telecharger les donnees de Grasscutter (derniere version)",
"grasscutter_stable_data_update": "Mettre a jour les donnees de Grasscutter (version stable)",
"grasscutter_latest_data_update": "Mettre a jour les donnees de Grasscutter (derniere version)",
"grasscutter_stable": "Telecharger Grasscutter (version stable)",
"grasscutter_latest": "Telecharger Grasscutter (derniere version)",
"grasscutter_stable_update": "Mettre a jour Grasscutter (version stable)",
"grasscutter_latest_update": "Mettre a jour Grasscutter (derniere version)",
"resources": "Telecharger les ressources logicielles de Grasscutter",
"aio_header": "Telechargements tout-en-un:",
"individual_header": "Telechargements de pièces individuelles:",
"mods_header": "Mods:",
"migoto": "Telecharger GIMI 3dmigoto"
},
"download_status": {
"downloading": "Telechargement",
"extracting": "Extraction",
"error": "Erreur",
"finished": "Termine",
"stopped": "Arrete"
},
"components": {
"select_file": "choisir fichier ou dossier...",
"select_folder": "choisir dossier...",
"download": "Telecharger",
"delete": "Supprimer",
"install": "Installer"
},
"news": {
"latest_commits": "Recents Commits",
"latest_version": "Derniere version"
},
"help": {
"port_help_text": "Assurez-vous que c'est le port serveur Dispatch, et non le port du serveur de jeu. C'est presque toujours '433'.",
"game_help_text": "Vous n'avez pas besoin d'une copie differente du jeu pour jouer avec Grasscutter. Cela est ou pour retrograder en 2.6 ou si vous n'avez pas le jeu d'installe",
"gc_stable_jar": "Telecharger le dernier build stable de Grasscutter, ce qui inclut le fichier jar et les fichiers de donnees",
"gc_fullbuild": "Téléchargez un build complet de Grasscutter, incluant le repo, le jar et les ressources. Il est entièrement configuré et ne nécessite aucun autre téléchargement à partir de ce menu.",
"gc_dev_jar": "Telecharger le dernier build en development de Grasscutter, ce qui inclut le fichier jar et les fichiers de donnees",
"gc_stable_data": "Telecharger le dernier build stable de Grasscutter, ce qui n'inclut pasle fichier jar. Cela est utile pour mettre a jour",
"gc_dev_data": "Telecharger le dernier build en development de Grasscutter, ce qui n'inclut pasle fichier jar. Cela est utile pour mettre a jour",
"resources": "Les ressources sont aussi necessaires pour lancer un serveur Grasscutter. Ce bouton deviendra gris si vous avez deja un fichier ressources avec les donnees dedans.",
"add_delay": "Définir un délai dans le chargeur de 3dmigoto ! \nCela devrait résoudre les problèmes de chargement, mais ajoutera un petit délai au chargement de 3dmigoto lors du lancement du jeu. \nVous pouvez maintenant lancer le jeu avec 3dmigoto.",
"migoto": "Pour importer des modèles depuis GameBanana"
}
}

View File

@@ -10,15 +10,28 @@
"files_extracting": "MengExtract File: "
},
"options": {
"game_exec": "Set Game Executable",
"enabled": "Diaktifkan",
"disabled": "Dinonaktifkan",
"game_path": "Mengatur jalur penginstalan game",
"game_command": "Perintah peluncuran game",
"game_executable": "Mengatur game yang dapat dieksekusi",
"recover_rsa": "Pemulihan RSA darurat",
"grasscutter_jar": "Path ke Grasscutter JAR",
"toggle_encryption": "Alihkan enkripsi",
"install_certificate": "Instal sertifikat proxy",
"java_path": "Atur kustom Java path",
"grasscutter_with_game": "Otomatis Menjalankan Grasscutter Dengan Game",
"language": "Pilih Bahasa",
"background": "Atur Kustom Latar Belakang (link atau gambar file)",
"theme": "Atur Tema"
"theme": "Atur Tema",
"patch_rsa": "Automatically Patch RSA",
"use_proxy": "Gunakan Proxy Internal",
"wipe_login": "Menghapus Cache Login",
"horny_mode": "Mode Terangsang",
"auto_mongodb": "Mulai MongoDB secara otomatis"
},
"downloads": {
"downloads": {
"grasscutter_fullbuild": "Sedang Mendownload Grasscutter Semua Dalam Satu",
"grasscutter_stable_data": "Sedang Mendownload Grasscutter Versi Stabil",
"grasscutter_latest_data": "Sedang Mendownload Grasscutter Data Terbaru",
"grasscutter_stable_data_update": "Memperbaharui Grasscutter Data Stabil",
@@ -27,7 +40,11 @@
"grasscutter_latest": "Download Grasscutter Terbaru Version",
"grasscutter_stable_update": "Sedang MengUpdate Grasscutter Stabil",
"grasscutter_latest_update": "Sedang MengUpdate Grasscutter Terbaru",
"resources": "Mendownload Grasscutter Resources"
"resources": "Mendownload Grasscutter Resources",
"aio_header": "Unduhan Semua Dalam Satu:",
"individual_header": "Unduhan Bagian Individual:",
"mods_header": "Mods:",
"migoto": "Sedang Mendownload GIMI 3dmigoto"
},
"download_status": {
"downloading": "Sedang Mendownload",
@@ -39,7 +56,8 @@
"components": {
"select_file": "Pilih File Atau Folder...",
"select_folder": "Pilih Folder...",
"download": "download"
"download": "download",
"delete": "Menghapus"
},
"news": {
"latest_commits": "Commit Terbaru",
@@ -49,9 +67,12 @@
"port_help_text": "Pastikan ini adalah port server port, bukan port server Game. ini Hampir Selalu '443'.",
"game_help_text": "Anda tidak perlu menggunakan salinan Genshin Impact untuk bermain dengan Grasscutter. Ini untuk menurunkan versi ke 2.6 atau jika Anda belum menginstal game..",
"gc_stable_jar": "Unduh Build Stabil Grasscutter Saat ini, Dimana Ada Jar File Dan Data File.",
"gc_fullbuild": "Unduh versi lengkap Grasscutter, termasuk repo, jar, dan sumber daya. Sudah diatur sepenuhnya dan tidak memerlukan unduhan lain dari menu ini.",
"gc_dev_jar": "Unduh Build Development Grasscutter saat ini, Dimana Ada Jar File Dan Data File.",
"gc_stable_data": "Unduh file data Grasscutter stabil saat ini, dimana Tidak Ada JAR file. Ini Berguna Untuk memperbarui.",
"gc_dev_data": "Unduh file data Grasscutter Development saat ini, dimana Tidak Ada JAR file. Ini Berguna Untuk memperbarui.",
"resources": "Ini juga diperlukan untuk menjalankan server Grasscutter. Tombol ini akan berwarna abu-abu jika Anda memiliki folder Resource yang ada dengan File di dalamnya"
"resources": "Ini juga diperlukan untuk menjalankan server Grasscutter. Tombol ini akan berwarna abu-abu jika Anda memiliki folder Resource yang ada dengan File di dalamnya",
"add_delay": "Atur penundaan di pemuat 3dmigoto! \nIni akan memperbaiki masalah pemuatan, tetapi akan menambah sedikit penundaan ketika 3dmigoto dimuat saat meluncurkan game. \nAnda sekarang dapat meluncurkan dengan 3dmigoto lagi.",
"migoto": "Untuk mengimpor model dari GameBanana"
}
}
}

93
src-tauri/lang/ko.json Normal file
View File

@@ -0,0 +1,93 @@
{
"lang_name": "한국어",
"main": {
"title": "Cultivation",
"launch_button": "게임 시작",
"gc_enable": "Grasscutter 연결",
"https_enable": "HTTPS 사용",
"ip_placeholder": "서버 주소",
"port_placeholder": "포트",
"files_downloading": "파일 다운로드 중: ",
"files_extracting": "파일 추출 중: "
},
"options": {
"enabled": "활성",
"disabled": "비활성",
"game_path": "게임 설치 경로 설정",
"game_command": "게임 시작 명령",
"game_executable": "게임 실행 파일 설정",
"recover_rsa": "긴급 RSA 복원",
"grasscutter_jar": "그래스커터 JAR 설정",
"toggle_encryption": "암호화 전환",
"install_certificate": "프록시 인증서 설치",
"java_path": "사용자 지정 Java 경로 설정",
"grasscutter_with_game": "게임에서 자동으로 그래스커터 실행",
"language": "언어 선택",
"background": "사용자 지정 배경 설정(링크 또는 이미지 파일)",
"theme": "테마 설정",
"patch_rsa": "RSA 패치 자동 적용",
"use_proxy": "내부 프록시 사용",
"wipe_login": "로그인 캐시 지우기",
"horny_mode": "Horny 모드",
"auto_mongodb": "MongoDB 자동 시작"
},
"downloads": {
"grasscutter_fullbuild": "올인원 Grasscutter 다운로드",
"grasscutter_stable_data": "안정적인 데이터 다운로드",
"grasscutter_latest_data": "최신 데이터 다운로드",
"grasscutter_stable_data_update": "안정적 데이터 업데이트",
"grasscutter_latest_data_update": "최신 데이터 업데이트",
"grasscutter_stable": "안정 다운로드",
"grasscutter_latest": "최신 다운로드",
"grasscutter_stable_update": "안정 업데이트",
"grasscutter_latest_update": "최신 업데이트",
"resources": "리소스 다운로드",
"game": "게임 다운로드",
"aio_header": "올인원 다운로드",
"individual_header": "개별 부품 다운로드:",
"mods_header": "Mods:",
"migoto": "GIMI 3dmigoto 다운로드"
},
"download_status": {
"downloading": "다운로드 중",
"extracting": "추출 중",
"error": "오류",
"finished": "완료",
"stopped": "중지"
},
"components": {
"select_file": "파일 또는 폴더 선택...",
"select_folder": "폴더 선택...",
"download": "다운로드",
"delete": "삭제",
"install": "설치"
},
"news": {
"latest_commits": "공지 사항",
"latest_version": "최신 버전"
},
"help": {
"port_help_text": "게임 서버 포트가 아닌 패치 서버 포트인지 확인하십시오. (기본 포트: 443)",
"game_help_text": "그래스커터를 사용하기 위해 별도의 복사본을 사용할 필요가 없습니다. 이는 2.6으로 다운그레이드 하거나 게임을 설치하지 않은 경우에 적용됩니다.",
"gc_stable_jar": "jar 파일과 데이터 파일이 포함된 현재 안정적인 Grasscuter 빌드를 다운로드합니다.",
"gc_fullbuild": "저장소, 저장소, 리소스를 포함한 Grasscutter 정식 버전을 다운로드하세요. 모든 준비가 완료되었으며 이 메뉴에서 다른 다운로드는 필요하지 않습니다.",
"gc_dev_jar": "jar 파일 및 데이터 파일이 포함된 최신 개발 Grasscuter 빌드를 다운로드하십시오.",
"gc_stable_data": "jar 파일과 함께 제공되지 않는 현재 안정적인 Grasscuter 데이터 파일을 다운로드합니다. 이것은 업데이트하는 데 유용합니다.",
"gc_dev_data": "jar 파일과 함께 제공되지 않는 최신 개발 Grasscuter 데이터 파일을 다운로드합니다. 이것은 업데이트하는 데 유용합니다.",
"encryption": "일반적으로 이 기능을 사용하지 않도록 설정해야 합니다.",
"resources": "또한 Grasscutter 서버를 실행하는 데도 필요합니다. 내용이 포함된 기존 리소스 폴더가 있는 경우 이 버튼은 회색으로 표시됩니다",
"emergency_rsa": "문제가 있는 경우 RSA 패치를 제거하십시오.",
"use_proxy": "Culturation 내부 프록시를 사용합니다. 피들러와 같은 것을 사용하지 않는 한 이 기능을 활성화해야 합니다",
"patch_rsa": "게임 RSA를 자동으로 패치 및 패치 해제합니다. 이전/비공식 버전을 사용하거나 RSA를 수동으로 패치하지 않은 경우 이 기능을 활성화해야 합니다.",
"add_delay": "3dmigoto 로더에서 지연을 설정하세요! \n이렇게하면 로딩 문제가 해결되지만 게임을 시작할 때 3dmigoto가로드되는시기에 약간의 지연이 추가됩니다. \n이제 3dmigoto로 다시 시작할 수 있습니다.",
"migoto": "GameBanana에서 모델 가져오기"
},
"swag": {
"akebi_name": "Akebi",
"migoto_name": "Migoto",
"reshade_name": "Reshade",
"akebi": "Akebi 실행 파일 설정",
"migoto": "3DMigoto 실행 파일 설정",
"reshade": "Reshade 인젝터 설정"
}
}

77
src-tauri/lang/lv.json Normal file
View File

@@ -0,0 +1,77 @@
{
"lang_name": "Latviešu",
"main": {
"title": "Cultivation",
"launch_button": "Palaist",
"gc_enable": "Savienot ar Grasscutter",
"https_enable": "Izm. HTTPS",
"ip_placeholder": "Servera Adrese...",
"port_placeholder": "Ports...",
"files_downloading": "Failu Lejupielāde: ",
"files_extracting": "Failu Izvilkšana: "
},
"options": {
"enabled": "Iespējots",
"disabled": "Atspējots",
"game_executable": "Iestatīt spēles izpildāmu",
"recover_rsa": "Avārijas RSA atjaunošana",
"grasscutter_jar": "Iestatiet Grasscutter JAR",
"toggle_encryption": "Pārslēgt Šifrēšanu",
"install_certificate": "Proxy sertifikāta instalēšana",
"java_path": "Iestatiet pielāgotu Java ceļu",
"grasscutter_with_game": "Automātiski palaidiet Grasscutter ar spēli",
"language": "Izvēlēties valodu",
"background": "Iestatīt pielāgotu fonu (saite vai attēla fails)",
"theme": "Iestatīt tēmu",
"patch_rsa": "Automātiski ielāpot RSA",
"use_proxy": "Izmantot iekšējo starpniekserveri",
"wipe_login": "Noslaucīt pieteikšanās kešatmiņu",
"horny_mode": "Uzbudināts režīms",
"auto_mongodb": "Automātiski startējiet MongoDB"
},
"downloads": {
"grasscutter_fullbuild": "Lejupielādējiet Grasscutter viss vienā",
"grasscutter_stable_data": "Lejupielādējiet Grasscutter stabilos datus",
"grasscutter_latest_data": "Lejupielādējiet Grasscutter jaunākos datus",
"grasscutter_stable_data_update": "Atjauniniet Grasscutter stabilos datus",
"grasscutter_latest_data_update": "Atjauniniet Grasscutter jaunākos datus",
"grasscutter_stable": "Lejupielādēt Grasscutter stabilo",
"grasscutter_latest": "Lejupielādēt Grasscutter jaunāko",
"grasscutter_stable_update": "Atjauniet Grasscutter stabilo",
"grasscutter_latest_update": "Atjauniet Grasscutter jaunāko",
"resources": "Lejupielādējiet Grasscutter resursi",
"aio_header": "Lejupielādes viss vienā",
"individual_header": "Atsevišķu daļu lejupielādes:",
"mods_header": "Mods:",
"migoto": "Lejupielādēt GIMI 3dmigoto"
},
"download_status": {
"downloading": "Notiek lejupielāde",
"extracting": "Notiek izvilkšana",
"error": "Kļūda",
"finished": "Pabeigts",
"stopped": "Partraukta"
},
"components": {
"select_file": "Izvēlēties failu vai mapu...",
"select_folder": "Izvēlēties mapu...",
"download": "Lejupielādēt",
"delete": "Dzēst"
},
"news": {
"latest_commits": "Nesen kommitus",
"latest_version": "Jaunākā versija"
},
"help": {
"port_help_text": "Pārliecinieties, vai tas ir Dispatch-servera ports, nevis spēļu servera ports. Tas gandrīz vienmēr ir '443'.",
"game_help_text": "Lai spēlētu ar Grasscutter, jums nav jāizmanto atsevišķa kopija. Tas ir izveidots, lai pazeminātu versiju uz 2.6 vai ja jums nav instalēta spēle.",
"gc_stable_jar": "Lejupielādējiet pašreizējo stabilo Grasscutter versiju, kuram ir jar failu un datu failus.",
"gc_fullbuild": "Lejupielādējiet pilnu Grasscutter versiju, tostarp repozitorijus, krātuves un resursus. Viss ir gatavs, un no šīs izvēlnes nav nepieciešama nekāda cita lejupielāde.",
"gc_dev_jar": "Lejupielādējiet jaunāko izstrāde Grasscutter versiju, kuram ir jar failu un datu failus.",
"gc_stable_data": "Lejupielādējiet pašreizējos stabilos Grasscutter datu failus, kuriem nav jar fails. Tas ir noderīgi atjaunināšanai.",
"gc_dev_data": "Lejupielādējiet jaunāko izstrāde Grasscutter datu failus, kuriem nav pievienots jar fails. Tas ir noderīgi atjaunināšanai.",
"resources": "Tie ir nepieciešami arī Grasscutter servera darbināšanai. Šī poga būs pelēka, ja jums ir resursu mape ar saturu.",
"add_delay": "Iestatiet kavēšanos 3dmigoto iekrāvē! \nTam vajadzētu novērst ielādes problēmas, bet tas nedaudz aizkavēs 3dmigoto ielādēšanu, uzsākot spēli. \nTagad atkal varat sākt ar 3dmigoto.",
"migoto": "Modeļu importēšanai no GameBanana"
}
}

92
src-tauri/lang/nl.json Normal file
View File

@@ -0,0 +1,92 @@
{
"lang_name": "Nederlands",
"main": {
"title": "Cultivation",
"launch_button": "Start",
"gc_enable": "Verbind Met Grasscutter",
"https_enable": "Gebruik HTTPS",
"ip_placeholder": "Server Address...",
"port_placeholder": "Poort...",
"files_downloading": "Bestanden Aan Downloaden: ",
"files_extracting": "Bestanden Uitpakken: "
},
"options": {
"enabled": "Ingeschakeld",
"disabled": "Uitgeschakeld",
"game_path": "Spel Installatie Pad Instellen",
"game_executable": "Stel De Exe Van Het Spel In",
"recover_rsa": "Noodherstel Van De RSA",
"grasscutter_jar": "Stel De Grasscutter JAR In",
"toggle_encryption": "Versleuteling Inschakelen",
"install_certificate": "Proxy-certificaat installeren",
"java_path": "Aangepast Java-pad Instellen",
"grasscutter_with_game": "Start Automatisch Grasscutter Met Spel",
"language": "Selecteer Taal",
"background": "Aangepaste Achtergrond Instellen (link of afbeeldingsbestand)",
"theme": "Thema instellen",
"patch_rsa": "RSA Automatisch Bijwerken",
"use_proxy": "Gebruik Interne Proxy",
"wipe_login": "Login cache wissen",
"horny_mode": "Geile modus",
"auto_mongodb": "Start automatisch MongoDB"
},
"downloads": {
"grasscutter_fullbuild": "Grasscutter Alles-in-één Downloaden",
"grasscutter_stable_data": "Download Stabiele Gegevens Van Grasscutter",
"grasscutter_latest_data": "Download De Nieuwste Gegevens Van Grasscutter",
"grasscutter_stable_data_update": "Stabiele gegevens Van Grasscutter bijwerken",
"grasscutter_latest_data_update": "Nieuwste gegevens Van Grasscutter bijwerken",
"grasscutter_stable": "Download Stabiele Versie Van Grasscutter",
"grasscutter_latest": "Download De Nieuwste Versie Van Grasscutter",
"grasscutter_stable_update": "Update Grasscutter Naar De Stabiele Versie",
"grasscutter_latest_update": "Update Grasscutter Naar De Nieuwste Versie",
"resources": "Download Grasscutter bronnen",
"game": "Download Spel",
"aio_header": "Alles-in-één Downloads:",
"individual_header": "Downloads van afzonderlijke onderdelen:",
"mods_header": "Mods:",
"migoto": "Download GIMI 3dmigoto"
},
"download_status": {
"downloading": "Aan Het Downloading",
"extracting": "Uitpakken",
"error": "Fout",
"finished": "Voltooid",
"stopped": "Gestopt"
},
"components": {
"select_file": "Select file or folder...",
"select_folder": "Select folder...",
"download": "Download",
"delete": "Verwijder",
"install": "Install"
},
"news": {
"latest_commits": "Recente Opdrachten",
"latest_version": "Nieuwste Versie"
},
"help": {
"port_help_text": "Zorg ervoor dat dit de Dispatch server poort is, niet de Game server poort. Dit is bijna altijd '443'.",
"game_help_text": "U hoeft geen aparte kopie te gebruiken om met Grasscutter te spelen. Dit is voor downgraden naar 2.6 of als u het spel niet geinstalleerd heeft.",
"gc_stable_jar": "Download de huidige stabiele Grasscutter build, die jar file en data bestanden bevat.",
"gc_fullbuild": "Download een volledige Grasscutter build, inclusief repo, jar en resources. Is volledig ingesteld en vereist geen andere downloads uit dit menu.",
"gc_dev_jar": "Download de laatste ontwikkeling Grasscutter build, die jar file en data bestanden bevat.",
"gc_stable_data": "Download de huidige stabiele versie van de Grasscutter data bestanden, die niet met een jar file komen. Dit is handig voor het bijwerken.",
"gc_dev_data": "Download de nieuwste versie van de Grasscutter data bestanden, die niet met een jar file komen. Dit is handig voor het bijwerken.",
"encryption": "Dit wordt meestal uitgeschakeld.",
"resources": "Deze zijn ook nodig om een Grasscutter server te draaien. Deze knop zal grijs zijn als u een bestaande resources map heeft met inhoud erin",
"emergency_rsa": "Voor het geval er iets fout is gegaan, herstel uw rsa naar de laatste offici<63>le versies rsa.",
"use_proxy": "Gebruik de Cultivation interne proxy. U zou dit ingeschakeld moeten hebben, tenzij u iets als Fiddler gebruikt",
"patch_rsa": "Patch en unpatch je spel rsa automatisch. Tenzij je met oude/niet-offici<63>le versies speelt, of je hebt je rsa handmatig gepatcht, zou dit ingeschakeld moeten zijn.",
"add_delay": "Vertraging instellen in 3dmigoto loader! \nDit zou laadproblemen moeten oplossen, maar zal een kleine vertraging toevoegen aan het laden van 3dmigoto bij het opstarten van het spel. \nJe kunt nu weer starten met 3dmigoto.",
"migoto": "Voor het importeren van modellen uit GameBanana"
},
"swag": {
"akebi_name": "Akebi",
"migoto_name": "Migoto",
"reshade_name": "Reshade",
"akebi": "Stel Akebi Exe Bestand In",
"migoto": "Stel 3DMigoto Exe Bestand In",
"reshade": "Stel Reshade Injector In"
}
}

93
src-tauri/lang/pt-br.json Normal file
View File

@@ -0,0 +1,93 @@
{
"lang_name": "Português Brasileiro",
"main": {
"title": "Cultivation: Edição Thorny",
"launch_button": "Iniciar",
"gc_enable": "Conectar ao Grasscutter",
"https_enable": "Usar HTTPS",
"ip_placeholder": "Endereço do Servidor...",
"port_placeholder": "Porta...",
"files_downloading": "Baixando Arquivos: ",
"files_extracting": "Extraindo Arquivos: "
},
"options": {
"enabled": "Habilitado",
"disabled": "Desabilitado",
"game_path": "Definir o Local de Instalação do Jogo",
"game_command": "Comando de Iniciação do Jogo",
"game_executable": "Definir o Executavel do Jogo",
"recover_rsa": "Exclusão de Emergencia de RSA",
"grasscutter_jar": "Definir o arquivo JAR do Grasscutter",
"toggle_encryption": "Ativar/Desativar Criptografia",
"install_certificate": "Instalar o Certificado de Proxy",
"java_path": "Definir um Local Customizado do Java",
"grasscutter_with_game": "Iniciar automaticamente o Grasscutter com o Jogo",
"language": "Selecionar Idioma",
"background": "Definir Fundo Customizado (link ou arquivo de imagem)",
"theme": "Definir Tema",
"patch_rsa": "Automaticamente Corrigir RSA",
"use_proxy": "Usar Proxy Interno",
"wipe_login": "Limpar Cache de Login",
"horny_mode": "Modo com tesão",
"auto_mongodb": "Iniciar MongoDB Automaticamente"
},
"downloads": {
"grasscutter_fullbuild": "Baixar o Grasscutter Tudo-em-Um",
"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",
"grasscutter_latest_data_update": "Atualizar os Dados do Grasscutter Mais Recente",
"grasscutter_stable": "Baixar o Grasscutter Estável",
"grasscutter_latest": "Baixar o Grasscutter Mais Recente",
"grasscutter_stable_update": "Atualizar o Grasscutter Estável",
"grasscutter_latest_update": "Atualizar o Grasscutter Mais Recente",
"resources": "Baixar os Recursos do Grasscutter ",
"game": "Baixar o Jogo",
"aio_header": "Downloads Tudo-em-Um:",
"individual_header": "Downloads Individuais:",
"mods_header": "Mods:",
"migoto": "Baixar o GIMI 3dmigoto"
},
"download_status": {
"downloading": "Baixando",
"extracting": "Extraindo",
"error": "Erro",
"finished": "Finalizado",
"stopped": "Parado"
},
"components": {
"select_file": "Selecione o arquivo ou pasta...",
"select_folder": "Selecione a pasta...",
"download": "Baixar",
"delete": "Deletar",
"install": "Instalar"
},
"news": {
"latest_commits": "Commits Recentes",
"latest_version": "Versão mais Recente"
},
"help": {
"port_help_text": "Certifique-se de que esta é a porta do servidor dispatch, não a porta do jogo. Ela é quase sempre '443'.",
"game_help_text": "Você não precisa de uma cópia do jogo separada para jogar com o Grasscutter. Isso é para, ou desatualizar para a 2.6, ou se você não tiver o jogo instalado.",
"gc_stable_jar": "Baixar a versão atual do Grasscutter estável, que inclui o arquivo jar e os arquivos de dados.",
"gc_fullbuild": "Baixar uma versão completa do Grasscutter, incluindo a repo, jar e recursos. Ela está totalmente configurada e não requer nenhum outro download deste menu.",
"gc_dev_jar": "Baixar a versão de desenvolvimento mais recente do Grasscutter, que inclui o arquivo jar e os arquivos de dados.",
"gc_stable_data": "Baixar os arquivos de dados da versão atual do Grasscutter estável, que não inclui o arquivo jar. Isso é útil para atualizações.",
"gc_dev_data": "Baixar os arquivos de dados da versão de desenvolvimento mais recente do Grasscutter, que não vem com um arquivo jar. Isso é útil para atualizações.",
"encryption": "Isso normalmente deve estar desativado.",
"resources": "Esses também são necessários para usar o Grasscutter. Esse botão ficará cinza caso você já tenha uma pasta resources com coisas dentro.",
"emergency_rsa": "Caso algo dê errado, força a exclusão da correção RSA.",
"use_proxy": "Usa o proxy interno do Cultivation. Isso deveria estar habilitado a não ser que você utilize algo como o Fiddler.",
"patch_rsa": "Corrigir e 'descorrigir' o RSA do seu jogo automaticamente. Isso deve estar habilitado a não ser que você esteja jogando com versões antigas (3.0 ou mais antigas) ou não oficiais.",
"add_delay": "Atraso definido na carregadeira 3dmigoto! \nIsto deve resolver os problemas de carregamento, mas acrescentará um pequeno atraso quando o 3dmigoto for carregado no lançamento do jogo. \nAgora você pode lançar novamente com o 3dmigoto.",
"migoto": "Para importação de modelos da GameBanana"
},
"swag": {
"akebi_name": "Akebi",
"migoto_name": "Migoto",
"reshade_name": "Reshade",
"akebi": "Definir o Executavel do Akebi/Acrepi",
"migoto": "Definir o Executavel do 3DMigoto",
"reshade": "Definir o Executavel do Reshade Injector"
}
}

92
src-tauri/lang/ru.json Normal file
View File

@@ -0,0 +1,92 @@
{
"lang_name": "Русский",
"main": {
"title": "Cultivation",
"launch_button": "Запустить",
"gc_enable": "Подключиться с Grasscutter",
"https_enable": "Исп. HTTPS",
"ip_placeholder": "Айпи адрес...",
"port_placeholder": "Порт...",
"files_downloading": "Файлов скачано: ",
"files_extracting": "Извлечено файлов: "
},
"options": {
"enabled": "Включено",
"disabled": "Выключено",
"game_path": "Установить путь к файлам игры",
"game_executable": "Установить исполняемый файл игры",
"recover_rsa": "Принудительное удаление RSA",
"grasscutter_jar": "Установить Grasscutter JAR",
"toggle_encryption": "Переключить шифрование",
"install_certificate": "Установить сертификат для работы Прокси",
"java_path": "Установить пользовательский путь Java",
"grasscutter_with_game": "Автоматически запускать Grasscutter вместе с игрой",
"language": "Установить язык",
"background": "Установить свой фон (ссылка или файл)",
"theme": "Установить тему",
"patch_rsa": "Автоматическое исправление RSA",
"use_proxy": "Использовать встроенный Прокси",
"wipe_login": "Очистить кэш входа в систему",
"horny_mode": "роговой режим",
"auto_mongodb": "Автоматически запускать MongoDB"
},
"downloads": {
"grasscutter_fullbuild": "Скачать все в одном Grasscutter",
"grasscutter_stable_data": "Скачать стабильные данные Grasscutter",
"grasscutter_latest_data": "Скачать последние данные Grasscutter",
"grasscutter_stable_data_update": "Обновить стабильные данные Grasscutter",
"grasscutter_latest_data_update": "Обновить последние данные Grasscutter",
"grasscutter_stable": "Скачать стабильную версию Grasscutter",
"grasscutter_latest": "Скачать последнюю версию Grasscutter",
"grasscutter_stable_update": "Обновить стабильную версию Grasscutter",
"grasscutter_latest_update": "Обновить последнюю версию Grasscutter",
"resources": "Скачать ресурсы Grasscutter",
"game": "Скачать Игру",
"aio_header": "Все в одной загрузке:",
"individual_header": "загрузка отдельных частей:",
"mods_header": "Mods:",
"migoto": "Скачать GIMI 3dmigoto"
},
"download_status": {
"downloading": "Скачивание",
"extracting": "Извлечение",
"error": "Ошибка",
"finished": "Закончено",
"stopped": "Остановлено"
},
"components": {
"select_file": "Выберите файл или папку...",
"select_folder": "Выберите папку...",
"download": "Скачать",
"delete": "Удалить",
"install": "Установить"
},
"news": {
"latest_commits": "Последние коммиты",
"latest_version": "Последняя версия"
},
"help": {
"port_help_text": "Убедитесь, что это порт Dispatch-сервера, не порт игрового сервера. Обычно это '443'.",
"game_help_text": "Вам не нужно устанавливать еще одну копию, что бы играть с Grascutter. Это нужно или для версии 2.6, или если у Вас не установлена игра.",
"gc_stable_jar": "Скачать последнюю стабильную версию Grasscutter, которая содержит jar файл и данные.",
"gc_fullbuild": "Загрузите полную сборку Grasscutter, включая репо, jar и ресурсы. Полностью настроена и не требует других загрузок из этого меню.",
"gc_dev_jar": "Скачать последнюю версию для разработки Grasscutter, которая содержит jar файл и данные.",
"gc_stable_data": "Скачать стабильные данные Grasscutter, в которой нету jar файла. Это полезно для обновления.",
"gc_dev_data": "Скачать последнюю версию для разработки Grasscutter, в которой нету jar файла. Это полезно для обновления.",
"encryption": "Обычно это должно быть выключено.",
"resources": "Это необходимо для запуска сервера Grasscutter. Эта кнопка будет серой, если у Вас уже есть не пустая папка с ресурсами.",
"emergency_rsa": "Если что-то пошло не так, восстановит RSA до последней официальной версии.",
"use_proxy": "Использовать встроенный Прокси. Отключите если используете Fiddler или подобную программу",
"patch_rsa": "Патчит и восстанавливает RSA автоматически. Если вы не играете на старых/модифицированых версиях, или сами в ручную патчите Метаданные, эта опция должна быть включена.",
"add_delay": "Установите задержку в загрузчике 3dmigoto! \nЭто должно исправить проблемы с загрузкой, но добавит небольшую задержку в момент загрузки 3dmigoto при запуске игры. \nТеперь вы снова можете запускать игру с помощью 3dmigoto.",
"migoto": "Для импорта моделей из GameBanana"
},
"swag": {
"akebi_name": "Akebi",
"migoto_name": "Migoto",
"reshade_name": "Reshade",
"akebi": "Путь к исполняемому файлу Akebi",
"migoto": "Путь к исполняемому файлу 3DMigoto ",
"reshade": "Путь к инжектору Reshade"
}
}

93
src-tauri/lang/vi.json Normal file
View File

@@ -0,0 +1,93 @@
{
"lang_name": "Tiếng Việt",
"main": {
"title": "Cultivation",
"launch_button": "Khởi Chạy",
"gc_enable": "Kết nối qua Grasscutter",
"https_enable": "Dùng HTTPS",
"ip_placeholder": "Địa chỉ máy chủ...",
"port_placeholder": "Cổng...",
"files_downloading": "Đang tải tập tin: ",
"files_extracting": "Đang giải nén tập tin: "
},
"options": {
"enabled": "Bật",
"disabled": "Tắt",
"game_path": "Đường dẫn cài game",
"game_command": "Lệnh khởi chạy game",
"game_executable": "Tập tin thực thi game",
"recover_rsa": "Khôi phục RSA khẩn cấp",
"grasscutter_jar": "Tập tin JAR Grasscutter",
"toggle_encryption": "Bật/tắt mã hóa",
"install_certificate": "Cài chứng chỉ proxy",
"java_path": "Đường dẫn Java tùy chỉnh",
"grasscutter_with_game": "Tự động chạy Grasscutter cùng với game",
"language": "Ngôn ngữ",
"background": "Hình nền tùy chỉnh (liên kết hoặc tập tin hình ảnh)",
"theme": "Giao diện",
"patch_rsa": "Tự động vá RSA",
"use_proxy": "Sử dụng proxy nội bộ",
"wipe_login": "Tẩy sạch cache đăng nhập",
"horny_mode": "Chế độ hứng tình",
"auto_mongodb": "Tự động khởi động MongoDB"
},
"downloads": {
"grasscutter_fullbuild": "Tải Grasscutter tất cả trong một",
"grasscutter_stable_data": "Tải dữ liệu Grasscutter bản ổn định",
"grasscutter_latest_data": "Tải dữ liệu Grasscutter bản mới nhất",
"grasscutter_stable_data_update": "Cập nhật dữ liệu Grasscutter bản ổn định",
"grasscutter_latest_data_update": "Cập nhật dữ liệu Grasscutter bản mới nhất",
"grasscutter_stable": "Tải Grasscutter bản ổn định",
"grasscutter_latest": "Tải Grasscutter bản mới nhất",
"grasscutter_stable_update": "Cập nhật Grasscutter bản ổn định",
"grasscutter_latest_update": "Cập nhật Grasscutter bản mới nhất",
"resources": "Tải tài nguyên Grasscutter",
"game": "Tải game",
"aio_header": "Tải xuống tất cả trong một:",
"individual_header": "Tải xuống từng phần:",
"mods_header": "Mods:",
"migoto": "Tải GIMI 3dmigoto"
},
"download_status": {
"downloading": "Đang tải",
"extracting": "Đang giải nén",
"error": "Lỗi",
"finished": "Đã hoàn thành",
"stopped": "Đã dừng"
},
"components": {
"select_file": "Chọn tập tin hoặc thư mục...",
"select_folder": "Chọn thư mục...",
"download": "Tải",
"delete": "Xóa bỏ",
"install": "Cài"
},
"news": {
"latest_commits": "Thay Đổi Gần Đây",
"latest_version": "Phiên Bản Mới Nhất"
},
"help": {
"port_help_text": "Hãy đảm bảo đây là cổng của máy chủ Dispatch, không phải cổng của máy chủ game. Thường sẽ là '443'.",
"game_help_text": "Bạn không cần phải sử dụng một bản sao riêng để chơi với Grasscutter. Việc này chỉ xảy ra nếu bạn hạ phiên bản xuống 2.6 hoặc chưa cài game.",
"gc_stable_jar": "Tải xuống phiên bản ổn định của Grasscutter, bao gồm tập tin jar và các tệp dữ liệu.",
"gc_fullbuild": "Tải xuống bản dựng Grasscutter đầy đủ, bao gồm repo, jar và tài nguyên. Được thiết lập đầy đủ và không yêu cầu bất kỳ tải xuống nào khác từ menu này",
"gc_dev_jar": "Tải xuống phiên bản phát triển mới nhất của Grasscutter, bao gồm tập tin jar và các tệp dữ liệu.",
"gc_stable_data": "Tải xuống tệp dữ liệu phiên bản ổn định hiện hành của Grasscutter. Bản này không đi kèm với tập tin jar, hữu dụng khi muốn cập nhật.",
"gc_dev_data": "Tải xuống tệp dữ liệu phiên bản mới nhất của Grasscutter. Bản này không đi kèm với tập tin jar, hữu dụng khi muốn cập nhật.",
"encryption": "Mục này nên được tắt.",
"resources": "Tài nguyên được yêu cầu để chạy máy chủ Grasscutter. Nút này sẽ có màu xám nếu bạn đã có sẵn một thư mục tài nguyên (resources) có nội dung bên trong",
"emergency_rsa": "Trong trường hợp gặp vấn đề, khôi phục lại RSA về phiên bản chính thức mới nhất.",
"use_proxy": "Sử dụng proxy nội bộ của Cultivation. Nên bật tùy chọn này trừ khi bạn đang sử dụng một ứng dụng khác, như Fiddler",
"patch_rsa": "Tự động vá và sửa lại RSA của game. Tùy chọn này nên được bật trừ khi bạn đang sử dụng phiên bản cũ, phiên bản không chính thức hoặc bạn đã tự vá rsa rồi.",
"add_delay": "Đặt độ trễ trong trình tải 3dmigoto! \nĐiều này sẽ khắc phục sự cố tải, nhưng sẽ thêm một độ trễ nhỏ khi 3dmigoto được tải khi khởi chạy trò chơi. \nBây giờ bạn có thể khởi chạy lại với 3dmigoto.",
"migoto": "Để nhập mô hình từ GameBanana"
},
"swag": {
"akebi_name": "Akebi",
"migoto_name": "Migoto",
"reshade_name": "Reshade",
"akebi": "Tập tin thực thi Akebi",
"migoto": "Tập tin thực thi 3dMigoto",
"reshade": "Tập tin inject Reshade"
}
}

387
src-tauri/mhycrypto/aes.c Normal file
View File

@@ -0,0 +1,387 @@
// Simple, thoroughly commented implementation of 128-bit AES / Rijndael using C
// Chris Hulbert - chris.hulbert@gmail.com - http://splinter.com.au/blog
// References:
// http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
// http://en.wikipedia.org/wiki/Rijndael_key_schedule
// http://en.wikipedia.org/wiki/Rijndael_mix_columns
// http://en.wikipedia.org/wiki/Rijndael_S-box
// This code is public domain, or any OSI-approved license, your choice. No warranty.
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "aes.h"
typedef unsigned char byte;
// Here are all the lookup tables for the row shifts, rcon, s-boxes, and galois field multiplications
static const byte shift_rows_table[] = {0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11};
static const byte shift_rows_table_inv[] = {0, 13, 10, 7, 4, 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3};
static const byte lookup_rcon[] = {
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a};
static const byte lookup_sbox[] = {
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16};
static const byte lookup_sbox_inv[] = {
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d};
static const byte lookup_g2[] = {
0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e,
0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e,
0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e,
0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e,
0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe,
0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde,
0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe,
0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05,
0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25,
0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45,
0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65,
0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85,
0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5,
0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5,
0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5};
static const byte lookup_g3[] = {
0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11,
0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21,
0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71,
0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41,
0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1,
0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1,
0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1,
0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81,
0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a,
0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba,
0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea,
0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda,
0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a,
0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a,
0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a,
0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a};
static const byte lookup_g9[] = {
0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7,
0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,
0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc,
0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01,
0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91,
0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a,
0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa,
0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b,
0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b,
0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0,
0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30,
0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed,
0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d,
0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6,
0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46};
static const byte lookup_g11[] = {
0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69,
0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9,
0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12,
0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2,
0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f,
0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f,
0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4,
0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54,
0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e,
0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e,
0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5,
0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55,
0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68,
0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8,
0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13,
0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3};
static const byte lookup_g13[] = {
0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b,
0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b,
0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0,
0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20,
0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26,
0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6,
0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d,
0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d,
0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91,
0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41,
0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a,
0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa,
0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc,
0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c,
0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47,
0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97};
static const byte lookup_g14[] = {
0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a,
0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba,
0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81,
0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61,
0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7,
0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17,
0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c,
0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc,
0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b,
0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb,
0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0,
0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20,
0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6,
0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56,
0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d,
0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d};
// Xor's all elements in a n byte array a by b
static void xor_s(byte * a, const byte *b, int n) {
int i;
for (i = 0; i < n; i++) {
a[i] ^= b[i];
}
}
// Xor the current cipher state by a specific round key
static void xor_round_key(byte *state, const byte *keys, int round) {
xor_s(state, keys + round * 16, 16);
}
// Apply the rijndael s-box to all elements in an array
// http://en.wikipedia.org/wiki/Rijndael_S-box
static void sub_bytes(byte *a, int n) {
int i;
for (i = 0; i < n; i++) {
a[i] = lookup_sbox[a[i]];
}
}
static void sub_bytes_inv(byte *a, int n) {
int i;
for (i = 0; i < n; i++) {
a[i] = lookup_sbox_inv[a[i]];
}
}
// Perform the core key schedule transform on 4 bytes, as part of the key expansion process
// http://en.wikipedia.org/wiki/Rijndael_key_schedule#Key_schedule_core
static void key_schedule_core(byte *a, int i) {
byte temp = a[0]; // Rotate the output eight bits to the left
a[0] = a[1];
a[1] = a[2];
a[2] = a[3];
a[3] = temp;
sub_bytes(a, 4); // Apply Rijndael's S-box on all four individual bytes in the output word
a[0] ^= lookup_rcon[i]; // On just the first (leftmost) byte of the output word, perform the rcon operation with i
// as the input, and exclusive or the rcon output with the first byte of the output word
}
// Expand the 16-byte key to 11 round keys (176 bytes)
// http://en.wikipedia.org/wiki/Rijndael_key_schedule#The_key_schedule
void oqs_aes128_load_schedule_c(const uint8_t *key, void **_schedule) {
*_schedule = malloc(16 * 11);
assert(*_schedule != NULL);
uint8_t *schedule = (uint8_t *) *_schedule;
int bytes = 16; // The count of how many bytes we've created so far
int i = 1; // The rcon iteration value i is set to 1
int j; // For repeating the second stage 3 times
byte t[4]; // Temporary working area known as 't' in the Wiki article
memcpy(schedule, key, 16); // The first 16 bytes of the expanded key are simply the encryption key
while (bytes < 176) { // Until we have 176 bytes of expanded key, we do the following:
memcpy(t, schedule + bytes - 4, 4); // We assign the value of the previous four bytes in the expanded key to t
key_schedule_core(t, i); // We perform the key schedule core on t, with i as the rcon iteration value
i++; // We increment i by 1
xor_s(t, schedule + bytes - 16, 4); // We exclusive-or t with the four-byte block 16 bytes before the new expanded key.
memcpy(schedule + bytes, t, 4); // This becomes the next 4 bytes in the expanded key
bytes += 4; // Keep track of how many expanded key bytes we've added
// We then do the following three times to create the next twelve bytes
for (j = 0; j < 3; j++) {
memcpy(t, schedule + bytes - 4, 4); // We assign the value of the previous 4 bytes in the expanded key to t
xor_s(t, schedule + bytes - 16, 4); // We exclusive-or t with the four-byte block n bytes before
memcpy(schedule + bytes, t, 4); // This becomes the next 4 bytes in the expanded key
bytes += 4; // Keep track of how many expanded key bytes we've added
}
}
}
void oqs_aes128_free_schedule_c(void *schedule) {
if (schedule != NULL) {
free(schedule);
}
}
// Apply the shift rows step on the 16 byte cipher state
// http://en.wikipedia.org/wiki/Advanced_Encryption_Standard#The_ShiftRows_step
static void shift_rows(byte *state) {
int i;
byte temp[16];
memcpy(temp, state, 16);
for (i = 0; i < 16; i++) {
state[i] = temp[shift_rows_table[i]];
}
}
static void shift_rows_inv(byte *state) {
int i;
byte temp[16];
memcpy(temp, state, 16);
for (i = 0; i < 16; i++) {
state[i] = temp[shift_rows_table_inv[i]];
}
}
// Perform the mix columns matrix on one column of 4 bytes
// http://en.wikipedia.org/wiki/Rijndael_mix_columns
static void mix_col(byte *state) {
byte a0 = state[0];
byte a1 = state[1];
byte a2 = state[2];
byte a3 = state[3];
state[0] = lookup_g2[a0] ^ lookup_g3[a1] ^ a2 ^ a3;
state[1] = lookup_g2[a1] ^ lookup_g3[a2] ^ a3 ^ a0;
state[2] = lookup_g2[a2] ^ lookup_g3[a3] ^ a0 ^ a1;
state[3] = lookup_g2[a3] ^ lookup_g3[a0] ^ a1 ^ a2;
}
// Perform the mix columns matrix on each column of the 16 bytes
static void mix_cols(byte *state) {
mix_col(state);
mix_col(state + 4);
mix_col(state + 8);
mix_col(state + 12);
}
// Perform the inverse mix columns matrix on one column of 4 bytes
// http://en.wikipedia.org/wiki/Rijndael_mix_columns
static void mix_col_inv(byte *state) {
byte a0 = state[0];
byte a1 = state[1];
byte a2 = state[2];
byte a3 = state[3];
state[0] = lookup_g14[a0] ^ lookup_g9[a3] ^ lookup_g13[a2] ^ lookup_g11[a1];
state[1] = lookup_g14[a1] ^ lookup_g9[a0] ^ lookup_g13[a3] ^ lookup_g11[a2];
state[2] = lookup_g14[a2] ^ lookup_g9[a1] ^ lookup_g13[a0] ^ lookup_g11[a3];
state[3] = lookup_g14[a3] ^ lookup_g9[a2] ^ lookup_g13[a1] ^ lookup_g11[a0];
}
// Perform the inverse mix columns matrix on each column of the 16 bytes
static void mix_cols_inv(byte *state) {
mix_col_inv(state);
mix_col_inv(state + 4);
mix_col_inv(state + 8);
mix_col_inv(state + 12);
}
void oqs_aes128_enc_c(const uint8_t *plaintext, const void *_schedule, uint8_t *ciphertext) {
const uint8_t *schedule = (const uint8_t *) _schedule;
int i; // To count the rounds
// First Round
memcpy(ciphertext, plaintext, 16);
xor_round_key(ciphertext, schedule, 0);
// Middle rounds
for (i = 0; i < 9; i++) {
sub_bytes(ciphertext, 16);
shift_rows(ciphertext);
mix_cols(ciphertext);
xor_round_key(ciphertext, schedule, i + 1);
}
// Final Round
sub_bytes(ciphertext, 16);
shift_rows(ciphertext);
xor_round_key(ciphertext, schedule, 10);
}
void oqs_aes128_dec_c(const uint8_t *ciphertext, const void *_schedule, uint8_t *plaintext) {
const uint8_t *schedule = (const uint8_t *) _schedule;
int i; // To count the rounds
// Reverse the final Round
memcpy(plaintext, ciphertext, 16);
xor_round_key(plaintext, schedule, 10);
shift_rows_inv(plaintext);
sub_bytes_inv(plaintext, 16);
// Reverse the middle rounds
for (i = 0; i < 9; i++) {
xor_round_key(plaintext, schedule, 9 - i);
mix_cols_inv(plaintext);
shift_rows_inv(plaintext);
sub_bytes_inv(plaintext, 16);
}
// Reverse the first Round
xor_round_key(plaintext, schedule, 0);
}
// It's not enc nor dec, it's something in between
void oqs_mhy128_enc_c(const uint8_t *plaintext, const void *_schedule, uint8_t *ciphertext) {
const uint8_t *schedule = (const uint8_t *) _schedule;
int i; // To count the rounds
// First Round
memcpy(ciphertext, plaintext, 16);
xor_round_key(ciphertext, schedule, 0);
// Middle rounds
for (i = 0; i < 9; i++) {
sub_bytes_inv(ciphertext, 16);
shift_rows_inv(ciphertext);
mix_cols_inv(ciphertext);
xor_round_key(ciphertext, schedule, i + 1);
}
// Final Round
sub_bytes_inv(ciphertext, 16);
shift_rows_inv(ciphertext);
xor_round_key(ciphertext, schedule, 10);
}
void oqs_mhy128_dec_c(const uint8_t *ciphertext, const void *_schedule, uint8_t *plaintext) {
const uint8_t *schedule = (const uint8_t *) _schedule;
int i; // To count the rounds
// Reverse the final Round
memcpy(plaintext, ciphertext, 16);
xor_round_key(plaintext, schedule, 10);
shift_rows(plaintext);
sub_bytes(plaintext, 16);
// Reverse the middle rounds
for (i = 0; i < 9; i++) {
xor_round_key(plaintext, schedule, 9 - i);
mix_cols(plaintext);
shift_rows(plaintext);
sub_bytes(plaintext, 16);
}
// Reverse the first Round
xor_round_key(plaintext, schedule, 0);
}

66
src-tauri/mhycrypto/aes.h Normal file
View File

@@ -0,0 +1,66 @@
/**
* \file aes.h
* \brief Header defining the API for OQS AES
*/
#ifndef __OQS_AES_H
#define __OQS_AES_H
#include <stdint.h>
#include <stdlib.h>
/**
* Function to fill a key schedule given an initial key.
*
* @param key Initial Key.
* @param schedule Abstract data structure for a key schedule.
* @param forEncryption 1 if key schedule is for encryption, 0 if for decryption.
*/
void OQS_AES128_load_schedule(const uint8_t *key, void **schedule, int for_encryption);
/**
* Function to free a key schedule.
*
* @param schedule Schedule generated with OQS_AES128_load_schedule().
*/
void OQS_AES128_free_schedule(void *schedule);
/**
* Function to encrypt blocks of plaintext using ECB mode.
* A schedule based on the key is generated and used internally.
*
* @param plaintext Plaintext to be encrypted.
* @param plaintext_len Length on the plaintext in bytes. Must be a multiple of 16.
* @param key Key to be used for encryption.
* @param ciphertext Pointer to a block of memory which >= in size to the plaintext block. The result will be written here.
*/
void OQS_AES128_ECB_enc(const uint8_t *plaintext, const size_t plaintext_len, const uint8_t *key, uint8_t *ciphertext);
/**
* Function to decrypt blocks of plaintext using ECB mode.
* A schedule based on the key is generated and used internally.
*
* @param ciphertext Ciphertext to be decrypted.
* @param ciphertext_len Length on the ciphertext in bytes. Must be a multiple of 16.
* @param key Key to be used for encryption.
* @param ciphertext Pointer to a block of memory which >= in size to the ciphertext block. The result will be written here.
*/
void OQS_AES128_ECB_dec(const uint8_t *ciphertext, const size_t ciphertext_len, const uint8_t *key, uint8_t *plaintext);
/**
* Same as OQS_AES128_ECB_enc() except a schedule generated by
* OQS_AES128_load_schedule() is passed rather then a key. This is faster
* if the same schedule is used for multiple encryptions since it does
* not have to be regenerated from the key.
*/
void OQS_AES128_ECB_enc_sch(const uint8_t *plaintext, const size_t plaintext_len, const void *schedule, uint8_t *ciphertext);
/**
* Same as OQS_AES128_ECB_dec() except a schedule generated by
* OQS_AES128_load_schedule() is passed rather then a key. This is faster
* if the same schedule is used for multiple encryptions since it does
* not have to be regenerated from the key.
*/
void OQS_AES128_ECB_dec_sch(const uint8_t *ciphertext, const size_t ciphertext_len, const void *schedule, uint8_t *plaintext);
#endif

View File

@@ -0,0 +1,31 @@
#include "memecrypto.h"
#include <cstring>
#include <stdio.h>
extern "C" void oqs_mhy128_enc_c(const uint8_t *plaintext, const void *_schedule, uint8_t *ciphertext);
extern "C" void oqs_mhy128_dec_c(const uint8_t *ciphertext, const void *_schedule, uint8_t *plaintext);
static uint8_t dexor16(const uint8_t *c) {
uint8_t ret = 0;
for (int i = 0; i < 16; i++)
ret ^= c[i];
return ret;
}
void memecrypto_prepare_key(const uint8_t *in, uint8_t *out) {
for (int i = 0; i < 0xB0; i++)
out[i] = dexor16(&in[0x10 * i]);
}
void memecrypto_decrypt(const uint8_t *key, uint8_t *data) {
uint8_t plaintext[16];
oqs_mhy128_enc_c(data, key, plaintext);
memcpy(data, plaintext, 16);
}
void memecrypto_encrypt(const uint8_t *key, uint8_t *data) {
uint8_t ciphertext[16];
oqs_mhy128_dec_c(data, key, ciphertext);
memcpy(data, ciphertext, 16);
}

View File

@@ -0,0 +1,12 @@
#ifndef MEMECRYPTO_H
#define MEMECRYPTO_H
#include <cstdint>
void memecrypto_prepare_key(const uint8_t *in, uint8_t *out);
void memecrypto_decrypt(const uint8_t *key, uint8_t *data);
void memecrypto_encrypt(const uint8_t *key, uint8_t *data);
#endif //MEMECRYPTO_H

View File

@@ -0,0 +1,146 @@
#include "metadata.h"
#include <cstring>
#include <random>
#include <stdio.h>
#include "memecrypto.h"
#include "metadatastringdec.h"
unsigned char initial_prev_xor[] = { 0xad, 0x2f, 0x42, 0x30, 0x67, 0x04, 0xb0, 0x9c, 0x9d, 0x2a, 0xc0, 0xba, 0x0e, 0xbf, 0xa5, 0x68 };
bool get_global_metadata_keys(uint8_t *src, size_t srcn, uint8_t *longkey, uint8_t *shortkey) {
if (srcn != 0x4000)
return false;
if (*(uint16_t *) (src + 0xc8) != 0xfc2e || *(uint16_t *) (src + 0xca) != 0x2cfe)
return true;
auto offB00 = *(uint16_t *) (src + 0xd2);
for (size_t i = 0; i < 16; i++)
shortkey[i] = src[offB00 + i] ^ src[0x3000 + i];
for (size_t i = 0; i < 0xb00; i++)
longkey[i] = src[offB00 + 0x10 + i] ^ src[0x3000 + 0x10 + i] ^ shortkey[i % 16];
return true;
}
bool gen_global_metadata_key(uint8_t* src, size_t srcn) {
if (srcn != 0x4000)
return false;
#if 0
std::vector<uint8_t> read_file(const char* n);
auto data = read_file("xorpad.bin");
memcpy(src, data.data(), 0x4000);
return false;
#endif
std::mt19937_64 rand (0xDEADBEEF);
uint64_t* key = (uint64_t*)src;
for (size_t i = 0; i < srcn / sizeof(uint64_t); i++)
key[i] = rand();
*(uint16_t *) (src + 0xc8) = 0xfc2e; // Magic
*(uint16_t *) (src + 0xca) = 0x2cfe; // Magic
*(uint16_t *) (src + 0xd2) = rand() & 0x1FFFu; // Just some random value
return true;
}
void decrypt_global_metadata_inner(uint8_t *data, size_t size) {
uint8_t longkey[0xB00];
uint8_t longkeyp[0xB0];
uint8_t shortkey[16];
get_global_metadata_keys(data + size - 0x4000, 0x4000, longkey, shortkey);
for (int i = 0; i < 16; i++)
shortkey[i] ^= initial_prev_xor[i];
memecrypto_prepare_key(longkey, longkeyp);
auto perentry = (uint32_t) (size / 0x100 / 0x40);
for (int i = 0; i < 0x100; i++) {
auto off = (0x40u * perentry) * i;
uint8_t prev[16];
memcpy(prev, shortkey, 16);
for (int j = 0; j < 4; j++) {
uint8_t curr[16];
memcpy(curr, &data[off + j * 0x10], 16);
memecrypto_decrypt(longkeyp, curr);
for (int k = 0; k < 16; k++)
curr[k] ^= prev[k];
memcpy(prev, &data[off + j * 0x10], 16);
memcpy(&data[off + j * 0x10], curr, 16);
}
}
uint8_t literal_dec_key[0x5000];
recrypt_global_metadata_header_string_fields(data, size, literal_dec_key);
recrypt_global_metadata_header_string_literals(data, size, literal_dec_key);
}
extern "C" int decrypt_global_metadata(uint8_t *data, size_t size) {
try {
decrypt_global_metadata_inner(data, size);
return 0;
} catch (...) {
return -1;
}
}
void encrypt_global_metadata_inner(uint8_t* data, size_t size) {
uint8_t literal_dec_key[0x5000];
gen_global_metadata_key(data + size - 0x4000, 0x4000);
generate_key_for_global_metadata_header_string(data, size, literal_dec_key);
recrypt_global_metadata_header_string_literals(data, size, literal_dec_key);
recrypt_global_metadata_header_string_fields(data, size, literal_dec_key);
uint8_t longkey[0xB00];
uint8_t longkeyp[0xB0];
uint8_t shortkey[16];
get_global_metadata_keys(data + size - 0x4000, 0x4000, longkey, shortkey);
for (int i = 0; i < 16; i++)
shortkey[i] ^= initial_prev_xor[i];
memecrypto_prepare_key(longkey, longkeyp);
auto perentry = (uint32_t) (size / 0x100 / 0x40);
for (int i = 0; i < 0x100; i++) {
auto off = (0x40u * perentry) * i;
uint8_t prev[16];
memcpy(prev, shortkey, 16);
for (int j = 0; j < 4; j++) {
uint8_t curr[16];
memcpy(curr, &data[off + j * 0x10], 16);
for (int k = 0; k < 16; k++)
curr[k] ^= prev[k];
memecrypto_encrypt(longkeyp, curr);
memcpy(prev, curr, 16);
memcpy(&data[off + j * 0x10], curr, 16);
}
}
}
extern "C" int encrypt_global_metadata(uint8_t* data, size_t size) {
try {
encrypt_global_metadata_inner(data, size);
return 0;
} catch (...) {
return -1;
}
}

View File

@@ -0,0 +1,10 @@
#ifndef METADATA_H
#define METADATA_H
#include <cstdint>
#include <cstdlib>
extern "C" int decrypt_global_metadata(uint8_t *data, size_t size);
extern "C" int encrypt_global_metadata(uint8_t *data, size_t size);
#endif //METADATA_H

View File

@@ -0,0 +1,121 @@
#include "metadatastringdec.h"
#include <stdexcept>
#include <random>
#include <stdio.h>
struct m_header_fields {
char filler1[0x18];
uint32_t stringLiteralDataOffset; // 18
uint32_t stringLiteralDataCount; // 1c
uint32_t stringLiteralOffset; // 20
uint32_t stringLiteralCount; // 24
char filler2[0xd8 - 0x28];
uint32_t stringOffset, stringCount;
};
struct m_literal {
uint32_t offset, length;
};
void generate_key_for_global_metadata_header_string(uint8_t* data, size_t len, uint8_t* literal_dec_key) {
if (len < sizeof(m_header_fields))
throw std::out_of_range("data not big enough for global metadata header");
uint32_t values[0x12] = {
*(uint32_t *) (data + 0x60),
*(uint32_t *) (data + 0x64),
*(uint32_t *) (data + 0x68),
*(uint32_t *) (data + 0x6c),
*(uint32_t *) (data + 0x140),
*(uint32_t *) (data + 0x144),
*(uint32_t *) (data + 0x148),
*(uint32_t *) (data + 0x14c),
*(uint32_t *) (data + 0x100),
*(uint32_t *) (data + 0x104),
*(uint32_t *) (data + 0x108),
*(uint32_t *) (data + 0x10c),
*(uint32_t *) (data + 0xf0),
*(uint32_t *) (data + 0xf4),
*(uint32_t *) (data + 8),
*(uint32_t *) (data + 0xc),
*(uint32_t *) (data + 0x10),
*(uint32_t *) (data + 0x14)
};
uint64_t seed = ((uint64_t) values[values[0] & 0xfu] << 0x20u) | values[(values[0x11] & 0xf) + 2];
std::mt19937_64 rand (seed);
for (int i = 0; i < 6; i++) // Skip
rand();
auto key64 = (uint64_t *) literal_dec_key;
for (int i = 0; i < 0xa00; i++)
key64[i] = rand();
}
void recrypt_global_metadata_header_string_fields(uint8_t *data, size_t len, uint8_t *literal_dec_key) {
if (len < sizeof(m_header_fields))
throw std::out_of_range("data not big enough for global metadata header");
uint32_t values[0x12] = {
*(uint32_t *) (data + 0x60),
*(uint32_t *) (data + 0x64),
*(uint32_t *) (data + 0x68),
*(uint32_t *) (data + 0x6c),
*(uint32_t *) (data + 0x140),
*(uint32_t *) (data + 0x144),
*(uint32_t *) (data + 0x148),
*(uint32_t *) (data + 0x14c),
*(uint32_t *) (data + 0x100),
*(uint32_t *) (data + 0x104),
*(uint32_t *) (data + 0x108),
*(uint32_t *) (data + 0x10c),
*(uint32_t *) (data + 0xf0),
*(uint32_t *) (data + 0xf4),
*(uint32_t *) (data + 8),
*(uint32_t *) (data + 0xc),
*(uint32_t *) (data + 0x10),
*(uint32_t *) (data + 0x14)
};
uint64_t seed = ((uint64_t) values[values[0] & 0xfu] << 0x20u) | values[(values[0x11] & 0xf) + 2];
std::mt19937_64 rand (seed);
auto header = (m_header_fields *) data;
header->stringCount ^= (uint32_t) rand();
header->stringOffset ^= (uint32_t) rand();
rand();
header->stringLiteralOffset ^= (uint32_t) rand();
header->stringLiteralDataCount ^= (uint32_t) rand();
header->stringLiteralDataOffset ^= (uint32_t) rand();
auto key64 = (uint64_t *) literal_dec_key;
for (int i = 0; i < 0xa00; i++)
key64[i] = rand();
}
void recrypt_global_metadata_header_string_literals(uint8_t *data, size_t len, uint8_t *literal_dec_key) {
if (len < sizeof(m_header_fields))
throw std::out_of_range("data not big enough for global metadata header");
auto header = (m_header_fields *) data;
if ((size_t) header->stringLiteralCount + header->stringLiteralOffset > len)
throw std::out_of_range("file trimmed or string literal offset/count field invalid");
auto literals = (m_literal *) (data + header->stringLiteralOffset);
auto count = header->stringLiteralCount / sizeof(m_literal);
for (size_t i = 0; i < count; i++) {
auto slen = literals[i].length;
uint8_t *str = data + header->stringLiteralDataOffset + literals[i].offset;
uint8_t *okey = literal_dec_key + (i % 0x2800);
if ((size_t) header->stringLiteralDataOffset + literals[i].offset + slen > len)
throw std::out_of_range("file trimmed or contains invalid string entry");
for (size_t j = 0; j < slen; j++)
str[j] ^= literal_dec_key[(j + 0x1400u) % 0x5000u] ^ (okey[j % 0x2800u] + (uint8_t) j);
}
}

View File

@@ -0,0 +1,13 @@
#ifndef METADATASTRINGDEC_H
#define METADATASTRINGDEC_H
#include <cstdint>
#include <cstdlib>
void recrypt_global_metadata_header_string_fields(uint8_t *data, size_t len, uint8_t *literal_dec_key);
void recrypt_global_metadata_header_string_literals(uint8_t *data, size_t len, uint8_t *literal_dec_key);
void generate_key_for_global_metadata_header_string(uint8_t* data, size_t len, uint8_t* literal_dec_key);
#endif //METADATASTRINGDEC_H

BIN
src-tauri/patch/version.dll Normal file

Binary file not shown.

4
src-tauri/rustfmt.toml Normal file
View File

@@ -0,0 +1,4 @@
newline_style = "Unix"
tab_spaces = 2
use_field_init_shorthand = true
use_try_shorthand = true

22
src-tauri/src/admin.rs Normal file
View File

@@ -0,0 +1,22 @@
#[cfg(windows)]
pub fn reopen_as_admin() {
use std::process::{exit, Command};
let install = std::env::current_exe().unwrap();
println!("Opening as admin: {}", install.to_str().unwrap());
Command::new("powershell.exe")
.arg("powershell")
.arg(format!(
"-command \"&{{Start-Process -filepath '{}' -verb runas}}\"",
install.to_str().unwrap()
))
.spawn()
.expect("Error starting exec as admin");
exit(0);
}
#[cfg(target_os = "linux")]
pub fn reopen_as_admin() {}

View File

@@ -1,36 +1,28 @@
use lazy_static::lazy_static;
use once_cell::sync::Lazy;
use std::sync::Mutex;
use std::cmp::min;
use std::fs::File;
use std::io::Write;
use std::sync::Mutex;
use futures_util::StreamExt;
// This will create a downloads list that will be used to check if we should continue downloading the file
lazy_static! {
static ref DOWNLOADS: Mutex<Vec<String>> = {
let m = Vec::new();
Mutex::new(m)
};
}
static DOWNLOADS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
// Lots of help from: https://gist.github.com/giuliano-oliveira/4d11d6b3bb003dba3a1b53f43d81b30d
// and docs ofc
#[tauri::command]
pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Result<(), String> {
// Reqwest setup
let res = match reqwest::get(url)
.await {
let res = match reqwest::get(url).await {
Ok(r) => r,
Err(_e) => {
emit_download_err(window, format!("Failed to request {}", url), path);
return Err(format!("Failed to request {}", url));
}
};
let total_size = res
.content_length()
.unwrap_or(0);
let total_size = res.content_length().unwrap_or(0);
// Create file path
let mut file = match File::create(path) {
@@ -59,17 +51,17 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu
let chunk = match item {
Ok(itm) => itm,
Err(e) => {
emit_download_err(window, format!("Error while downloading file"), path);
emit_download_err(window, "Error while downloading file".to_string(), path);
return Err(format!("Error while downloading file: {}", e));
}
};
let vect = &chunk.to_vec()[..];
// Write bytes
match file.write_all(&vect) {
match file.write_all(vect) {
Ok(x) => x,
Err(e) => {
emit_download_err(window, format!("Error while writing file"), path);
emit_download_err(window, "Error while writing file".to_string(), path);
return Err(format!("Error while writing file: {}", e));
}
}
@@ -78,29 +70,14 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu
let new = min(downloaded + (chunk.len() as u64), total_size);
downloaded = new;
total_downloaded = total_downloaded + chunk.len() as u64;
total_downloaded += chunk.len() as u64;
let mut res_hash = std::collections::HashMap::new();
res_hash.insert(
"downloaded".to_string(),
downloaded.to_string(),
);
res_hash.insert(
"total".to_string(),
total_size.to_string(),
);
res_hash.insert(
"path".to_string(),
path.to_string(),
);
res_hash.insert(
"total_downloaded".to_string(),
total_downloaded.to_string(),
);
res_hash.insert("downloaded".to_string(), downloaded.to_string());
res_hash.insert("total".to_string(), total_size.to_string());
res_hash.insert("path".to_string(), path.to_string());
res_hash.insert("total_downloaded".to_string(), total_downloaded.to_string());
// Create event to send to frontend
window.emit("download_progress", &res_hash).unwrap();
@@ -110,21 +87,14 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu
window.emit("download_finished", &path).unwrap();
// We are done
return Ok(());
Ok(())
}
pub fn emit_download_err(window: tauri::Window, msg: std::string::String, path: &str) {
pub fn emit_download_err(window: tauri::Window, msg: String, path: &str) {
let mut res_hash = std::collections::HashMap::new();
res_hash.insert(
"error".to_string(),
msg.to_string(),
);
res_hash.insert(
"path".to_string(),
path.to_string(),
);
res_hash.insert("error".to_string(), msg);
res_hash.insert("path".to_string(), path.to_string());
window.emit("download_error", &res_hash).unwrap();
}
@@ -144,4 +114,4 @@ pub fn stop_download(path: String) {
if let Err(_e) = std::fs::remove_file(&path) {
// Do nothing
}
}
}

View File

@@ -1,35 +1,170 @@
use file_diff::diff;
use std::fs;
use std::io::{Read, Write};
use std::path::PathBuf;
#[tauri::command]
pub fn rename(path: String, new_name: String) {
let mut new_path = path.clone();
// Check if file/folder to replace exists
if !fs::metadata(&path).is_ok() {
if fs::metadata(&path).is_err() {
return;
}
// Check if path uses forward or back slashes
if new_path.contains("\\") {
new_path = path.replace("\\", "/");
if new_path.contains('\\') {
new_path = path.replace('\\', "/");
}
let path_replaced = &path.replace(&new_path.split("/").last().unwrap(), &new_name);
let path_replaced = &path.replace(new_path.split('/').last().unwrap(), &new_name);
fs::rename(path, &path_replaced).unwrap();
match fs::rename(&path, path_replaced) {
Ok(_) => {
println!("Renamed {} to {}", &path, path_replaced);
}
Err(e) => {
println!("Error: {}", e);
}
};
}
#[tauri::command]
pub fn dir_create(path: String) {
fs::create_dir_all(path).unwrap();
}
#[tauri::command]
pub fn dir_exists(path: &str) -> bool {
return fs::metadata(&path).is_ok();
let path_buf = PathBuf::from(path);
fs::metadata(path_buf).is_ok()
}
#[tauri::command]
pub fn dir_is_empty(path: &str) -> bool {
return fs::read_dir(&path).unwrap().count() == 0;
let path_buf = PathBuf::from(path);
fs::read_dir(path_buf).unwrap().count() == 0
}
#[tauri::command]
pub fn dir_delete(path: &str) {
fs::remove_dir_all(path).unwrap();
}
let path_buf = PathBuf::from(path);
fs::remove_dir_all(path_buf).unwrap();
}
#[tauri::command]
pub fn are_files_identical(path1: &str, path2: &str) -> bool {
diff(path1, path2)
}
#[tauri::command]
pub fn copy_file(path: String, new_path: String) -> bool {
let filename = &path.split('/').last().unwrap();
let path_buf = PathBuf::from(&path);
// If the new path doesn't exist, create it.
if !dir_exists(PathBuf::from(&new_path).pop().to_string().as_str()) {
std::fs::create_dir_all(&new_path).unwrap();
}
// Copy old to new
match std::fs::copy(path_buf, format!("{}/{}", new_path, filename)) {
Ok(_) => true,
Err(e) => {
println!("Failed to copy file: {}", e);
println!("Path: {}", path);
println!("New Path: {}", new_path);
false
}
}
}
#[tauri::command]
pub fn copy_file_with_new_name(path: String, new_path: String, new_name: String) -> bool {
let mut new_path_buf = PathBuf::from(&new_path);
let path_buf = PathBuf::from(&path);
// If the new path doesn't exist, create it.
if !dir_exists(PathBuf::from(&new_path).pop().to_string().as_str()) {
match std::fs::create_dir_all(&new_path) {
Ok(_) => {}
Err(e) => {
println!("Failed to create directory: {}", e);
return false;
}
};
}
new_path_buf.push(new_name);
// Copy old to new
match std::fs::copy(path_buf, &new_path_buf) {
Ok(_) => true,
Err(e) => {
println!("Failed to copy file: {}", e);
println!("Path: {}", path);
println!("New Path: {}", new_path);
false
}
}
}
#[tauri::command]
pub fn delete_file(path: String) -> bool {
let path_buf = PathBuf::from(&path);
match std::fs::remove_file(path_buf) {
Ok(_) => true,
Err(e) => {
println!("Failed to delete file: {}", e);
false
}
};
false
}
#[tauri::command]
pub fn read_file(path: String) -> String {
let path_buf = PathBuf::from(&path);
let mut file = match fs::File::open(path_buf) {
Ok(file) => file,
Err(e) => {
if path.contains("config") {
// Server.ts won't print the error so handle the message here for the user
println!("Server config not found or invalid. Be sure to run the server at least once to generate it before making edits.");
} else {
println!("Failed to open file: {}", e);
}
return String::new(); // Send back error for handling by the caller
}
};
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
contents
}
#[tauri::command]
pub fn write_file(path: String, contents: String) {
let path_buf = PathBuf::from(&path);
// Create file if it exists, otherwise just open and rewrite
let mut file = match fs::File::create(path_buf) {
Ok(file) => file,
Err(e) => {
println!("Failed to open file: {}", e);
return;
}
};
// Write contents to file
match file.write_all(contents.as_bytes()) {
Ok(_) => (),
Err(e) => {
println!("Failed to write to file: {}", e);
}
}
}

View File

@@ -0,0 +1,84 @@
use crate::file_helpers;
use crate::web;
use std::collections::HashMap;
use std::fs::read_dir;
use std::io::Read;
use std::path::PathBuf;
static SITE_URL: &str = "https://gamebanana.com";
#[tauri::command]
pub async fn get_download_links(mod_id: String) -> String {
web::query(format!("{}/apiv9/Mod/{}/DownloadPage", SITE_URL, mod_id).as_str()).await
}
#[tauri::command]
pub async fn list_submissions(mode: String, page: String) -> String {
web::query(
format!(
"{}/apiv9/Util/Game/Submissions?_idGameRow=8552&_nPage={}&_nPerpage=50&_sMode={}",
SITE_URL, page, mode
)
.as_str(),
)
.await
}
#[tauri::command]
pub async fn list_mods(path: String) -> HashMap<String, String> {
let mut path_buf = PathBuf::from(path);
// If the path includes a file, remove it
if path_buf.file_name().is_some() {
path_buf.pop();
}
// Ensure we are in the Mods folder
path_buf.push("Mods");
// Check if dir is empty
if file_helpers::dir_is_empty(path_buf.to_str().unwrap()) {
return HashMap::new();
}
let mut mod_info_files = vec![];
let mut mod_info_strings = HashMap::new();
for entry in read_dir(path_buf).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
// Check each dir for a modinfo.json file
if path.is_dir() {
let mut mod_info_path = path.clone();
mod_info_path.push("modinfo.json");
if mod_info_path.exists() {
// Push path AND file contents into the hashmap using path as key
mod_info_files.push(mod_info_path.to_str().unwrap().to_string());
} else {
// No modinfo, but we can still push a JSON obj with the folder name
mod_info_strings.insert(
path.to_str().unwrap().to_string(),
format!(
"{{ \"name\": \"{}\" }}",
path.file_name().unwrap().to_str().unwrap()
),
);
}
}
}
// Read each modinfo.json file
for mod_info_file in mod_info_files {
let mut mod_info_string = String::new();
// It is safe to unwrap here since we *know* that the file exists
let mut file = std::fs::File::open(&mod_info_file).unwrap();
file.read_to_string(&mut mod_info_string).unwrap();
// Push into hashmap using path as key
mod_info_strings.insert(mod_info_file, mod_info_string);
}
mod_info_strings
}

View File

@@ -1,19 +1,21 @@
use crate::system_helpers::*;
use std::path::{ Path, PathBuf };
#[tauri::command]
pub async fn get_lang(window: tauri::Window, lang: String) -> String {
let lang = lang.to_lowercase();
// Send contents of language file back
let contents = match std::fs::read_to_string(format!("{}/lang/{}.json", install_location(), lang)) {
let lang_path: PathBuf = [&install_location(), "lang", &format!("{}.json", lang)]
.iter()
.collect();
match std::fs::read_to_string(lang_path) {
Ok(x) => x,
Err(e) => {
emit_lang_err(window, format!("Failed to read language file: {}", e));
return "".to_string();
"".to_string()
}
};
return contents;
}
}
#[tauri::command]
@@ -21,14 +23,19 @@ pub async fn get_languages() -> std::collections::HashMap<String, String> {
// for each lang file, set the key as the filename and the value as the lang_name contained in the file
let mut languages = std::collections::HashMap::new();
let mut lang_files = std::fs::read_dir(format!("{}/lang", install_location())).unwrap();
let lang_files = std::fs::read_dir(Path::new(&install_location()).join("lang")).unwrap();
while let Some(entry) = lang_files.next() {
for entry in lang_files {
let entry = entry.unwrap();
let path = entry.path();
let filename = path.file_name().unwrap().to_str().unwrap();
let filename = path
.file_name()
.unwrap_or_else(|| panic!("Failed to get filename from path: {:?}", path))
.to_str()
.unwrap_or_else(|| panic!("Failed to convert filename to string: {:?}", path))
.to_string();
let content = match std::fs::read_to_string(path.to_str().unwrap()) {
let content = match std::fs::read_to_string(&path) {
Ok(x) => x,
Err(e) => {
println!("Failed to read language file: {}", e);
@@ -39,16 +46,13 @@ pub async fn get_languages() -> std::collections::HashMap<String, String> {
languages.insert(filename.to_string(), content);
}
return languages;
languages
}
pub fn emit_lang_err(window: tauri::Window, msg: std::string::String) {
pub fn emit_lang_err(window: tauri::Window, msg: String) {
let mut res_hash = std::collections::HashMap::new();
res_hash.insert(
"error".to_string(),
msg.to_string(),
);
res_hash.insert("error".to_string(), msg);
window.emit("lang_error", &res_hash).unwrap();
}

View File

@@ -1,109 +1,144 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use lazy_static::lazy_static;
use std::{sync::Mutex, collections::HashMap};
use file_helpers::dir_exists;
use once_cell::sync::Lazy;
use std::fs;
use std::io::Write;
use std::{collections::HashMap, sync::Mutex};
use system_helpers::is_elevated;
use tauri::api::path::data_dir;
use tauri::async_runtime::block_on;
use std::thread;
use sysinfo::{System, SystemExt};
use structs::{APIQuery};
use sysinfo::{Pid, ProcessExt, System, SystemExt};
mod structs;
mod system_helpers;
mod file_helpers;
mod unzip;
use crate::admin::reopen_as_admin;
mod admin;
mod downloader;
mod file_helpers;
mod gamebanana;
mod lang;
mod proxy;
mod system_helpers;
mod unzip;
mod web;
lazy_static! {
static ref WATCH_GAME_PROCESS: Mutex<String> = {
let m = "".to_string();
Mutex::new(m)
};
static WATCH_GAME_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
static WATCH_GRASSCUTTER_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
static GC_PID: std::sync::Mutex<usize> = Mutex::new(696969);
fn try_flush() {
std::io::stdout().flush().unwrap_or(())
}
fn has_arg(args: &[String], arg: &str) -> bool {
args.contains(&arg.to_string())
}
async fn arg_handler(args: &[String]) {
if has_arg(args, "--proxy") {
let mut pathbuf = tauri::api::path::data_dir().unwrap();
pathbuf.push("cultivation");
pathbuf.push("ca");
connect(8035, pathbuf.to_str().unwrap().to_string()).await;
}
}
fn main() {
// Start the game process watcher.
process_watcher();
let args: Vec<String> = std::env::args().collect();
// Make BG folder if it doesn't exist.
let bg_folder = format!("{}/bg", system_helpers::install_location());
if !std::path::Path::new(&bg_folder).exists() {
std::fs::create_dir_all(&bg_folder).unwrap();
if !is_elevated() && !has_arg(&args, "--no-admin") {
println!("===============================================================================");
println!("You running as a non-elevated user. Some stuff will almost definitely not work.");
println!("===============================================================================");
reopen_as_admin();
}
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
enable_process_watcher,
connect,
disconnect,
req_get,
get_bg_file,
base64_decode,
is_game_running,
get_theme_list,
system_helpers::run_command,
system_helpers::run_program,
system_helpers::run_jar,
system_helpers::open_in_browser,
system_helpers::copy_file,
system_helpers::install_location,
system_helpers::is_elevated,
proxy::set_proxy_addr,
proxy::generate_ca_files,
unzip::unzip,
file_helpers::rename,
file_helpers::dir_exists,
file_helpers::dir_is_empty,
file_helpers::dir_delete,
downloader::download_file,
downloader::stop_download,
lang::get_lang,
lang::get_languages,
web::valid_url
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
// 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();
}
fn process_watcher() {
// Every 5 seconds, see if the game process is still running.
// If it is not, then we assume the game has closed and disable the proxy
// to prevent any requests from being sent to the game.
// 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();
// Start a thread so as to not block the main thread.
thread::spawn(|| {
let mut system = System::new_all();
block_on(arg_handler(&args));
loop {
// Refresh system info
system.refresh_all();
// For disabled GUI
ctrlc::set_handler(|| {
disconnect();
std::process::exit(0);
})
.unwrap_or(());
// Grab the game process name
let proc = WATCH_GAME_PROCESS.lock().unwrap().to_string();
if !has_arg(&args, "--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_relative,
system_helpers::start_service,
system_helpers::service_status,
system_helpers::stop_service,
system_helpers::run_jar,
system_helpers::open_in_browser,
system_helpers::install_location,
system_helpers::is_elevated,
system_helpers::set_migoto_target,
system_helpers::set_migoto_delay,
system_helpers::wipe_registry,
system_helpers::get_platform,
proxy::set_proxy_addr,
proxy::generate_ca_files,
unzip::unzip,
file_helpers::rename,
file_helpers::dir_create,
file_helpers::dir_exists,
file_helpers::dir_is_empty,
file_helpers::dir_delete,
file_helpers::copy_file,
file_helpers::copy_file_with_new_name,
file_helpers::delete_file,
file_helpers::are_files_identical,
file_helpers::read_file,
file_helpers::write_file,
downloader::download_file,
downloader::stop_download,
lang::get_lang,
lang::get_languages,
web::valid_url,
web::web_get,
gamebanana::get_download_links,
gamebanana::list_submissions,
gamebanana::list_mods
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
} else {
try_flush();
println!("Press enter or CTRL-C twice to quit...");
std::io::stdin().read_line(&mut String::new()).unwrap();
}
if !&proc.is_empty() {
let proc_with_name = system.processes_by_exact_name(&proc);
let mut exists = false;
for _p in proc_with_name {
exists = true;
break;
}
// If the game process closes, disable the proxy.
if !exists {
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
disconnect();
}
}
thread::sleep(std::time::Duration::from_secs(5));
}
});
// Always disconnect upon closing the program
disconnect();
}
#[tauri::command]
@@ -111,12 +146,150 @@ fn is_game_running() -> bool {
// Grab the game process name
let proc = WATCH_GAME_PROCESS.lock().unwrap().to_string();
return !proc.is_empty();
!proc.is_empty()
}
#[tauri::command]
fn enable_process_watcher(process: String) {
fn enable_process_watcher(window: tauri::Window, process: String) {
*WATCH_GAME_PROCESS.lock().unwrap() = process;
window.listen("disable_process_watcher", |_e| {
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
});
println!("Starting process watcher...");
thread::spawn(move || {
// Initial sleep for 8 seconds, since running 20 different injectors or whatever can take a while
std::thread::sleep(std::time::Duration::from_secs(10));
let mut system = System::new_all();
loop {
// 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;
}
}
}
});
}
#[tauri::command]
fn is_grasscutter_running() -> bool {
// Grab the grasscutter process name
let proc = WATCH_GRASSCUTTER_PROCESS.lock().unwrap().to_string();
!proc.is_empty()
}
#[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(Pid::from(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
}
}
#[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]
@@ -142,11 +315,8 @@ fn disconnect() {
#[tauri::command]
async fn req_get(url: String) -> String {
// Send a GET request to the specified URL.
let response = web::query(&url.to_string()).await;
// Send the response body back to the client.
return response;
// Send a GET request to the specified URL and send the response body back to the client.
web::query(&url.to_string()).await
}
#[tauri::command]
@@ -175,67 +345,12 @@ async fn get_theme_list(data_dir: String) -> Vec<HashMap<String, String>> {
map.insert("json".to_string(), theme_json);
map.insert("path".to_string(), path.to_str().unwrap().to_string());
// Push key-value pair containing "json" and "path"
themes.push(map);
}
}
}
return themes;
themes
}
#[tauri::command]
async fn get_bg_file(bg_path: String, appdata: String) -> String {
let copy_loc = appdata;
let query = web::query("https://api.grasscutters.xyz/cultivation/query").await;
let response_data: APIQuery = match serde_json::from_str(&query) {
Ok(data) => data,
Err(e) => {
println!("Failed to parse response: {}", e);
return "".to_string();
}
};
let file_name = response_data.bg_file.to_string();
// First we see if the file already exists in our local bg folder.
if file_helpers::dir_exists(format!("{}\\bg\\{}", copy_loc, file_name).as_str()) {
return format!("{}\\{}", copy_loc, response_data.bg_file.as_str());
}
// Now we check if the bg folder, which is one directory above the game_path, exists.
let bg_img_path = format!("{}\\{}", bg_path.clone().to_string(), file_name.as_str());
// If it doesn't, then we do not have backgrounds to grab.
if !file_helpers::dir_exists(&bg_path) {
return "".to_string();
}
// BG folder does exist, lets see if the image exists.
if !file_helpers::dir_exists(&bg_img_path) {
// Image doesn't exist
return "".to_string();
}
// The image exists, lets copy it to our local '\bg' folder.
let bg_img_path_local = format!("{}\\bg\\{}", copy_loc, file_name.as_str());
return match std::fs::copy(bg_img_path, bg_img_path_local) {
Ok(_) => {
// Copy was successful, lets return true.
format!("{}\\{}", copy_loc, response_data.bg_file.as_str())
}
Err(e) => {
// Copy failed, lets return false
println!("Failed to copy background image: {}", e);
"".to_string()
}
};
}
#[tauri::command]
fn base64_decode(encoded: String) -> String {
let decoded = base64::decode(&encoded).unwrap();
return String::from_utf8(decoded).unwrap();
}

View File

@@ -3,37 +3,38 @@
* https://github.com/omjadas/hudsucker/blob/main/examples/log.rs
*/
use lazy_static::lazy_static;
use std::{sync::Mutex, str::FromStr};
#[cfg(target_os = "linux")]
use crate::system_helpers::run_command;
use once_cell::sync::Lazy;
use std::{path::PathBuf, str::FromStr, sync::Mutex};
use rcgen::*;
use hudsucker::{
async_trait::async_trait,
certificate_authority::RcgenAuthority,
hyper::{Body, Request, Response},
hyper::{Body, Request, Response, StatusCode},
*,
};
use rcgen::*;
use std::fs;
use std::net::SocketAddr;
use registry::{Hive, Data, Security};
use std::path::Path;
use rustls_pemfile as pemfile;
use tauri::http::Uri;
use crate::system_helpers::run_command;
use tauri::{api::path::data_dir, http::Uri};
#[cfg(windows)]
use registry::{Data, Hive, Security};
async fn shutdown_signal() {
tokio::signal::ctrl_c().await
tokio::signal::ctrl_c()
.await
.expect("Failed to install CTRL+C signal handler");
}
// Global ver for getting server address.
lazy_static! {
static ref SERVER: Mutex<String> = {
let m = "http://localhost:443".to_string();
Mutex::new(m)
};
}
static SERVER: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new("http://localhost:443".to_string()));
#[derive(Clone)]
struct ProxyHandler;
@@ -45,48 +46,98 @@ pub fn set_proxy_addr(addr: String) {
#[async_trait]
impl HttpHandler for ProxyHandler {
async fn handle_request(&mut self,
_context: &HttpContext,
mut request: Request<Body>,
async fn handle_request(
&mut self,
_ctx: &HttpContext,
mut req: Request<Body>,
) -> RequestOrResponse {
let uri = request.uri().to_string();
let uri_path = request.uri().path();
let uri = req.uri().to_string();
if uri.contains("hoyoverse.com") || uri.contains("mihoyo.com") || uri.contains("yuanshen.com") {
// Create new URI.
let new_uri = Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path).as_str()).unwrap();
// Set request URI to the new one.
*request.uri_mut() = new_uri;
// Handle CONNECTs
if req.method().as_str() == "CONNECT" {
let builder = Response::builder()
.header("DecryptEndpoint", "Created")
.status(StatusCode::OK);
let res = builder.body(()).unwrap();
// Respond to CONNECT
*res.body()
} else {
let uri_path_and_query = req.uri().path_and_query().unwrap().as_str();
// Create new URI.
let new_uri =
Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path_and_query).as_str())
.unwrap();
// Set request URI to the new one.
*req.uri_mut() = new_uri;
}
}
RequestOrResponse::Request(request)
req.into()
}
async fn handle_response(&mut self,
_context: &HttpContext,
response: Response<Body>,
) -> Response<Body> { response }
async fn handle_response(
&mut self,
_context: &HttpContext,
response: Response<Body>,
) -> Response<Body> {
response
}
async fn should_intercept(&mut self, _ctx: &HttpContext, _req: &Request<Body>) -> bool {
let uri = _req.uri().to_string();
if uri.contains("hoyoverse.com") || uri.contains("mihoyo.com") || uri.contains("yuanshen.com") {
true
} else {
false
}
}
}
/**
* Starts an HTTP(S) proxy server.
*/
pub async fn create_proxy(proxy_port: u16, certificate_path: String) {
let cert_path = PathBuf::from(certificate_path);
let pk_path = cert_path.join("private.key");
let ca_path = cert_path.join("cert.crt");
// Get the certificate and private key.
let mut private_key_bytes: &[u8] = &fs::read(format!("{}\\private.key", certificate_path)).expect("Could not read private key");
let mut ca_cert_bytes: &[u8] = &fs::read(format!("{}\\cert.crt", certificate_path)).expect("Could not read certificate");
let mut private_key_bytes: &[u8] = &match fs::read(&pk_path) {
// Try regenerating the CA stuff and read it again. If that doesn't work, quit.
Ok(b) => b,
Err(e) => {
println!("Encountered {}. Regenerating CA cert and retrying...", e);
generate_ca_files(&data_dir().unwrap().join("cultivation"));
fs::read(&pk_path).expect("Could not read private key")
}
};
let mut ca_cert_bytes: &[u8] = &match fs::read(&ca_path) {
// Try regenerating the CA stuff and read it again. If that doesn't work, quit.
Ok(b) => b,
Err(e) => {
println!("Encountered {}. Regenerating CA cert and retrying...", e);
generate_ca_files(&data_dir().unwrap().join("cultivation"));
fs::read(&ca_path).expect("Could not read certificate")
}
};
// Parse the private key and certificate.
let private_key = rustls::PrivateKey(
pemfile::pkcs8_private_keys(&mut private_key_bytes)
.expect("Failed to parse private key")
.remove(0)
.remove(0),
);
let ca_cert = rustls::Certificate(
pemfile::certs(&mut ca_cert_bytes)
.expect("Failed to parse CA certificate")
.remove(0)
.remove(0),
);
// Create the certificate authority.
@@ -108,44 +159,105 @@ pub async fn create_proxy(proxy_port: u16, certificate_path: String) {
/**
* Connects to the local HTTP(S) proxy server.
*/
#[cfg(windows)]
pub fn connect_to_proxy(proxy_port: u16) {
if cfg!(target_os = "windows") {
// Create 'ProxyServer' string.
let server_string: String = format!("http=127.0.0.1:{};https=127.0.0.1:{}", proxy_port, proxy_port);
// Create 'ProxyServer' string.
let server_string: String = format!(
"http=127.0.0.1:{};https=127.0.0.1:{}",
proxy_port, proxy_port
);
// Fetch the 'Internet Settings' registry key.
let settings = Hive::CurrentUser.open(r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", Security::Write).unwrap();
// Fetch the 'Internet Settings' registry key.
let settings = Hive::CurrentUser
.open(
r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
// Only write should be needed but too many cases of Culti not being able to read/write proxy settings
Security::AllAccess,
)
.unwrap();
// Set registry values.
settings.set_value("ProxyServer", &Data::String(server_string.parse().unwrap())).unwrap();
settings.set_value("ProxyEnable", &Data::U32(1)).unwrap();
}
// Set registry values.
settings
.set_value("ProxyServer", &Data::String(server_string.parse().unwrap()))
.unwrap();
settings.set_value("ProxyEnable", &Data::U32(1)).unwrap();
println!("Connected to the proxy.");
}
#[cfg(unix)]
pub fn connect_to_proxy(proxy_port: u16) {
// Edit /etc/environment to set $http_proxy and $https_proxy
let mut env_file = match fs::read_to_string("/etc/environment") {
Ok(f) => f,
Err(e) => {
println!("Error opening /etc/environment: {}", e);
return;
}
};
// Append the proxy configuration.
// We will not remove the current proxy config if it exists, so we can just remove these last lines when we disconnect
env_file += format!("\nhttps_proxy=127.0.0.1:{}", proxy_port).as_str();
env_file += format!("\nhttp_proxy=127.0.0.1:{}", proxy_port).as_str();
// Save
fs::write("/etc/environment", env_file).unwrap();
}
#[cfg(target_od = "macos")]
pub fn connect_to_proxy(_proxy_port: u16) {
println!("No Mac support yet. Someone mail me a Macbook and I will do it B)")
}
/**
* Disconnects from the local HTTP(S) proxy server.
*/
#[cfg(windows)]
pub fn disconnect_from_proxy() {
if cfg!(target_os = "windows") {
// Fetch the 'Internet Settings' registry key.
let settings = Hive::CurrentUser.open(r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", Security::Write).unwrap();
// Fetch the 'Internet Settings' registry key.
let settings = Hive::CurrentUser
.open(
r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
Security::AllAccess,
)
.unwrap();
// Set registry values.
settings.set_value("ProxyEnable", &Data::U32(0)).unwrap();
}
// Set registry values.
settings.set_value("ProxyEnable", &Data::U32(0)).unwrap();
println!("Disconnected from proxy.");
}
#[cfg(target_os = "linux")]
pub fn disconnect_from_proxy() {
println!("Re-writing environment variables");
let regexp = regex::Regex::new(
// This has to be specific as possible or we risk fuckin up their environment LOL
r"(https|http)_proxy=.*127.0.0.1:.*",
)
.unwrap();
let environment = &fs::read_to_string("/etc/environment").expect("Failed to open environment");
let new_environment = regexp.replace_all(environment, "").to_string();
// Write new environment
fs::write("/etc/environment", new_environment.trim_end()).expect(
"Could not write environment, remove proxy declarations manually if they are still set",
);
}
#[cfg(target_os = "macos")]
pub fn disconnect_from_proxy() {}
/*
* Generates a private key and certificate used by the certificate authority.
* Additionally installs the certificate and private key in the Root CA store.
* Source: https://github.com/zu1k/good-mitm/raw/master/src/ca/gen.rs
*/
#[tauri::command]
pub fn generate_ca_files(path: &str) {
pub fn generate_ca_files(path: &Path) {
let mut params = CertificateParams::default();
let mut details = DistinguishedName::new();
@@ -154,7 +266,7 @@ pub fn generate_ca_files(path: &str) {
details.push(DnType::OrganizationName, "Grasscutters");
details.push(DnType::CountryName, "CN");
details.push(DnType::LocalityName, "CN");
// Set details in the parameter.
params.distinguished_name = details;
// Set other properties.
@@ -162,49 +274,99 @@ pub fn generate_ca_files(path: &str) {
params.key_usages = vec![
KeyUsagePurpose::DigitalSignature,
KeyUsagePurpose::KeyCertSign,
KeyUsagePurpose::CrlSign
KeyUsagePurpose::CrlSign,
];
// Create certificate.
let cert_path = format!("{}\\ca", path);
let cert = Certificate::from_params(params).unwrap();
let cert_crt = cert.serialize_pem().unwrap();
let private_key = cert.serialize_private_key_pem();
// Make certificate directory.
match fs::create_dir(&cert_path) {
Ok(_) => {},
let cert_dir = path.join("ca");
match fs::create_dir(&cert_dir) {
Ok(_) => {}
Err(e) => {
println!("{}", e);
}
};
// Write the certificate to a file.
match fs::write(format!("{}\\cert.crt", &cert_path), cert_crt) {
Ok(_) => println!("Wrote certificate to {}", &cert_path),
Err(e) => println!("Error writing certificate to {}: {}", &cert_path, e),
let cert_path = cert_dir.join("cert.crt");
match fs::write(&cert_path, cert_crt) {
Ok(_) => println!("Wrote certificate to {}", cert_path.to_str().unwrap()),
Err(e) => println!(
"Error writing certificate to {}: {}",
cert_path.to_str().unwrap(),
e
),
}
// Write the private key to a file.
match fs::write(format!("{}\\private.key", &cert_path), private_key) {
Ok(_) => println!("Wrote private key to {}", &cert_path),
Err(e) => println!("Error writing private key to {}: {}", &cert_path, e),
let private_key_path = cert_dir.join("private.key");
match fs::write(&private_key_path, private_key) {
Ok(_) => println!(
"Wrote private key to {}",
private_key_path.to_str().unwrap()
),
Err(e) => println!(
"Error writing private key to {}: {}",
private_key_path.to_str().unwrap(),
e
),
}
// Install certificate into the system's Root CA store.
install_ca_files(path);
install_ca_files(&cert_path);
}
/*
* Attempts to install the certificate authority's certificate into the Root CA store.
*/
pub fn install_ca_files(path: &str) {
if cfg!(target_os = "windows") {
run_command(format!("certutil -user -addstore \"Root\" {}\\ca\\cert.crt", path).to_string());
} else {
run_command(format!("security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain {}/ca/cert.crt", path).to_string());
}
#[cfg(windows)]
pub fn install_ca_files(cert_path: &Path) {
crate::system_helpers::run_command(
"certutil",
vec!["-user", "-addstore", "Root", cert_path.to_str().unwrap()],
None,
);
println!("Installed certificate.");
}
#[cfg(target_os = "macos")]
pub fn install_ca_files(cert_path: &Path) {
crate::system_helpers::run_command(
"security",
vec![
"add-trusted-cert",
"-d",
"-r",
"trustRoot",
"-k",
"/Library/Keychains/System.keychain",
cert_path.to_str().unwrap(),
],
None,
);
println!("Installed certificate.");
}
// If this is borked on non-debian platforms, so be it
#[cfg(target_os = "linux")]
pub fn install_ca_files(cert_path: &Path) {
let usr_certs = PathBuf::from("/usr/local/share/ca-certificates");
let usr_cert_path = usr_certs.join("cultivation.crt");
// Create dir if it doesn't exist
fs::create_dir_all(&usr_certs).expect("Unable to create local certificate directory");
fs::copy(cert_path, usr_cert_path).expect("Unable to copy cert to local certificate directory");
run_command("update-ca-certificates", vec![], None);
println!("Installed certificate.");
}
}
#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
pub fn install_ca_files(_cert_path: &Path) {
println!("Certificate installation is not supported on this platform.");
}

View File

@@ -1,8 +0,0 @@
#![allow(non_snake_case)]
use serde::Deserialize;
#[derive(Deserialize)]
pub(crate) struct APIQuery {
pub bg_file: String,
}

View File

@@ -1,36 +1,62 @@
use duct::cmd;
use ini::Ini;
use std::ffi::OsStr;
use std::path::PathBuf;
use windows_service::service::{ServiceAccess, ServiceState::Stopped};
use windows_service::service_manager::{ServiceManager, ServiceManagerAccess};
use std::thread;
use std::process::Command;
use tauri;
use open;
use crate::file_helpers;
#[cfg(windows)]
use registry::{Data, Hive, Security};
#[tauri::command]
pub fn run_program(path: String) {
// Open the program from the specified path.
// Open in new thread to prevent blocking.
thread::spawn(move || {
open::that(&path).unwrap();
});
pub fn run_program(path: String, args: Option<String>) {
// Without unwrap_or, this can crash when UAC prompt is denied
open::that(format!("{} {}", &path, &args.unwrap_or_else(|| "".into()))).unwrap_or(());
}
#[tauri::command]
pub fn run_command(command: String) {
// Run the specified command.
if cfg!(target_os = "windows") {
Command::new("cmd")
.args(["/C", command.as_str()])
.output()
.expect("failed to execute process")
} else {
Command::new("sh")
.arg("-c")
.arg(command.as_str())
.output()
.expect("failed to execute process")
};
pub fn run_program_relative(path: String, args: Option<String>) {
// Save the current working directory
let cwd = std::env::current_dir().unwrap();
// Set the new working directory to the path before the executable
let mut path_buf = std::path::PathBuf::from(&path);
path_buf.pop();
// Set new working directory
std::env::set_current_dir(&path_buf).unwrap();
// Without unwrap_or, this can crash when UAC prompt is denied
open::that(format!("{} {}", &path, args.unwrap_or_else(|| "".into()))).unwrap_or(());
// Restore the original working directory
std::env::set_current_dir(cwd).unwrap();
}
#[tauri::command]
pub fn run_command(program: &str, args: Vec<&str>, relative: Option<bool>) {
let prog = program.to_string();
let args = args.iter().map(|s| s.to_string()).collect::<Vec<String>>();
// Commands should not block (this is for the reshade injector mostly)
std::thread::spawn(move || {
// Save the current working directory
let cwd = std::env::current_dir().unwrap();
if relative.unwrap_or(false) {
// Set the new working directory to the path before the executable
let mut path_buf = std::path::PathBuf::from(&prog);
path_buf.pop();
// Set new working directory
std::env::set_current_dir(&path_buf).unwrap();
}
cmd(prog, args).run().unwrap();
// Restore the original working directory
std::env::set_current_dir(cwd).unwrap();
});
}
#[tauri::command]
@@ -42,7 +68,10 @@ pub fn run_jar(path: String, execute_in: String, java_path: String) {
};
// Open the program from the specified path.
match open::with(format!("/k cd /D \"{}\" & {}", &execute_in, &command).to_string(), "C:\\Windows\\System32\\cmd.exe") {
match open::with(
format!("/k cd /D \"{}\" & {}", &execute_in, &command),
"C:\\Windows\\System32\\cmd.exe",
) {
Ok(_) => (),
Err(e) => println!("Failed to open jar ({} from {}): {}", &path, &execute_in, e),
};
@@ -57,26 +86,6 @@ pub fn open_in_browser(url: String) {
};
}
#[tauri::command]
pub fn copy_file(path: String, new_path: String) -> bool {
let filename = &path.split("/").last().unwrap();
let mut new_path_buf = std::path::PathBuf::from(&new_path);
// If the new path doesn't exist, create it.
if !file_helpers::dir_exists(new_path_buf.pop().to_string().as_str()) {
std::fs::create_dir_all(&new_path).unwrap();
}
// Copy old to new
match std::fs::copy(&path, format!("{}/{}", new_path, filename)) {
Ok(_) => true,
Err(e) => {
println!("Failed to copy file: {}", e);
false
}
}
}
#[tauri::command]
pub fn install_location() -> String {
let mut exe_path = std::env::current_exe().unwrap();
@@ -87,7 +96,186 @@ pub fn install_location() -> String {
return exe_path.to_str().unwrap().to_string();
}
#[tauri::command]
pub fn set_migoto_target(window: tauri::Window, migoto_path: String) -> bool {
let mut migoto_pathbuf = PathBuf::from(migoto_path);
migoto_pathbuf.pop();
migoto_pathbuf.push("d3dx.ini");
let mut conf = match Ini::load_from_file(&migoto_pathbuf) {
Ok(c) => {
println!("Loaded migoto ini");
c
}
Err(e) => {
println!("Error loading migoto config: {}", e);
return false;
}
};
window.emit("migoto_set", &()).unwrap();
// Set options
conf
.with_section(Some("Loader"))
.set("target", "GenshinImpact.exe");
// Write file
match conf.write_to_file(&migoto_pathbuf) {
Ok(_) => {
println!("Wrote config!");
true
}
Err(e) => {
println!("Error writing config: {}", e);
false
}
}
}
#[tauri::command]
pub fn set_migoto_delay(migoto_path: String) -> bool {
let mut migoto_pathbuf = PathBuf::from(migoto_path);
migoto_pathbuf.pop();
migoto_pathbuf.push("d3dx.ini");
let mut conf = match Ini::load_from_file(&migoto_pathbuf) {
Ok(c) => {
println!("Loaded migoto ini");
c
}
Err(e) => {
println!("Error loading migoto config: {}", e);
return false;
}
};
// Set options
conf
.with_section(Some("Loader"))
.set("delay", "20");
// Write file
match conf.write_to_file(&migoto_pathbuf) {
Ok(_) => {
println!("Wrote delay!");
true
}
Err(e) => {
println!("Error writing delay: {}", e);
false
}
}
}
#[cfg(windows)]
#[tauri::command]
pub fn wipe_registry(exec_name: String) {
// Fetch the 'Internet Settings' registry key.
let settings =
match Hive::CurrentUser.open(format!("Software\\miHoYo\\{}", exec_name), Security::Write) {
Ok(s) => s,
Err(e) => {
println!("Error getting registry setting: {}", e);
return;
}
};
// Wipe login cache
match settings.set_value(
"MIHOYOSDK_ADL_PROD_OVERSEA_h1158948810",
&Data::String("".parse().unwrap()),
) {
Ok(_) => (),
Err(e) => println!("Error wiping registry: {}", e),
}
}
#[cfg(windows)]
#[tauri::command]
pub fn service_status(service: String) -> bool {
let manager = match ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT) {
Ok(manager) => manager,
Err(_e) => return false,
};
let my_service = match manager.open_service(service.clone(), ServiceAccess::QUERY_STATUS) {
Ok(my_service) => my_service,
Err(_e) => {
println!("{} service not found! Not installed?", service);
return false;
}
};
let status_result = my_service.query_status();
if status_result.is_ok() {
let status = status_result.unwrap();
println!("{} service status: {:?}", service, status.current_state);
if status.current_state == Stopped {
// Start the service if it is stopped
start_service(service);
}
true
} else {
false
}
}
#[cfg(windows)]
#[tauri::command]
pub fn start_service(service: String) -> bool {
println!("Starting service: {}", service);
let manager = match ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT) {
Ok(manager) => manager,
Err(_e) => return false,
};
let my_service = match manager.open_service(service, ServiceAccess::START) {
Ok(my_service) => my_service,
Err(_e) => return false,
};
match my_service.start(&[OsStr::new("Started service!")]) {
Ok(_s) => true,
Err(_e) => return false,
};
true
}
#[cfg(windows)]
#[tauri::command]
pub fn stop_service(service: String) -> bool {
println!("Stopping service: {}", service);
let manager = match ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT) {
Ok(manager) => manager,
Err(_e) => return false,
};
let my_service = match manager.open_service(service, ServiceAccess::STOP) {
Ok(my_service) => my_service,
Err(_e) => return false,
};
match my_service.stop() {
Ok(_s) => true,
Err(_e) => return false,
};
true
}
#[cfg(unix)]
#[tauri::command]
pub fn wipe_registry(_exec_name: String) {}
#[cfg(windows)]
#[tauri::command]
pub fn is_elevated() -> bool {
return is_elevated::is_elevated();
}
is_elevated::is_elevated()
}
#[cfg(unix)]
#[tauri::command]
pub fn is_elevated() -> bool {
sudo::check() == sudo::RunningAs::Root
}
#[tauri::command]
pub fn get_platform() -> &'static str {
std::env::consts::OS
}

View File

@@ -1,11 +1,16 @@
use zip_extract;
use zip;
use std::fs::File;
use std::fs::{read_dir, File};
use std::path;
use std::thread;
use unrar::archive::Archive;
#[tauri::command]
pub fn unzip(window: tauri::Window, zipfile: String, destpath: String) {
pub fn unzip(
window: tauri::Window,
zipfile: String,
destpath: String,
top_level: Option<bool>,
folder_if_loose: Option<bool>,
) {
// Read file TODO: replace test file
let f = match File::open(&zipfile) {
Ok(f) => f,
@@ -17,54 +22,177 @@ pub fn unzip(window: tauri::Window, zipfile: String, destpath: String) {
let write_path = path::PathBuf::from(&destpath);
// Get a list of all current directories
let mut dirs = vec![];
for entry in read_dir(&write_path).unwrap() {
let entry = entry.unwrap();
let entry_path = entry.path();
if entry_path.is_dir() {
dirs.push(entry_path);
}
}
// Run extraction in seperate thread
thread::spawn(move || {
let full_path = write_path;
let mut full_path = write_path.clone();
window.emit("extract_start", &zipfile).unwrap();
match zip_extract::extract(&f, &full_path, true) {
Ok(_) => {
println!("Extracted zip file to: {}", full_path.to_str().unwrap_or("Error"));
}
Err(e) => {
println!("Failed to extract zip file: {}", e);
let mut res_hash = std::collections::HashMap::new();
if folder_if_loose.unwrap_or(false) {
// Create a new folder with the same name as the zip file
let mut file_name = path::Path::new(&zipfile)
.file_name()
.unwrap()
.to_str()
.unwrap();
res_hash.insert(
"error".to_string(),
e.to_string(),
);
// remove ".zip" from the end of the file name
file_name = &file_name[..file_name.len() - 4];
res_hash.insert(
"path".to_string(),
zipfile.to_string(),
);
let new_path = full_path.join(file_name);
match std::fs::create_dir_all(&new_path) {
Ok(_) => {}
Err(e) => {
println!("Failed to create directory: {}", e);
return;
}
};
window.emit("download_error", &res_hash).unwrap();
}
};
full_path = new_path;
}
// Get the name of the inenr file in the zip file
let mut zip = zip::ZipArchive::new(&f).unwrap();
let file = zip.by_index(0).unwrap();
let name = file.name();
println!("Is rar file? {}", zipfile.ends_with(".rar"));
let name;
let success;
// If file ends in zip, OR is unknown, extract as zip, otherwise extract as rar
if zipfile.ends_with(".rar") {
success = extract_rar(&zipfile, &f, &full_path, top_level.unwrap_or(true));
let archive = Archive::new(zipfile.clone());
name = archive.list().unwrap().next().unwrap().unwrap().filename;
} else if zipfile.ends_with(".7z") {
success = extract_7z(&zipfile, &f, &full_path, top_level.unwrap_or(true));
name = String::from("banana");
} else {
success = extract_zip(&zipfile, &f, &full_path, top_level.unwrap_or(true));
// Get the name of the inenr file in the zip file
let mut zip = zip::ZipArchive::new(&f).unwrap();
let file = zip.by_index(0).unwrap();
name = file.name().to_string();
}
if !success {
let mut res_hash = std::collections::HashMap::new();
res_hash.insert("path".to_string(), zipfile.to_string());
window.emit("download_error", &res_hash).unwrap();
}
// If the contents is a jar file, emit that we have extracted a new jar file
if name.ends_with(".jar") {
window.emit("jar_extracted", destpath.to_string() + name).unwrap();
window
.emit("jar_extracted", destpath.to_string() + name.as_str())
.unwrap();
}
// If downloading full build, emit that the jar was extracted with it
if zipfile.contains("GrasscutterCulti") {
window
.emit("jar_extracted", destpath.to_string() + "grasscutter.jar")
.unwrap();
}
if zipfile.contains("GIMI") {
window
.emit("migoto_extracted", destpath.to_string() + "3DMigoto Loader.exe")
.unwrap();
}
// Delete zip file
match std::fs::remove_file(&zipfile) {
Ok(_) => {
// Get any new directory that could have been created
let mut new_dir: String = String::new();
for entry in read_dir(&write_path).unwrap() {
let entry = entry.unwrap();
let entry_path = entry.path();
if entry_path.is_dir() && !dirs.contains(&entry_path) {
new_dir = entry_path.to_str().unwrap().to_string();
}
}
let mut res_hash = std::collections::HashMap::new();
res_hash.insert("file", zipfile.to_string());
res_hash.insert("new_folder", new_dir);
window.emit("extract_end", &res_hash).unwrap();
println!("Deleted zip file: {}", zipfile);
}
Err(e) => {
println!("Failed to delete zip file: {}", e);
}
};
window.emit("extract_end", &zipfile).unwrap();
});
}
}
fn extract_rar(rarfile: &str, _f: &File, full_path: &path::Path, _top_level: bool) -> bool {
let archive = Archive::new(rarfile.to_string());
let mut open_archive = archive
.extract_to(full_path.to_str().unwrap().to_string())
.unwrap();
match open_archive.process() {
Ok(_) => {
println!(
"Extracted rar file to: {}",
full_path.to_str().unwrap_or("Error")
);
true
}
Err(e) => {
println!("Failed to extract rar file: {}", e);
false
}
}
}
fn extract_zip(_zipfile: &str, f: &File, full_path: &path::Path, top_level: bool) -> bool {
match zip_extract::extract(f, full_path, top_level) {
Ok(_) => {
println!(
"Extracted zip file to: {}",
full_path.to_str().unwrap_or("Error")
);
true
}
Err(e) => {
println!("Failed to extract zip file: {}", e);
false
}
}
}
fn extract_7z(sevenzfile: &str, _f: &File, full_path: &path::Path, _top_level: bool) -> bool {
match sevenz_rust::decompress_file(sevenzfile, full_path) {
Ok(_) => {
println!(
"Extracted 7zip file to: {}",
full_path.to_str().unwrap_or("Error")
);
true
}
Err(e) => {
println!("Failed to extract 7zip file: {}", e);
false
}
}
}

View File

@@ -1,10 +1,21 @@
use reqwest::header::USER_AGENT;
use reqwest::header::{CONTENT_TYPE, USER_AGENT};
pub(crate) async fn query(site: &str) -> String {
let client = reqwest::Client::new();
let response = client.get(site).header(USER_AGENT, "cultivation").send().await.unwrap();
return response.text().await.unwrap();
let response = client
.get(site)
.header(USER_AGENT, "cultivation")
.header(CONTENT_TYPE, "application/json")
.send()
.await
.ok();
if response.is_some() {
response.unwrap().text().await.unwrap()
} else {
false.to_string()
}
}
#[tauri::command]
@@ -12,7 +23,22 @@ pub(crate) async fn valid_url(url: String) -> bool {
// Check if we get a 200 response
let client = reqwest::Client::new();
let response = client.get(url).header(USER_AGENT, "cultivation").send().await.unwrap();
let response = client
.get(url)
.header(USER_AGENT, "cultivation")
.send()
.await
.ok();
return response.status().as_str() == "200";
}
if response.is_some() {
return response.unwrap().status().as_str() == "200";
} else {
false
}
}
#[tauri::command]
pub async fn web_get(url: String) -> String {
// Send a GET request to the specified URL and send the response body back to the client.
query(&url).await
}

View File

@@ -7,25 +7,17 @@
},
"package": {
"productName": "Cultivation",
"version": "1.0.0"
"version": "1.0.25"
},
"tauri": {
"allowlist": {
"fs": {
"scope": [
"$DATA",
"$DATA/cultivation",
"$DATA/cultivation/*"
]
"scope": ["$DATA", "$DATA/cultivation", "$DATA/cultivation/*"]
},
"protocol": {
"all": true,
"asset": true,
"assetScope": [
"$DATA",
"$DATA/cultivation",
"$DATA/cultivation/*"
]
"assetScope": ["$DATA", "$DATA/cultivation", "$DATA/cultivation/*"]
},
"all": true
},
@@ -37,13 +29,7 @@
"depends": []
},
"externalBin": [],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
"identifier": "io.grasscutter",
"shortDescription": "A game launcher.",
"longDescription": "A launcher for a certain anime game that proxies all related game traffic to external servers.",
@@ -54,9 +40,7 @@
"providerShortName": null,
"signingIdentity": null
},
"resources": [
"lang/*.json"
],
"resources": ["lang/*.json", "keys/*", "patch/*"],
"targets": "all",
"windows": {
"allowDowngrades": false,
@@ -72,10 +56,11 @@
"csp": "default-src 'self' https://asset.localhost; img-src 'self'; img-src https://* asset: https://asset.localhost"
},
"updater": {
"active": true,
"active": false,
"dialog": true,
"endpoints": [
"https://api.grasscutters.xyz/cultivation/update?version={{current_version}}"
"https://api.grasscutter.io/cultivation/updater?version={{current_version}}",
"https://api.grasscutters.xyz/cultivation/updater?version={{current_version}}"
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEIyM0MzMzk0NkM4OTQzNQpSV1ExbE1oR09jTWpDNFJ4NURsaS9mdHpDWmpBM3JNVHkrdER4bS9KdHFCQnNVSWs0ZjZTU295WAo="
},
@@ -90,4 +75,4 @@
}
]
}
}
}

View File

@@ -1,13 +1,11 @@
body {
margin: 0;
font-family: 'MiHoYo_SDK_Web', 'Helvetica Neue', BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
sans-serif;
font-family: 'MiHoYo_SDK_Web', 'Helvetica Neue', BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
'Cantarell', 'Fira Sans', 'Droid Sans', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}

View File

@@ -3,20 +3,19 @@ import ReactDOM from 'react-dom/client'
import './index.css'
import App from './ui/App'
// import Debug from './ui/Debug'
import Debug from './ui/Debug'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
)
import { getConfigOption } from './utils/configuration'
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
let isDebug = false
;async () => {
isDebug = await getConfigOption('debug_enabled')
}
root.render(<React.StrictMode>{isDebug ? <Debug /> : <App />}</React.StrictMode>)
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
import reportWebVitals from './utils/reportWebVitals'
reportWebVitals(console.log)
isDebug && reportWebVitals(console.log)

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 KiB

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M7045 10204 c-301 -50 -596 -277 -731 -564 -96 -204 -111 -463 -40
-692 16 -52 14 -58 -26 -58 -62 0 -220 -85 -331 -178 -24 -20 -105 -91 -178
-157 -74 -66 -177 -156 -229 -200 -208 -175 -629 -573 -1001 -944 -152 -152
-286 -284 -297 -294 -18 -17 -27 -17 -75 -8 -96 21 -249 29 -361 20 -321 -26
-613 -162 -836 -389 -341 -346 -457 -846 -303 -1301 l36 -106 -66 -100 c-102
-155 -212 -343 -303 -518 -353 -683 -556 -1455 -531 -2025 6 -136 29 -341 47
-415 36 -156 58 -234 92 -335 47 -137 52 -147 134 -315 90 -182 113 -220 223
-375 376 -528 945 -931 1571 -1113 261 -76 439 -94 503 -52 27 17 30 17 105
-4 125 -35 209 -50 345 -62 107 -9 152 -8 279 6 118 13 180 15 277 8 214 -14
475 -9 566 11 144 31 312 80 395 114 14 6 68 27 120 48 91 36 302 135 365 172
180 104 373 265 507 422 346 407 518 977 520 1725 0 287 -10 448 -48 755 -43
357 -95 631 -208 1100 -24 102 -47 199 -51 217 -5 26 -1 41 25 78 48 70 108
204 132 297 32 118 31 366 0 482 -75 272 -258 503 -507 639 l-77 42 -68 202
c-37 112 -70 213 -73 225 -3 13 -14 57 -26 98 -12 41 -33 134 -47 205 -23 115
-26 154 -27 345 0 223 1 232 62 580 44 245 54 349 47 490 -10 234 -52 334
-206 495 -98 103 -128 150 -167 261 -24 70 -27 94 -28 209 0 135 10 185 58
280 49 99 182 235 282 288 179 96 373 112 464 39 62 -49 101 -117 101 -174 0
-81 -49 -152 -117 -172 -13 -3 -32 -8 -42 -11 -13 -4 -34 7 -65 34 -26 22 -63
45 -84 52 -107 32 -219 -73 -198 -185 7 -37 89 -141 129 -163 137 -76 279 -77
428 -3 102 52 190 149 236 263 36 89 42 239 14 326 -36 108 -112 215 -206 286
-80 62 -158 92 -265 105 -105 11 -142 11 -245 -6z m-574 -1640 c138 -41 189
-186 160 -459 -13 -115 -45 -328 -62 -405 -19 -86 -49 -386 -49 -493 0 -163
17 -299 71 -552 7 -36 35 -132 102 -350 l24 -80 -31 -2 c-114 -9 -233 -28
-305 -50 -193 -55 -383 -185 -509 -347 -12 -16 -24 -27 -27 -24 -6 5 -22 125
-30 223 -3 39 -10 113 -15 165 -10 105 -26 365 -35 595 -5 120 -11 158 -26
192 -34 70 -110 104 -187 84 -44 -12 -77 -47 -217 -230 -38 -51 -90 -118 -115
-150 -25 -32 -70 -91 -100 -131 -30 -40 -64 -84 -76 -98 l-21 -25 -37 67 c-89
159 -229 312 -386 419 l-81 55 213 214 c371 372 775 751 1063 997 94 79 193
166 220 192 78 75 185 157 239 184 60 30 136 33 217 9z m-2317 -1783 c351 -93
611 -365 682 -712 22 -109 15 -291 -16 -403 -107 -396 -472 -688 -900 -722
l-85 -6 70 50 c180 127 282 307 293 513 16 326 -203 611 -533 695 -90 22 -256
23 -347 1 -136 -33 -284 -119 -366 -212 -22 -25 -42 -45 -44 -45 -9 0 11 102
33 175 104 343 411 610 782 681 104 20 329 12 431 -15z m1310 -406 c21 -268
29 -351 51 -545 31 -262 72 -520 120 -765 26 -130 66 -320 75 -360 5 -22 28
-128 50 -235 23 -107 45 -213 50 -235 41 -178 113 -614 145 -880 48 -401 57
-943 20 -1215 -55 -407 -186 -801 -368 -1102 -107 -178 -168 -262 -258 -355
-146 -150 -290 -212 -494 -213 -252 0 -520 107 -800 319 -274 207 -539 609
-674 1021 -112 339 -154 603 -154 960 0 278 18 425 82 671 92 348 301 725 615
1106 l50 61 87 16 c472 86 871 430 1017 876 42 128 55 211 58 383 l4 168 120
157 c169 220 194 252 196 249 1 -1 5 -38 8 -82z m1043 -511 c-144 -112 -216
-335 -168 -519 27 -101 69 -171 148 -245 74 -69 141 -104 246 -127 67 -14 90
-15 159 -5 145 21 277 102 353 218 60 90 86 178 86 287 0 51 -3 102 -6 112
-13 40 26 -17 61 -89 95 -196 84 -447 -28 -633 -183 -303 -569 -423 -917 -284
-171 69 -330 226 -398 394 -152 373 46 791 432 913 73 23 82 17 32 -22z
m-3393 -1044 c104 -70 239 -133 356 -166 52 -15 97 -28 98 -30 4 -2 -15 -29
-83 -124 -60 -83 -171 -254 -217 -335 -185 -323 -305 -691 -349 -1070 -19
-158 -16 -519 5 -690 34 -284 98 -554 186 -790 44 -120 179 -400 231 -480 171
-266 299 -420 509 -610 50 -45 61 -59 47 -63 -47 -10 -352 117 -535 224 -700
409 -1152 1060 -1253 1804 -19 136 -16 466 5 625 78 591 314 1222 678 1813
l70 113 87 -84 c48 -46 122 -107 165 -137z m3105 -469 c175 -88 288 -116 481
-116 190 0 316 30 470 111 30 16 58 30 61 32 15 7 131 -523 183 -833 64 -382
88 -661 88 -1015 -1 -346 -24 -553 -94 -820 -112 -430 -342 -768 -675 -993
-139 -94 -386 -208 -643 -297 -67 -24 -174 -50 -255 -64 -94 -17 -350 -22
-359 -8 -2 4 16 26 40 47 42 37 171 184 204 231 176 256 269 425 360 654 118
295 181 554 221 907 19 168 21 222 16 503 -4 173 -11 369 -16 434 -30 341
-103 828 -177 1168 -15 73 -18 108 -7 108 4 0 50 -22 102 -49z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
<desc>Created with Fabric.js 1.7.22</desc>
<defs>
</defs>
<g transform="translate(128 128) scale(0.72 0.72)" style="">
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(-175.05 -175.05) scale(3.89 3.89)" >
<path d="M 45 0 c 24.813 0 45 20.187 45 45 c 0 24.813 -20.187 45 -45 45 C 20.186 90 0 69.813 0 45 C 0 20.187 20.186 0 45 0 z M 51.263 73.4 l 8.6 -8.6 L 40.064 45 l 19.799 -19.799 l -8.6 -8.6 L 22.864 45 L 51.263 73.4 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1002 B

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
<desc>Created with Fabric.js 1.7.22</desc>
<defs>
</defs>
<g transform="translate(128 128) scale(0.72 0.72)" style="">
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(-175.05 -175.05) scale(3.89 3.89)" >
<path d="M 89.307 43.082 C 74.775 25.601 59.868 16.737 45 16.737 c -14.869 0 -29.775 8.864 -44.307 26.345 c -0.924 1.112 -0.924 2.724 0 3.836 C 15.225 64.399 30.131 73.264 45 73.264 c 14.868 0 29.775 -8.864 44.307 -26.346 C 90.231 45.806 90.231 44.194 89.307 43.082 z M 45 62 c -9.374 0 -17 -7.626 -17 -17 s 7.626 -17 17 -17 s 17 7.626 17 17 S 54.374 62 45 62 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<circle cx="45" cy="45" r="9" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
<desc>Created with Fabric.js 1.7.22</desc>
<defs>
</defs>
<g transform="translate(128 128) scale(0.72 0.72)" style="">
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(-175.05 -175.05) scale(3.89 3.89)" >
<path d="M 0 87.201 h 18.478 c 1.44 0 2.607 -1.167 2.607 -2.607 V 35.343 c 0 -1.44 -1.167 -2.607 -2.607 -2.607 H 0 L 0 87.201 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<path d="M 83.186 46.32 c 3.763 0 6.814 -3.051 6.814 -6.814 c 0 -3.763 -3.051 -6.814 -6.814 -6.814 H 61.591 c 3.758 -6.768 3.872 -27.328 -5.046 -29.797 c -1.689 -0.468 -3.365 0.823 -3.554 2.565 c -1.568 14.428 -10.395 32.362 -19.37 32.881 h -6.991 v 43.003 h 3.627 c 1.952 0 3.817 0.666 5.444 1.743 c 4.063 2.691 10.906 4.265 17.465 4.101 h 3.172 v 0.012 h 21.788 c 3.763 0 6.814 -3.051 6.814 -6.814 c 0 -3.763 -3.051 -6.814 -6.814 -6.814 h 3.037 c 3.763 0 6.814 -3.051 6.814 -6.814 c 0 -3.763 -3.051 -6.814 -6.814 -6.814 h 2.025 c 3.763 0 6.814 -3.051 6.814 -6.814 S 86.949 46.32 83.186 46.32 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
<desc>Created with Fabric.js 1.7.22</desc>
<defs>
</defs>
<g transform="translate(128 128) scale(0.72 0.72)" style="">
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(-175.05 -175.05) scale(3.89 3.89)" >
<path d="M 58.921 90 H 31.079 c -1.155 0 -2.092 -0.936 -2.092 -2.092 V 2.092 C 28.988 0.936 29.924 0 31.079 0 h 27.841 c 1.155 0 2.092 0.936 2.092 2.092 v 85.817 C 61.012 89.064 60.076 90 58.921 90 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<path d="M 90 31.079 v 27.841 c 0 1.155 -0.936 2.092 -2.092 2.092 H 2.092 C 0.936 61.012 0 60.076 0 58.921 V 31.079 c 0 -1.155 0.936 -2.092 2.092 -2.092 h 85.817 C 89.064 28.988 90 29.924 90 31.079 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
<desc>Created with Fabric.js 1.7.22</desc>
<defs>
</defs>
<g transform="translate(128 128) scale(0.72 0.72)" style="">
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(-175.05 -175.05) scale(3.89 3.89)" >
<path d="M 87.12 73.212 L 48.774 34.866 c 3.723 -9.153 1.873 -20.042 -5.553 -27.468 c -7.008 -7.008 -17.094 -9.025 -25.9 -6.104 L 28.96 12.934 c 4.387 4.387 4.833 11.594 0.579 16.112 c -4.402 4.674 -11.761 4.757 -16.268 0.25 L 1.295 17.32 c -2.922 8.807 -0.904 18.892 6.104 25.9 c 7.426 7.426 18.315 9.276 27.468 5.553 L 73.212 87.12 c 3.84 3.84 10.067 3.84 13.908 0 l 0 0 C 90.96 83.279 90.96 77.052 87.12 73.212 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -22,7 +22,8 @@ select:focus {
border-bottom-color: #ffd326;
}
#root, .App {
#root,
.App {
height: 100%;
}
@@ -34,12 +35,29 @@ select:focus {
.TopButton {
height: 60%;
margin: 0px 10px;
color: #c5c5c5;
transition: color 0.1s ease-in-out;
}
.TopButton span {
display: inline-block;
line-height: normal;
border-bottom: 1px solid #c5c5c5;
}
.TopButton img {
filter: invert(95%) sepia(0%) saturate(18%) hue-rotate(153deg) brightness(88%) contrast(81%);
transition: filter 0.1s ease-in-out;
}
.TopButton:hover {
color: #fff;
cursor: pointer;
}
.TopButton:hover img {
filter: invert(100%) sepia(0%) saturate(18%) hue-rotate(153deg) brightness(100%) contrast(100%);
cursor: pointer;
}
@@ -64,8 +82,8 @@ select:focus {
}
.arrow-down {
width: 0;
height: 0;
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-top: 50px solid transparent;
@@ -82,28 +100,28 @@ select:focus {
.BottomSection {
position: absolute;
bottom: 0%;
left: 50%;
transform: translate(-50%, 0%);
bottom: 0%;
left: 50%;
transform: translate(-50%, 0%);
width: 100%;
height: 160px;
width: 100%;
height: 160px;
backdrop-filter: blur(10px);
box-shadow: inset 0px 5px 12px -3px rgb(50 50 50 / 75%);
margin: 0;
padding: 0;
margin: 0;
padding: 0;
}
@media(max-height: 580px) {
.BottomSection {
height: 150px;
}
@media (max-height: 580px) {
.BottomSection {
height: 150px;
}
}
@media(max-height: 500px) {
.BottomSection {
height: 140px;
}
}
@media (max-height: 500px) {
.BottomSection {
height: 140px;
}
}

View File

@@ -1,88 +1,35 @@
import React from 'react'
import { listen } from '@tauri-apps/api/event'
import './App.css'
import DownloadHandler from '../utils/download'
// Major Components
import TopBar from './components/TopBar'
import ServerLaunchSection from './components/ServerLaunchSection'
import MainProgressBar from './components/common/MainProgressBar'
import Options from './components/menu/Options'
import MiniDialog from './components/MiniDialog'
import DownloadList from './components/common/DownloadList'
import Downloads from './components/menu/Downloads'
import NewsSection from './components/news/NewsSection'
import Game from './components/menu/Game'
import RightBar from './components/RightBar'
import { getConfigOption, setConfigOption } from '../utils/configuration'
import { invoke } from '@tauri-apps/api'
import { dataDir } from '@tauri-apps/api/path'
import { appWindow } from '@tauri-apps/api/window'
import { convertFileSrc } from '@tauri-apps/api/tauri'
import { getConfigOption } from '../utils/configuration'
import { getTheme, loadTheme } from '../utils/themes'
import { convertFileSrc, invoke } from '@tauri-apps/api/tauri'
import { Main } from './Main'
import { Mods } from './Mods'
interface IProps {
[key: string]: never;
}
// From https://www.pixiv.net/en/artworks/93995273
import FALLBACK_BG from '../resources/icons/FALLBACK_BG.jpg'
interface IState {
isDownloading: boolean;
optionsOpen: boolean;
miniDownloadsOpen: boolean;
downloadsOpen: boolean;
gameDownloadsOpen: boolean;
bgFile: string;
page: string
bgFile: string
}
const DEFAULT_BG = 'https://api.grasscutters.xyz/content/bgfile'
const downloadHandler = new DownloadHandler()
const DEFAULT_BG = 'https://api.grasscutter.io/cultivation/bgfile'
class App extends React.Component<IProps, IState> {
constructor(props: IProps) {
class App extends React.Component<Readonly<unknown>, IState> {
constructor(props: Readonly<unknown>) {
super(props)
this.state = {
isDownloading: false,
optionsOpen: false,
miniDownloadsOpen: false,
downloadsOpen: false,
gameDownloadsOpen: false,
page: 'main',
bgFile: DEFAULT_BG,
}
listen('lang_error', (payload) => {
console.log(payload)
})
listen('jar_extracted', ({ payload }) => {
setConfigOption('grasscutter_path', payload)
})
let min = false
// periodically check if we need to min/max based on whether the game is open
setInterval(async () => {
const gameOpen = await invoke('is_game_running')
if (gameOpen && !min) {
appWindow.minimize()
min = true
} else if (!gameOpen && min) {
appWindow.unminimize()
min = false
}
}, 1000)
}
async componentDidMount() {
const cert_generated = await getConfigOption('cert_generated')
const game_exe = await getConfigOption('game_install_path')
const custom_bg = await getConfigOption('customBackground')
const game_path = game_exe?.substring(0, game_exe.replace(/\\/g, '/').lastIndexOf('/')) || ''
const root_path = game_path?.substring(0, game_path.replace(/\\/g, '/').lastIndexOf('/')) || ''
// Load a theme if it exists
const theme = await getConfigOption('theme')
if (theme && theme !== 'default') {
@@ -90,134 +37,78 @@ class App extends React.Component<IProps, IState> {
await loadTheme(themeObj, document)
}
if(!custom_bg || !/png|jpg|jpeg$/.test(custom_bg)) {
if(game_path) {
// Get the bg by invoking, then set the background to that bg.
const bgLoc: string = await invoke('get_bg_file', {
bgPath: root_path,
appdata: await dataDir()
})
// Get custom bg AFTER theme is loaded !! important !!
const custom_bg = await getConfigOption('customBackground')
bgLoc && this.setState({
bgFile: bgLoc
}, this.forceUpdate)
}
} else {
if (custom_bg) {
const isUrl = /^http(s)?:\/\//gm.test(custom_bg)
if (!isUrl) {
const isValid = await invoke('dir_exists', {
path: custom_bg
path: custom_bg,
})
this.setState({
bgFile: isValid ? convertFileSrc(custom_bg) : DEFAULT_BG
}, this.forceUpdate)
this.setState(
{
bgFile: isValid ? convertFileSrc(custom_bg) : DEFAULT_BG,
},
this.forceUpdate
)
} else {
// Check if URL returns a valid image.
const isValid = await invoke('valid_url', {
url: custom_bg
url: custom_bg,
})
this.setState({
bgFile: isValid ? custom_bg : DEFAULT_BG
}, this.forceUpdate)
this.setState(
{
bgFile: isValid ? custom_bg : DEFAULT_BG,
},
this.forceUpdate
)
}
}
if (!cert_generated) {
// Generate the certificate
await invoke('generate_ca_files', {
path: await dataDir() + 'cultivation'
} else {
// Check if api bg is accessible
const isDefaultValid = await invoke('valid_url', {
url: DEFAULT_BG,
})
await setConfigOption('cert_generated', true)
this.setState(
{
bgFile: isDefaultValid ? DEFAULT_BG : FALLBACK_BG,
},
this.forceUpdate
)
}
// Period check to only show progress bar when downloading files
setInterval(() => {
window.addEventListener('changePage', (e) => {
this.setState({
isDownloading: downloadHandler.getDownloads().filter(d => d.status !== 'finished')?.length > 0
// @ts-expect-error - TS doesn't like our custom event
page: e.detail,
})
}, 1000)
})
}
render() {
return (
<div className="App" style={
this.state.bgFile ? {
background: `url("${this.state.bgFile}") fixed`,
} : {}
}>
<TopBar
optFunc={() => {
this.setState({ optionsOpen: !this.state.optionsOpen })
}}
downFunc={() => this.setState({ downloadsOpen: !this.state.downloadsOpen })}
gameFunc={() => this.setState({ gameDownloadsOpen: !this.state.gameDownloadsOpen })}
/>
<RightBar />
<NewsSection />
{
// Mini downloads section
this.state.miniDownloadsOpen ? (
<div className="MiniDownloads">
<MiniDialog
title="Downloads"
closeFn={() => {
this.setState({ miniDownloadsOpen: false })
}}
>
<DownloadList downloadManager={downloadHandler} />
</MiniDialog>
<div className="arrow-down"></div>
</div>
) : null
<div
className="App"
style={
this.state.bgFile
? {
background: `url("${this.state.bgFile}") fixed`,
}
: {}
}
{
// Download menu
this.state.downloadsOpen ? (
<Downloads
downloadManager={downloadHandler}
closeFn={() => this.setState({ downloadsOpen: false })}
/>
) : null
}
{
// Options menu
this.state.optionsOpen ? (
<Options
closeFn={() => this.setState({ optionsOpen: !this.state.optionsOpen })}
/>
) : null
}
{
// Game downloads menu
this.state.gameDownloadsOpen ? (
<Game
downloadManager={downloadHandler}
closeFn={() => this.setState({ gameDownloadsOpen: false })}
/>
) : null
}
<div className="BottomSection">
<ServerLaunchSection />
<div id="DownloadProgress"
onClick={() => this.setState({ miniDownloadsOpen: !this.state.miniDownloadsOpen })}
>
{ this.state.isDownloading ?
<MainProgressBar downloadManager={downloadHandler} />
: null }
</div>
</div>
>
{(() => {
switch (this.state.page) {
case 'modding':
return <Mods downloadHandler={downloadHandler} />
default:
return <Main downloadHandler={downloadHandler} />
}
})()}
</div>
)
}

View File

@@ -3,8 +3,8 @@ import './App.css'
import TopBar from './components/TopBar'
import {invoke} from '@tauri-apps/api/tauri'
import {dataDir} from '@tauri-apps/api/path'
import { invoke } from '@tauri-apps/api/tauri'
import { dataDir } from '@tauri-apps/api/path'
import TextInput from './components/common/TextInput'
let proxyAddress = ''
@@ -15,7 +15,7 @@ async function setProxyAddress(address: string) {
}
async function startProxy() {
await invoke('connect', { port: 2222, certificatePath: await dataDir() + '\\cultivation\\ca' })
await invoke('connect', { port: 2222, certificatePath: (await dataDir()) + '\\cultivation\\ca' })
await invoke('open_in_browser', { url: 'https://hoyoverse.com' })
}
@@ -24,27 +24,23 @@ async function stopProxy() {
}
async function generateCertificates() {
await invoke('generate_ca_files', { path: await dataDir() + '\\cultivation' })
await invoke('generate_ca_files', { path: (await dataDir()) + '\\cultivation' })
}
async function generateInfo() {
console.log({
certificatePath: await dataDir() + '\\cultivation\\ca',
certificatePath: (await dataDir()) + '\\cultivation\\ca',
isAdmin: await invoke('is_elevated'),
connectingTo: proxyAddress
connectingTo: proxyAddress,
})
alert('check your dev console and send that in #cultivation')
}
function none() {
alert('none')
}
class Debug extends React.Component<any, any>{
class Debug extends React.Component {
render() {
return (
<div className="App">
<TopBar optFunc={none} downFunc={none} gameFunc={none} />
<TopBar />
<TextInput readOnly={false} initalValue={'change to set proxy address'} onChange={setProxyAddress} />
<button onClick={startProxy}>start proxy</button>
<button onClick={stopProxy}>stop proxy</button>
@@ -55,4 +51,4 @@ class Debug extends React.Component<any, any>{
}
}
export default Debug
export default Debug

268
src/ui/Main.tsx Normal file
View File

@@ -0,0 +1,268 @@
import React from 'react'
// Major Components
import TopBar from './components/TopBar'
import ServerLaunchSection from './components/ServerLaunchSection'
import MainProgressBar from './components/common/MainProgressBar'
import Options from './components/menu/Options'
import MiniDialog from './components/MiniDialog'
import DownloadList from './components/common/DownloadList'
import Downloads from './components/menu/Downloads'
import NewsSection from './components/news/NewsSection'
import Game from './components/menu/Game'
import RightBar from './components/RightBar'
import { getConfigOption, setConfigOption } from '../utils/configuration'
import { invoke } from '@tauri-apps/api'
import { listen } from '@tauri-apps/api/event'
import { dataDir } from '@tauri-apps/api/path'
import { appWindow } from '@tauri-apps/api/window'
import { unpatchGame } from '../utils/rsa'
import DownloadHandler from '../utils/download'
// Graphics
import cogBtn from '../resources/icons/cog.svg'
import downBtn from '../resources/icons/download.svg'
import wrenchBtn from '../resources/icons/wrench.svg'
import { ExtrasMenu } from './components/menu/ExtrasMenu'
interface IProps {
downloadHandler: DownloadHandler
}
interface IState {
isDownloading: boolean
optionsOpen: boolean
miniDownloadsOpen: boolean
downloadsOpen: boolean
gameDownloadsOpen: boolean
extrasOpen: boolean
migotoSet: boolean
playGame: (exe?: string, proc_name?: string) => void
}
export class Main extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props)
this.state = {
isDownloading: false,
optionsOpen: false,
miniDownloadsOpen: false,
downloadsOpen: false,
gameDownloadsOpen: false,
extrasOpen: false,
migotoSet: false,
playGame: () => {
alert('Error launching game')
},
}
listen('lang_error', (payload) => {
console.log(payload)
})
listen('jar_extracted', ({ payload }: { payload: string }) => {
setConfigOption('grasscutter_path', payload)
})
listen('migoto_extracted', ({ payload }: { payload: string }) => {
setConfigOption('migoto_path', payload)
invoke('set_migoto_target', {
migotoPath: payload,
})
})
// Emitted for rsa replacing-purposes
listen('game_closed', async () => {
const wasPatched = await getConfigOption('patch_rsa')
if (wasPatched) {
const unpatched = await unpatchGame()
if (unpatched) {
alert(`Could not unpatch game! (Delete version.dll in your game folder)`)
}
}
})
listen('migoto_set', async () => {
this.setState({
migotoSet: !!(await getConfigOption('migoto_path')),
})
window.location.reload()
})
// Emitted for automatic processes
listen('grasscutter_closed', async () => {
const autoService = await getConfigOption('auto_mongodb')
if (autoService) {
await invoke('stop_service', { service: 'MongoDB' })
}
})
let min = false
// periodically check if we need to min/max based on whether the game is open
setInterval(async () => {
const gameOpen = await invoke('is_game_running')
if (gameOpen && !min) {
appWindow.minimize()
min = true
} else if (!gameOpen && min) {
appWindow.unminimize()
min = false
}
}, 1000)
this.openExtrasMenu = this.openExtrasMenu.bind(this)
}
async componentDidMount() {
const cert_generated = await getConfigOption('cert_generated')
this.setState({
migotoSet: !!(await getConfigOption('migoto_path')),
})
if (!cert_generated) {
// Generate the certificate
await invoke('generate_ca_files', {
path: (await dataDir()) + 'cultivation',
})
await setConfigOption('cert_generated', true)
}
// Ensure old configs are updated to use RSA
const updatedConfig = await getConfigOption('patch_rsa')
await setConfigOption('patch_rsa', updatedConfig)
// Period check to only show progress bar when downloading files
setInterval(() => {
this.setState({
isDownloading: this.props.downloadHandler.getDownloads().filter((d) => d.status !== 'finished')?.length > 0,
})
}, 1000)
}
async openExtrasMenu(playGame: () => void) {
this.setState({
extrasOpen: true,
playGame,
})
}
render() {
return (
<>
<TopBar>
<div
id="settingsBtn"
onClick={() => this.setState({ optionsOpen: !this.state.optionsOpen })}
className="TopButton"
>
<img src={cogBtn} alt="settings" />
</div>
<div
id="downloadsBtn"
className="TopButton"
onClick={() => this.setState({ downloadsOpen: !this.state.downloadsOpen })}
>
<img src={downBtn} alt="downloads" />
</div>
{this.state.migotoSet && (
<div
id="modsBtn"
onClick={() => {
// Create and dispatch a custom "openMods" event
const event = new CustomEvent('changePage', { detail: 'modding' })
window.dispatchEvent(event)
}}
className="TopButton"
>
<img src={wrenchBtn} alt="mods" />
</div>
)}
{/* <div id="gameBtn" className="TopButton" onClick={() => this.setState({ gameDownloadsOpen: !this.state.gameDownloadsOpen })}>
<img src={gameBtn} alt="game" />
</div> */}
</TopBar>
<RightBar />
<NewsSection />
{
// Extras section
this.state.extrasOpen && (
<ExtrasMenu closeFn={() => this.setState({ extrasOpen: false })} playGame={this.state.playGame}>
Yo
</ExtrasMenu>
)
}
{
// Mini downloads section
this.state.miniDownloadsOpen ? (
<div className="MiniDownloads" id="miniDownloadContainer">
<MiniDialog
title="Downloads"
closeFn={() => {
this.setState({ miniDownloadsOpen: false })
}}
>
<DownloadList downloadManager={this.props.downloadHandler} />
</MiniDialog>
<div className="arrow-down"></div>
</div>
) : null
}
{
// Download menu
this.state.downloadsOpen ? (
<Downloads
downloadManager={this.props.downloadHandler}
closeFn={() => this.setState({ downloadsOpen: false })}
/>
) : null
}
{
// Options menu
this.state.optionsOpen ? (
<Options
downloadManager={this.props.downloadHandler}
closeFn={() => this.setState({ optionsOpen: !this.state.optionsOpen })}
/>
) : null
}
{
// Game downloads menu
this.state.gameDownloadsOpen ? (
<Game
downloadManager={this.props.downloadHandler}
closeFn={() => this.setState({ gameDownloadsOpen: false })}
/>
) : null
}
<div className="BottomSection" id="bottomSectionContainer">
<ServerLaunchSection openExtras={this.openExtrasMenu} />
<div
id="DownloadProgress"
onClick={() => this.setState({ miniDownloadsOpen: !this.state.miniDownloadsOpen })}
>
{this.state.isDownloading ? <MainProgressBar downloadManager={this.props.downloadHandler} /> : null}
</div>
</div>
</>
)
}
}

41
src/ui/Mods.css Normal file
View File

@@ -0,0 +1,41 @@
.Mods {
backdrop-filter: blur(10px);
height: 90%;
width: 100%;
}
/* Stuff for the top bar progress bar */
.TopDownloads {
position: absolute;
top: 10px;
left: 35.5%;
width: 30%;
}
.TopDownloads .ProgressBar {
width: 100%;
height: 10px;
}
.ModMenu {
width: 40%;
}
.ModMenu .BigButton {
font-size: 16px;
padding: 6px 30px;
}
.ModDownloadList {
width: 80%;
}
.ModDownloadItem {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin: 10px;
}

169
src/ui/Mods.tsx Normal file
View File

@@ -0,0 +1,169 @@
import { invoke } from '@tauri-apps/api'
import React from 'react'
import DownloadHandler from '../utils/download'
import { getModDownload, ModData } from '../utils/gamebanana'
import { getModsFolder } from '../utils/mods'
import { unzip } from '../utils/zipUtils'
import ProgressBar from './components/common/MainProgressBar'
import { ModHeader } from './components/mods/ModHeader'
import { ModList } from './components/mods/ModList'
import TopBar from './components/TopBar'
import './Mods.css'
import Back from '../resources/icons/back.svg'
import Menu from './components/menu/Menu'
import BigButton from './components/common/BigButton'
import Tr from '../utils/language'
interface IProps {
downloadHandler: DownloadHandler
}
interface IState {
isDownloading: boolean
category: string
downloadList: { name: string; url: string; mod: ModData }[] | null
}
const headers = [
{
name: 'ripe',
title: 'Hot',
},
{
name: 'new',
title: 'New',
},
{
name: 'installed',
title: 'Installed',
},
]
/**
* Mods currently install into folder labelled with their GB ID
*
* @TODO Categorizaiton/sorting (by likes, views, etc)
*/
export class Mods extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props)
this.state = {
isDownloading: false,
category: '',
downloadList: null,
}
this.setCategory = this.setCategory.bind(this)
this.addDownload = this.addDownload.bind(this)
}
async addDownload(mod: ModData) {
const dlLinks = await getModDownload(String(mod.id))
// Not gonna bother allowing sorting for now
const firstLink = dlLinks[0].downloadUrl
const fileExt = firstLink.split('.').pop()
const modName = `${mod.id}.${fileExt}`
if (dlLinks.length === 0) return
// If there is one download we don't care to choose
if (dlLinks.length === 1) {
this.downloadMod(firstLink, modName, mod)
return
}
this.setState({
downloadList: dlLinks.map((link) => ({
name: link.filename,
url: link.downloadUrl,
mod: mod,
})),
})
}
async downloadMod(link: string, modName: string, mod: ModData) {
const modFolder = await getModsFolder()
const path = `${modFolder}/${modName}`
if (!modFolder) return
this.props.downloadHandler.addDownload(link, path, async () => {
const unzipRes = await unzip(path, modFolder, false, true)
// Write a modinfo.json file
invoke('write_file', {
path: `${unzipRes.new_folder}/modinfo.json`,
contents: JSON.stringify(mod),
})
})
}
async setCategory(value: string) {
this.setState(
{
category: value,
},
this.forceUpdate
)
}
render() {
return (
<div className="Mods">
<TopBar>
<div
id="backbtn"
className="TopButton"
onClick={() => {
// Create and dispatch a custom "changePage" event
const event = new CustomEvent('changePage', { detail: 'main' })
window.dispatchEvent(event)
}}
>
<img src={Back} alt="back" />
</div>
</TopBar>
{this.state.downloadList && (
<Menu className="ModMenu" heading="Links" closeFn={() => this.setState({ downloadList: null })}>
<div className="ModDownloadList">
{this.state.downloadList.map((o) => {
return (
<div className="ModDownloadItem" key={o.name}>
<div className="ModDownloadName">{o.name}</div>
<BigButton
id={o.url}
onClick={() => {
const fileExt = o.url.split('.').pop()
const modName = `${o.mod.id}.${fileExt}`
this.downloadMod(o.url, modName, o.mod)
this.setState({
downloadList: null,
})
}}
>
<Tr text="components.download" />
</BigButton>
</div>
)
})}
</div>
</Menu>
)}
<div className="TopDownloads">
<ProgressBar downloadManager={this.props.downloadHandler} withStats={false} />
</div>
<ModHeader onChange={this.setCategory} headers={headers} defaultHeader={'ripe'} />
<ModList key={this.state.category} mode={this.state.category} addDownload={this.addDownload} />
</div>
)
}
}

View File

@@ -1,7 +1,7 @@
.MiniDialog {
position: fixed;
z-index: 99;
/* Len and width */
height: 30%;
width: 30%;
@@ -32,4 +32,4 @@
.MiniDialog .ProgressText {
color: #000;
}
}

View File

@@ -4,10 +4,10 @@ import Close from '../../resources/icons/close.svg'
import './MiniDialog.css'
interface IProps {
children: React.ReactNode[] | React.ReactNode;
title?: string;
closeable?: boolean;
closeFn: () => void;
children: React.ReactNode[] | React.ReactNode
title?: string
closeable?: boolean
closeFn: () => void
}
export default class MiniDialog extends React.Component<IProps, never> {
@@ -19,7 +19,7 @@ export default class MiniDialog extends React.Component<IProps, never> {
document.addEventListener('mousedown', (evt) => {
const tgt = evt.target as HTMLElement
const isInside = tgt.closest('.MiniDialog') !== null
if (!isInside) {
this.props.closeFn()
}
@@ -32,19 +32,18 @@ export default class MiniDialog extends React.Component<IProps, never> {
render() {
return (
<div className="MiniDialog">
{
this.props.closeable !== undefined && this.props.closeable ?
<div className="MiniDialogTop" onClick={this.props.closeFn}>
<span>{this.props?.title}</span>
<img src={Close} className="MiniDialogClose" />
</div> : null
}
<div className="MiniDialog" id="miniDialogContainer">
{this.props.closeable !== undefined && this.props.closeable ? (
<div className="MiniDialogTop" id="miniDialogContainerTop" onClick={this.props.closeFn}>
<span>{this.props?.title}</span>
<img src={Close} className="MiniDialogClose" id="miniDialogButtonClose" />
</div>
) : null}
<div className="MiniDialogInner">
<div className="MiniDialogInner" id="miniDialogContent">
{this.props.children}
</div>
</div>
)
}
}
}

View File

@@ -2,7 +2,7 @@
position: absolute;
transform: translate(0%, 0%);
display:flex;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
@@ -36,14 +36,14 @@
filter: invert(75%) sepia(0%) saturate(100%) hue-rotate(0deg) brightness(100%) contrast(100%);
}
@media(max-height: 580px) {
.RightBar {
height: calc(100vh - 180px);
}
@media (max-height: 580px) {
.RightBar {
height: calc(100vh - 180px);
}
}
@media(max-height: 500px) {
.RightBar {
height: calc(100vh - 170px);
}
}
@media (max-height: 500px) {
.RightBar {
height: calc(100vh - 170px);
}
}

View File

@@ -1,8 +1,8 @@
import { invoke } from '@tauri-apps/api'
import React from 'react'
import Discord from '../../resources/icons/discord.svg'
import Github from '../../resources/icons/github.svg'
import Discord from '../../resources/icons/discord.svg'
import Github from '../../resources/icons/github.svg'
import './RightBar.css'
@@ -16,16 +16,16 @@ export default class RightBar extends React.Component {
render() {
return (
<div className="RightBar">
<div className="RightBarInner">
<div className="BarDiscord BarImg" onClick={() => this.openInBrowser(DISCORD)}>
<div className="RightBar" id="rightBarContainer">
<div className="RightBarInner" id="rightBarContent">
<div className="BarDiscord BarImg" id="rightBarButtonDiscord" onClick={() => this.openInBrowser(DISCORD)}>
<img src={Discord} />
</div>
<div className="BarGithub BarImg" onClick={() => this.openInBrowser(GITHUB)}>
<div className="BarGithub BarImg" id="rightBarButtonGithub" onClick={() => this.openInBrowser(GITHUB)}>
<img src={Github} />
</div>
</div>
</div>
)
}
}
}

View File

@@ -62,20 +62,32 @@
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
align-items: flex-start;
height: 100%;
max-height: 60px;
}
#officialPlay {
width: 60%
.ServerLaunchButtons .BigButton {
margin-right: 8px;
}
#officialPlay {
max-width: 60%;
flex-grow: 1;
}
#akebiLaunch,
#serverLaunch {
width: 5%;
}
#ExtrasMenuButton {
width: 5%;
padding: 0 20px;
}
.ExtrasIcon,
.ServerIcon {
height: 20px;
filter: invert(28%) sepia(28%) saturate(1141%) hue-rotate(352deg) brightness(96%) contrast(88%);
@@ -102,17 +114,17 @@
}
@media (max-width: 1040px) {
#playButton {
right: 5%;
}
#playButton {
right: 5%;
}
}
@media (max-width: 870px) {
#playButton {
min-width: 235px;
}
#playButton {
min-width: 235px;
}
#officialPlay {
width: 40%;
}
#officialPlay {
width: 40%;
}
}

View File

@@ -8,27 +8,37 @@ import { translate } from '../../utils/language'
import { invoke } from '@tauri-apps/api/tauri'
import Server from '../../resources/icons/server.svg'
import Plus from '../../resources/icons/plus.svg'
import './ServerLaunchSection.css'
import {dataDir} from '@tauri-apps/api/path'
import { dataDir } from '@tauri-apps/api/path'
import { getGameExecutable, getGameVersion, getGrasscutterJar } from '../../utils/game'
import { patchGame, unpatchGame } from '../../utils/rsa'
import { listen } from '@tauri-apps/api/event'
interface IProps {
[key: string]: any
openExtras: (playGame: () => void) => void
}
interface IState {
grasscutterEnabled: boolean;
buttonLabel: string;
checkboxLabel: string;
ip: string;
port: string;
grasscutterEnabled: boolean
buttonLabel: string
checkboxLabel: string
ip: string
port: string
launchServer: (proc_name?: string) => void
ipPlaceholder: string;
portPlaceholder: string;
ipPlaceholder: string
portPlaceholder: string
portHelpText: string;
portHelpText: string
httpsLabel: string;
httpsEnabled: boolean;
httpsLabel: string
httpsEnabled: boolean
swag: boolean
akebiSet: boolean
migotoSet: boolean
}
export default class ServerLaunchSection extends React.Component<IProps, IState> {
@@ -45,7 +55,13 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
portPlaceholder: '',
portHelpText: '',
httpsLabel: '',
httpsEnabled: false
httpsEnabled: false,
launchServer: () => {
alert('Error launching grasscutter')
},
swag: false,
akebiSet: false,
migotoSet: false,
}
this.toggleGrasscutter = this.toggleGrasscutter.bind(this)
@@ -53,6 +69,11 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
this.setIp = this.setIp.bind(this)
this.setPort = this.setPort.bind(this)
this.toggleHttps = this.toggleHttps.bind(this)
this.launchServer = this.launchServer.bind(this)
listen('start_grasscutter', async () => {
this.launchServer()
})
}
async componentDidMount() {
@@ -69,6 +90,9 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
portHelpText: await translate('help.port_help_text'),
httpsLabel: await translate('main.https_enable'),
httpsEnabled: config.https_enabled || false,
swag: config.swag_mode || false,
akebiSet: config.akebi_path !== '',
migotoSet: config.migoto_path !== '',
})
}
@@ -79,72 +103,113 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
// Set state as well
this.setState({
grasscutterEnabled: config.toggle_grasscutter
grasscutterEnabled: config.toggle_grasscutter,
})
await saveConfig(config)
}
async playGame() {
async playGame(exe?: string, proc_name?: string) {
const config = await getConfig()
if (!config.game_install_path) return alert('Game path not set!')
if (!(await getGameExecutable())) {
alert('Game executable not set!')
return
}
// Connect to proxy
if (config.toggle_grasscutter) {
let game_exe = config.game_install_path
if (config.patch_rsa) {
const gameVersion = await getGameVersion()
console.log(gameVersion)
if (game_exe.includes('\\')) {
game_exe = game_exe.substring(config.game_install_path.lastIndexOf('\\') + 1)
} else {
game_exe = game_exe.substring(config.game_install_path.lastIndexOf('/') + 1)
if (gameVersion == null) {
alert(
'Game version could not be determined. Please make sure you have the game correctly selected and try again.'
)
return
}
if (gameVersion?.major == 2 && gameVersion?.minor < 9) {
alert('Game version is too old for RSA patching. Please disable RSA patching in the settings and try again.')
return
}
if (gameVersion?.major == 3 && gameVersion?.minor < 1) {
alert('Game version is too old for RSA patching. Please disable RSA patching in the settings and try again.')
return
}
const patched = await patchGame()
if (!patched) {
alert('Could not patch! Try launching again, or patching manually.')
return
}
}
const game_exe = await getGameExecutable()
// Save last connected server and port
await setConfigOption('last_ip', this.state.ip)
await setConfigOption('last_port', this.state.port)
// Set IP
await invoke('set_proxy_addr', { addr: (this.state.httpsEnabled ? 'https':'http') + '://' + this.state.ip + ':' + this.state.port })
await invoke('enable_process_watcher', {
process: game_exe
process: proc_name || game_exe,
})
// Connect to proxy
await invoke('connect', { port: 8365, certificatePath: await dataDir() + '\\cultivation\\ca' })
if (config.use_internal_proxy) {
// Set IP
await invoke('set_proxy_addr', {
addr: (this.state.httpsEnabled ? 'https' : 'http') + '://' + this.state.ip + ':' + this.state.port,
})
// Connect to proxy
await invoke('connect', { port: 8365, certificatePath: (await dataDir()) + '\\cultivation\\ca' })
}
// Open server as well if the options are set
if (config.grasscutter_with_game) {
let jarFolder = config.grasscutter_path
if (jarFolder.includes('/')) {
jarFolder = jarFolder.substring(0, config.grasscutter_path.lastIndexOf('/'))
} else {
jarFolder = jarFolder.substring(0, config.grasscutter_path.lastIndexOf('\\'))
}
await invoke('run_jar', {
path: config.grasscutter_path,
executeIn: jarFolder,
javaPath: config.java_path || ''
})
this.launchServer()
}
} else {
await unpatchGame()
}
if (config.wipe_login) {
// First wipe registry if we have to
await invoke('wipe_registry', {
// The exe is always PascalCase so we can get the dir using regex
execName: (await getGameExecutable())?.split('.exe')[0].replace(/([a-z\d])([A-Z])/g, '$1 $2'),
})
}
// Launch the program
const gameExists = await invoke('dir_exists', {
path: config.game_install_path
path: exe || config.game_install_path,
})
if (gameExists) await invoke('run_program', { path: config.game_install_path })
else alert('Game not found! At: ' + config.game_install_path)
if (gameExists) await invoke('run_program_relative', { path: exe || config.game_install_path })
else alert('Game not found! At: ' + (exe || config.game_install_path))
}
async launchServer() {
async launchServer(proc_name?: string) {
if (await invoke('is_grasscutter_running')) {
alert('Grasscutter already running!')
return
}
const config = await getConfig()
if (!config.grasscutter_path) return alert('Grasscutter not installed or set!')
if (config.auto_mongodb) {
const grasscutter_jar = await getGrasscutterJar()
await invoke('enable_grasscutter_watcher', {
process: proc_name || grasscutter_jar,
})
// Check if MongoDB is running and start it if not
await invoke('service_status', { service: 'MongoDB' })
}
let jarFolder = config.grasscutter_path
if (jarFolder.includes('/')) {
@@ -157,19 +222,19 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
await invoke('run_jar', {
path: config.grasscutter_path,
executeIn: jarFolder,
javaPath: config.java_path || ''
javaPath: config.java_path || '',
})
}
setIp(text: string) {
this.setState({
ip: text
ip: text,
})
}
setPort(text: string) {
this.setState({
port: text
port: text,
})
}
@@ -180,7 +245,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
// Set state as well
this.setState({
httpsEnabled: config.https_enabled
httpsEnabled: config.https_enabled,
})
await saveConfig(config)
@@ -190,33 +255,59 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
return (
<div id="playButton">
<div id="serverControls">
<Checkbox id="enableGC" label={this.state.checkboxLabel} onChange={this.toggleGrasscutter} checked={this.state.grasscutterEnabled}/>
<Checkbox
id="enableGC"
label={this.state.checkboxLabel}
onChange={this.toggleGrasscutter}
checked={this.state.grasscutterEnabled}
/>
</div>
{
this.state.grasscutterEnabled && (
<div>
<div className="ServerConfig">
<TextInput id="ip" key="ip" placeholder={this.state.ipPlaceholder} onChange={this.setIp} initalValue={this.state.ip} />
<TextInput style={{
{this.state.grasscutterEnabled && (
<div>
<div className="ServerConfig" id="serverConfigContainer">
<TextInput
id="ip"
key="ip"
placeholder={this.state.ipPlaceholder}
onChange={this.setIp}
initalValue={this.state.ip}
/>
<TextInput
style={{
width: '10%',
}} id="port" key="port" placeholder={this.state.portPlaceholder} onChange={this.setPort} initalValue={this.state.port} />
<HelpButton contents={this.state.portHelpText} />
<Checkbox id="httpsEnable" label={this.state.httpsLabel} onChange={this.toggleHttps} checked={this.state.httpsEnabled} />
</div>
}}
id="port"
key="port"
placeholder={this.state.portPlaceholder}
onChange={this.setPort}
initalValue={this.state.port}
/>
<HelpButton contents={this.state.portHelpText} />
<Checkbox
id="httpsEnable"
label={this.state.httpsLabel}
onChange={this.toggleHttps}
checked={this.state.httpsEnabled}
/>
</div>
</div>
)}
)
}
<div className="ServerLaunchButtons">
<BigButton onClick={this.playGame} id="officialPlay">{this.state.buttonLabel}</BigButton>
<div className="ServerLaunchButtons" id="serverLaunchContainer">
<BigButton onClick={this.playGame} id="officialPlay">
{this.state.buttonLabel}
</BigButton>
{this.state.swag && (
<BigButton onClick={() => this.props.openExtras(this.playGame)} id="ExtrasMenuButton">
<img className="ExtrasIcon" id="extrasIcon" src={Plus} />
</BigButton>
)}
<BigButton onClick={this.launchServer} id="serverLaunch">
<img className="ServerIcon" src={Server} />
<img className="ServerIcon" id="serverLaunchIcon" src={Server} />
</BigButton>
</div>
</div>
)
}
}
}

View File

@@ -25,4 +25,30 @@
#version {
margin: 0px 6px;
color: #434343;
}
}
#unassumingButton {
font-weight: bold;
margin: 0px 6px;
color: #141414;
transition: color 0.2s ease-in-out;
}
#unassumingButton:hover {
color: #434343;
}
#unassumingButton.spin {
color: #fff;
animation: spin 0.5s ease-in-out;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@@ -1,31 +1,34 @@
import React from 'react'
import { app } from '@tauri-apps/api'
import { appWindow } from '@tauri-apps/api/window'
import closeIcon from '../../resources/icons/close.svg'
import minIcon from '../../resources/icons/min.svg'
import cogBtn from '../../resources/icons/cog.svg'
import downBtn from '../../resources/icons/download.svg'
import gameBtn from '../../resources/icons/game.svg'
import { getConfig, setConfigOption } from '../../utils/configuration'
import Tr from '../../utils/language'
import './TopBar.css'
import closeIcon from '../../resources/icons/close.svg'
import minIcon from '../../resources/icons/min.svg'
interface IProps {
optFunc: () => void;
downFunc: () => void;
gameFunc: () => void;
children?: React.ReactNode | React.ReactNode[]
}
interface IState {
version: string;
version: string
clicks: number
intv: NodeJS.Timeout | null
}
export default class TopBar extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props)
this.state = { version: '0.0.0' }
this.state = {
version: '0.0.0',
clicks: 0,
intv: null,
}
this.activateClick = this.activateClick.bind(this)
}
async componentDidMount() {
@@ -41,33 +44,70 @@ export default class TopBar extends React.Component<IProps, IState> {
appWindow.minimize()
}
async activateClick() {
const config = await getConfig()
// They already got it, no need to reactivate
if (config.swag_mode) return
if (this.state.clicks === 2) {
setTimeout(() => {
// Gotta clear it so it goes back to regular colors
this.setState({
clicks: 0,
})
}, 600)
// Activate... SWAG MODE
await setConfigOption('swag_mode', true)
// Reload the window
window.location.reload()
return
}
if (this.state.clicks < 3) {
this.setState({
clicks: this.state.clicks + 1,
intv: setTimeout(() => this.setState({ clicks: 0 }), 1500),
})
return
}
}
render() {
return (
<div className="TopBar" data-tauri-drag-region>
<div className="TopBar" id="topBarContainer" data-tauri-drag-region>
<div id="title">
<span data-tauri-drag-region>
<Tr text="main.title" />
</span>
<span data-tauri-drag-region id="version">{this.state?.version}</span>
<span data-tauri-drag-region id="version">
{this.state?.version}
</span>
</div>
<div className="TopBtns">
<div id="closeBtn" onClick={this.handleClose} className='TopButton'>
{/**
* HEY YOU
*
* If you're looking at the source code to find the swag mode thing, that's okay! If you're not, move along...
* Just do me a favor and don't go telling everyone about how you found it. If you are just helping someone who
* for some reason needs it, that's fine, but not EVERYONE needs it, which is why it exists in the first place.
*/}
<div id="unassumingButton" className={this.state.clicks === 2 ? 'spin' : ''} onClick={this.activateClick}>
?
</div>
<div className="TopBtns" id="topBarButtonContainer">
<div id="closeBtn" onClick={this.handleClose} className="TopButton">
<img src={closeIcon} alt="close" />
</div>
<div id="minBtn" onClick={this.handleMinimize} className='TopButton'>
<div id="minBtn" onClick={this.handleMinimize} className="TopButton">
<img src={minIcon} alt="minimize" />
</div>
<div id="settingsBtn" onClick={this.props.optFunc} className='TopButton'>
<img src={cogBtn} alt="settings" />
</div>
<div id="downloadsBtn" className='TopButton' onClick={this.props.downFunc}>
<img src={downBtn} alt="downloads" />
</div>
{/* <div id="gameBtn" className="TopButton" onClick={this.props.gameFunc}>
<img src={gameBtn} alt="game" />
</div> */}
{this.props.children}
</div>
</div>
)
}
}
}

Some files were not shown because too many files have changed in this diff Show More