Compare commits

...

510 commits

Author SHA1 Message Date
Gravita
e1ee1099cc Merge branch 'hotfix/5.6.16' 2025-06-21 20:00:49 +07:00
Gravita
b0e840a040 [FIX] ModuleConf resolve mudule path, not class path 2025-06-21 19:59:11 +07:00
Gravita
c76aae76a5 Merge branch 'release/5.6.15' 2025-05-25 12:28:59 +07:00
Gravita
261e16cecb [ANY] 5.6.15 stable 2025-05-25 12:19:02 +07:00
Antoni
3c9e009433
[ANY] Rename Proguard Libraries for release 2025-05-18 21:43:14 +03:00
Gravita
2379fe5798 [FIX] Enter serverName and address in console 2025-05-14 20:30:49 +07:00
Gravita
911ca1e69f [FIX] Duplicate classpath and modulepath entities 2025-05-04 22:03:28 +07:00
Gravita
90f74aaf25 [FEATURE] Build ServerWrapper inline jar 2025-05-04 21:54:51 +07:00
Gravita
880957fa9b [FEATURE] Support inline initialization in DebugMain 2025-05-04 21:18:03 +07:00
Gravita
2fea94071b [FEATURE] Support runCompatClasses in ServerWrapperInlineInitializer 2025-05-04 21:12:57 +07:00
Gravita
b41e8db336 [FEATURE] Add ServerWrapperInlineInitializer 2025-05-04 19:38:34 +07:00
Gravita
76570ddbe5 [FEATURE] Support directories in classpath in ServerWrapper 2025-05-04 19:02:46 +07:00
Gravita
3a160b8124 Merge tag 'v5.6.14' into dev
5.6.14 stable
2025-05-04 17:58:08 +07:00
Gravita
8fb1dc275c Merge branch 'release/5.6.14' 2025-05-04 17:57:55 +07:00
Gravita
95aed151e7 [ANY] 5.6.14 2025-05-04 17:57:46 +07:00
Gravita
6934c37a33 [FIX] Wrong if in clientType 2025-05-04 05:35:35 +07:00
Gravita
5155c470c6 Merge tag 'v5.6.13' into dev
5.6.13 stable release
2025-05-03 23:59:13 +07:00
Gravita
533dcfce14 Merge branch 'release/5.6.13' 2025-05-03 23:58:58 +07:00
Gravita
d2f83d81eb [ANY] 5.6.13 stable 2025-05-03 23:58:41 +07:00
Gravita
2379398c30 [ANY] Update gradle to 8.14 2025-05-03 23:26:34 +07:00
Gravita
f53c48a5ae [ANY] Update gradle 2025-05-03 23:24:30 +07:00
Gravita
394b64e22d Merge tag 'v5.6.12' into dev
5.6.12 stable release
2025-05-03 23:11:55 +07:00
Gravita
7c15742478 Merge branch 'release/5.6.12' 2025-05-03 23:11:40 +07:00
Gravita
d65fffade9 [ANY] 5.6.12 release 2025-05-03 23:11:33 +07:00
Gravita
bb6c95ca12 [FEATURE] Support multiple lwjgl3 versions 2025-05-03 22:43:06 +07:00
Gravita
9027987b29 [FEATURE] Support multiple client types 2025-05-03 22:31:04 +07:00
Gravita
d410533c8d [FEATURE][EXPERIMENTAL] Add javaargs.txt and java24args.txt 2025-04-25 18:04:16 +07:00
Gravita
af2c5c33e8 [FEATURE][EXPERIMENTAL] Usage Java modular system for start, change proguard libraries 2025-04-25 17:54:00 +07:00
Gravita
e67bb6a12f [FEATURE] Support /webapi/status endpoint for Docker 2025-04-25 14:58:02 +07:00
Gravita
e2e0ef6ea4 [FEATURE] Support enableNativeAccess, fix libraries modularity 2025-04-25 14:49:51 +07:00
Gravita
18419fcd3a [ANY] Update dependencies 2025-04-20 14:09:12 +07:00
Gravita
73804a555e Merge tag 'v5.6.11' into dev
5.6.11 stable
2025-04-05 10:03:51 +07:00
Gravita
b16281e04a Merge branch 'release/5.6.11' 2025-04-05 10:03:36 +07:00
Gravita
06f0bc873a [ANY] 5.6.11 stable 2025-04-05 10:00:34 +07:00
Gravita
834fbab12b [FIX] Java 24 dont support SecurityManager 2025-04-03 15:29:53 +07:00
Gravita
822872992f [FIX] Component proguard commands 2025-03-28 11:42:24 +07:00
Gravita
ac43034d45 [ANY] Update dependencies 2025-03-28 11:41:16 +07:00
Gravita
00446b40f0 [FIX] DiscordGameSDK library link 2025-03-19 18:16:45 +07:00
Gravita
9e29053afa [FEATURE] Setup support env configuration in ServerWrapper, fix compile 2025-02-24 19:47:41 +07:00
Gravita
2e93102106 [FEATURE] Add serverName to env configuration in ServerWrapper 2025-02-24 19:21:10 +07:00
Gravita
d4b69195b3 [FEATURE] Support env configuration in ServerWrapper 2025-02-24 19:18:49 +07:00
Gravita
a67fbac8bc [ANY] 5.6.11-dev 2025-02-24 15:19:34 +07:00
Gravita
a7abb9cbfc [ANY] IDEA code inspect 2025-02-24 15:19:09 +07:00
Gravita
689478ee0f Merge tag 'v5.6.10' into dev
5.6.10 stable
2025-02-24 14:35:09 +07:00
Gravita
df77a1ebd6 Merge branch 'release/5.6.10' 2025-02-24 14:34:51 +07:00
Gravita
641796b44e [ANY] 5.6.10 stable 2025-02-24 14:34:44 +07:00
Gravita
b7ed56b27e [ANY] Update Hikari 2025-02-24 14:30:41 +07:00
Gravita
f119bd4b30 [ANY] Delete not working modules 2025-02-19 11:51:51 +07:00
Gravita
bbff0eac64 [FEATURE] Support IO_URING mode in netty 2025-02-18 15:14:55 +07:00
Gravita
41f93b9f8d [ANY] Upgrade netty to 4.2.0.RC3 2025-02-18 14:56:16 +07:00
Gravita
263cf26258
[ANY] Update gradle 2025-02-03 01:03:26 +07:00
Gravita
b9ad7c0f26
[ANY] Update dependencies 2025-02-03 01:03:01 +07:00
Gravita
5ba32e3405
[ANY] Update modules 2025-01-27 20:20:47 +07:00
Gravita
907332ff06
[FEATURE] Fix truststore path 2025-01-27 20:20:44 +07:00
microwin7
1d63fbbd93 [FEATURE] Disabling log4j.xml update while launchserver is running 2025-01-07 11:44:55 +03:00
Gravita
2f4667f5a6
Merge tag 'v5.6.9' into dev
5.6.9 stable release
2025-01-03 12:55:53 +07:00
Gravita
92ada65079
Merge branch 'release/5.6.9' 2025-01-03 12:55:42 +07:00
Gravita
29d98defff
[ANY] 5.6.9 stable 2025-01-03 12:55:33 +07:00
Gravita
d7a379383b
[FIX] Remove socket command debug 2024-12-31 14:35:42 +07:00
Gravita
0111b2ca2b
[FEATURE] Add ControlFile and proxy command handlers 2024-12-31 14:19:31 +07:00
microwin7
4671dfe49d [ANY] Added proxy repository jitpack. Sync moduls 2024-12-31 04:18:40 +03:00
Gravita
bb63aaa0ab
[ANY] Update modules 2024-12-13 22:40:23 +07:00
Gravita
912caa6b8a
[FIX] Add more logging to timestamp failed error 2024-11-05 08:52:59 +07:00
Gravita
926094076c
[FIX] Fix NPE with nativePath PR #719 2024-11-05 07:08:31 +07:00
Gravita
a1af61a599
[ANY] Update modules 2024-10-22 17:14:36 +07:00
Gravita
81b16cb54e
[ANY] Update gradle 2024-10-15 18:53:03 +07:00
Gravita
a486f21fa2
[ANY] Update dependencies 2024-10-15 18:48:20 +07:00
Gravita
86ea247f07
Merge tag 'v5.6.8' into dev
v5.6.8 stable
2024-10-13 19:19:00 +07:00
Gravita
d26b179006
Merge branch 'release/5.6.8' 2024-10-13 19:18:50 +07:00
Gravita
c4d1251429
[ANY] 5.6.8 stable 2024-10-13 19:05:35 +07:00
Gravita
f16f5fbc6d
[FIX] NPE: User not found and permissions enabled 2024-10-06 22:59:49 +07:00
Gravita
abe904d73c
[FEATURE] ExtendedCheckServer support in SQLAuthCoreProvider 2024-10-02 20:50:36 +07:00
Gravita
eaf685897f
Merge tag 'v5.6.7' into dev
5.6.7 stable release
2024-09-28 20:31:33 +07:00
Gravita
c8934d887a
Merge branch 'release/5.6.7' 2024-09-28 20:31:23 +07:00
Gravita
070a5d9b69
[ANY] 5.6.7 release 2024-09-28 20:31:10 +07:00
Gravita
cc2bce4300
[FIX] initializeAtStart thread safety 2024-09-28 02:29:19 +07:00
Gravita
c7f4d8ac49
[FEATURE] Support initializeAtStart 2024-09-28 02:27:42 +07:00
Gravita
7dcb08fdaf
[FEATURE] Support HWID in SQLCoreProvider 2024-09-28 01:47:55 +07:00
Gravita
9d870849a1
[ANY] Update dependencies 2024-09-18 16:36:15 +07:00
microwin7
dda3ebc7b4 [FIX] RejectedExecutionException after cancelled Download task 2024-08-23 22:02:13 +03:00
Gravita
537623afaf
Merge tag 'v5.6.6' into dev
5.6.6 stable
2024-08-18 23:57:14 +07:00
Gravita
0cff6e247a
Merge branch 'release/5.6.6' 2024-08-18 23:57:08 +07:00
Gravita
b85075c559
[ANY] 5.6.6 Stable 2024-08-18 23:57:00 +07:00
Gravita
3e654f4d79
[FEATURE] Add support opens, exports, reads to SYSTEM_CLASS_LOADER. Remove AGENT 2024-08-18 23:10:58 +07:00
Gravita
46f1f7b69e
[FIX] Symlink in launcher-pack 2024-08-18 19:18:53 +07:00
microwin7
981f2ac3dd [ANY] Update modules and update version oshi 2024-08-18 01:00:08 +03:00
microwin7
8509cbb6b5 [ANY] Update modules 2024-08-14 00:13:45 +03:00
microwin7
1119625d12 [ANY] Update modules 2024-08-13 05:44:32 +03:00
microwin7
9a69426547 [ANY] Update modules 2024-08-12 07:03:17 +03:00
microwin7
31cbfe2919 [ANY][FEATURE] Update modules. Removed mainClass for Cleanroom
In MakeProfileHelper
2024-08-12 06:01:08 +03:00
microwin7
27ebadcd19 [ANY] Update modules 2024-08-12 02:00:49 +03:00
Antoni
b3349044b5
[ANY] Update patch Version and Type release 2024-08-07 01:27:18 +03:00
Antoni
c43edeb982
[FIX] UpdatesProvider register 2024-08-06 03:25:02 +03:00
Gravita
1ff58099bd
[FIX] UpdatesProvider register 2024-08-06 06:19:12 +07:00
Gravita
9fba637f83
[FIX] Launcher update token 2024-08-05 05:36:53 +07:00
Gravita
ca70ee78d1
[FIX] Launcher update 2024-08-05 05:34:45 +07:00
Gravita
5e116a81e5
[ANY] Update modules 2024-08-05 03:25:05 +07:00
Gravita
b0f799d194
[FIX] UpdatesProvider init 2024-08-05 03:24:42 +07:00
Gravita
63f9f8e21d
[FEATURE] UpdatesProvider Part 2 2024-07-31 00:36:04 +07:00
Gravita
b8ccbc5e48
[FEATURE] UpdatesProvider Part 1 2024-07-31 00:15:08 +07:00
Gravita
a687c5afd8
[ANY] Update gradle 2024-07-30 23:20:35 +07:00
Gravita
3969d81db7
[ANY] Update modules 2024-07-30 23:06:54 +07:00
Gravita
a30d0624a1
[ANY] Update modules 2024-07-30 21:05:37 +07:00
Gravita
d5abe0d411
[ANY] Update modules 2024-07-30 17:34:20 +07:00
Gravita
1e7a856a99
[FIX] Profile duplicate 2024-07-30 15:43:22 +07:00
Gravita
e6f5b585a7
[ANY] Update modules 2024-07-30 15:33:46 +07:00
Gravita
2ed4abf9b0
[FIX] Usage profilesProvider 2024-07-26 01:27:01 +07:00
Gravita
af2dcec8cd
[FIX] ProfileProvider support overwrite profile 2024-07-25 22:28:26 +07:00
Gravita
9bffe07d36
[FEATURE] ProfileProvider 2024-07-25 22:20:48 +07:00
Gravita
4be299f6ca
[FIX] Add ClassLoader.registerAsParallelCapable() 2024-07-25 16:43:00 +07:00
Gravita
ef4f14f9b4
[FIX] Add netty epoll lib for aarch64 (ARM) 2024-07-25 16:33:49 +07:00
Gravita
d720328bc4
[FEATURE] moduleConf in SYSTEM_ARGS 2024-07-25 15:52:39 +07:00
Gravita
88f1eaf750
[ANY] Update modules 2024-07-21 20:39:30 +07:00
Gravita
a5ef86b105
Merge tag 'v5.6.5' into dev
5.6.5-stable
2024-07-21 20:18:08 +07:00
Gravita
b1a5ecdc13
Merge branch 'release/5.6.5' 2024-07-21 20:18:00 +07:00
Gravita
68e9affbe0
[ANY] 5.6.5-stable 2024-07-21 20:17:51 +07:00
Gravita
7d7485afdc
[FIX] Downloader downloadList truncate file 2024-07-21 19:48:55 +07:00
Gravita
c2926b5b40
[ANY] Update modules 2024-07-20 19:20:12 +07:00
Gravita
9c82d76781
[FEATURE][REFACTOR] Refactoring GenerateCertificateModule 2024-07-20 19:19:41 +07:00
Gravita
450774de7e
Merge tag 'v5.6.4' into dev
5.6.4-stable hotfix
2024-07-18 20:35:57 +07:00
Gravita
f88c0308f8
Merge branch 'release/5.6.4' 2024-07-18 20:35:46 +07:00
Gravita
20f713be05
5.6.4-stable 2024-07-18 20:35:26 +07:00
Gravita
5bf92d9a00
[FIX] ProGuard librariesDir 2024-07-18 20:34:31 +07:00
Gravita
903c4d40c5
Merge tag 'v5.6.3' into dev
5.6.3-stable
2024-07-18 15:55:07 +07:00
Gravita
ede9ab2c85
Merge branch 'release/5.6.3' 2024-07-18 15:54:58 +07:00
Gravita
c8c83c0dba
[ANY] 5.6.3-stable 2024-07-18 15:54:45 +07:00
Gravita
1bd5d8854c
[FIX] ProGuard and launchserver.dir.libraries 2024-07-17 01:32:51 +07:00
Gravita
992d31c883
[FEATURE] Set netty bind port in first run 2024-07-16 22:05:13 +07:00
Gravita
accbbe6b13
[FEATURE] Support port in launchserver address in first run 2024-07-16 21:54:59 +07:00
Gravita
0894e0b9c3
[FIX] Sync profiles before updates 2024-07-16 17:11:12 +07:00
Gravita
925007015f
[ANY] Update modules 2024-07-09 20:57:52 +07:00
Gravita
ec526a343d
[FIX] OptionalView.fixDependencies throw out of range 2024-07-08 21:29:40 +07:00
Gravita
1bebd8de2c
[FIX] classLoaderConfig is null 2024-07-08 21:22:44 +07:00
Gravita
c7781b30be
[FIX] launcher-libraries-compile not found 2024-07-06 22:34:20 +07:00
Gravita
df9d05a49c
[FEATURE] Support -Dlaunchserver.dir.modules and -Dlaunchserver.dir.launcher-modules 2024-07-06 03:24:02 +07:00
Gravita
03d53d4a09
[FEATURE] Support -Dlaunchserver.dir.libraries 2024-07-06 02:59:36 +07:00
Gravita
c261496af8
[ANY] Update modules 2024-07-06 01:55:10 +07:00
Gravita
8c11ab0cbe
[FIX] Disable Sodium lwjgl check 2024-07-05 16:27:50 +07:00
Gravita
78b4f1e3aa
[FIX] Gson version 2024-07-05 15:55:54 +07:00
Gravita
bbc5f1722b
[ANY] Update modules 2024-07-05 15:30:03 +07:00
Gravita
51411c5838
[ANY] Update modules 2024-07-05 14:55:21 +07:00
Gravita
3b22b76278
[FIX] Cleanroom install 2024-07-05 14:55:10 +07:00
Gravita
ef9cbfe0da
[ANY] Update modules 2024-07-05 14:38:57 +07:00
Gravita
353b663e12
[FEATURE] Support command execute by args 2024-07-05 14:38:45 +07:00
Gravita
78be606029
[FIX] FabricLoader main class 2024-07-05 14:27:45 +07:00
Gravita
584acdb8c3
[ANY] Update dependencies 2024-07-05 14:15:22 +07:00
Gravita
7f6a645dd7 [FIX] Compile fix 2024-06-01 23:16:34 +07:00
luckybl0ck
c6930ded74
[FIX] Set Java distrubution in Actions workflow (#714) 2024-06-01 23:11:50 +07:00
Gravita
c2a6a408c4 [FIX] Hikari getConnection() deadlock when with using user_permissions table 2024-06-01 23:10:56 +07:00
luckybl0ck
01cd50840a
[ANY] Update actions versions (#713) 2024-06-01 22:21:35 +07:00
Gravita
bb4d5b99c6 [ANY] 5.6.3-dev 2024-06-01 00:13:00 +07:00
Gravita
ff2f647b50 Merge tag 'v5.6.2' into dev
5.6.2 stable
2024-05-31 23:53:32 +07:00
Gravita
79fc42e86a Merge branch 'release/5.6.2' 2024-05-31 23:53:22 +07:00
Gravita
5b8aa8cd5e [ANY] 5.6.2-stable 2024-05-31 23:45:01 +07:00
Gravita
a4e5ef9d01 [FIX] Bouncy Castle 2024-05-31 23:30:19 +07:00
Gravita
1d5044c24a [FIX] Bouncy Castle cert gen 2024-05-31 22:58:18 +07:00
Gravita
b84911d445 [ANY] Update modules 2024-05-30 22:46:28 +07:00
Gravita
6a173b9b1b [ANY] Update modules 2024-05-30 22:35:27 +07:00
Gravita
6a53891c6a [ANY] Update dependencies 2024-05-30 22:28:34 +07:00
XakepSDK
7c637e078d
[FIX] Fix OpenID access token validation (#712)
Co-authored-by: d3coder <admin@xakeps.dk>
2024-05-24 00:42:32 +07:00
Gravita
c9a81da60c [FIX] Profile generation 2024-04-26 02:01:50 +07:00
Gravita
6c0ead015b [FIX] NPE in new profiles 2024-04-26 01:49:09 +07:00
XakepSDK
b5457ee866
[FIX OpenID validate issuer and aud (#710)
Co-authored-by: d3coder <admin@xakeps.dk>
2024-04-25 22:05:53 +07:00
Gravita
52c9196dcc [FIX] SQLCoreProvider 2024-04-25 03:05:13 +07:00
Gravita
095a5aef8b [FEATURE] Add mariadb connector 2024-04-25 02:09:17 +07:00
Gravita
765f1a9d8f [FEATURE] SQLCoreProvider 2024-04-25 02:06:44 +07:00
Gravita
9bd65c797b [REFACTOR] ClientProfileBuilder 2024-04-25 01:31:46 +07:00
XakepSDK
8908710ad6
[ANY] Add OpenID auth module (#709)
Co-authored-by: d3coder <admin@xakeps.dk>
2024-04-24 20:21:16 +07:00
Gravita
748612783c Merge tag 'vv5.6.1' into dev
5.6.1 stable
2024-04-21 13:42:04 +07:00
Gravita
42cf9bc79e Merge branch 'release/v5.6.1' 2024-04-21 13:41:55 +07:00
Gravita
8a81989d65 [ANY] 5.6.1 stable 2024-04-21 13:41:41 +07:00
Gravita
58fd3a7e8b [FIX] Add 'Eclipse Adoptium' to java search directory 2024-04-14 21:06:51 +07:00
Gravita
cc6ed82afb [FIX] Forge 1.7.10 lwjgl3ify classpath order 2024-04-14 20:53:09 +07:00
Gravita
00ab20473c [FIX] Strict profile access, update gradle 2024-04-10 16:49:05 +07:00
Gravita
cdb54b34de [ANY] Update modules 2024-04-03 22:13:03 +07:00
XakepSDK
1ebe68f5b8
[ANY] Add query helper (#708)
* [ANY] Add query helper

* [ANY] Rename AuthCodePassword field to the uri

---------

Co-authored-by: d3coder <admin@xakeps.dk>
2024-04-02 00:09:38 +07:00
Gravita
b719255bd5 [ANY] 5.6.1 dev 2024-03-27 23:37:55 +07:00
Gravita
6ecf716fca [FIX] ProGuard 2024-03-27 23:37:08 +07:00
Gravita
873100cf0a Merge tag 'v5.6.0' into dev
5.6.0 stable
2024-03-24 22:13:25 +07:00
Gravita
a8b165f081 Merge branch 'release/5.6.0' 2024-03-24 22:13:15 +07:00
Gravita
06ada30459 [ANY] 5.6.0 stable 2024-03-24 22:10:25 +07:00
Gravita
6017a89e20 [ANY] 5.6.0 beta 2024-03-24 16:11:32 +07:00
Gravita
bd677c26ba [FIX] Oshi requires slf4j api 2024-03-23 14:09:47 +07:00
Gravita
e5840243b3 [ANY] Update gradle 2024-03-22 13:40:46 +07:00
Gravita
09d36e066a [ANY] Update modules 2024-03-21 19:41:00 +07:00
Gravita
038af764a1 [REFACTOR] IDEA Code Inspect 2024-03-21 19:40:45 +07:00
Gravita
f7decac23d [FIX] Jwt library remove deprecated 2024-03-20 12:40:24 +07:00
zznty
1710eb7bec
[FIX] Fix resolution of files with special characters for embedded file server (#705) 2024-03-16 21:50:47 +07:00
XakepSDK
3926f3e5bf
[ANY] Update JWT library (#706)
Co-authored-by: d3coder <admin@xakeps.dk>
2024-03-14 18:07:44 +07:00
microwin7
7759ea9182 Revert "Merge pull request #703 from zznty/dev"
This reverts commit 3e6af5afd3, reversing
changes made to 730efae7c7.
2024-03-12 19:33:17 +03:00
zznty
ac6c312ed4
[FIX] Fix log4j config unpacking (#704) 2024-03-12 21:46:17 +07:00
microwin7
3e6af5afd3
Merge pull request #703 from zznty/dev
[FIX] Fix resolution of files with special characters for embedded file server
2024-03-12 17:06:25 +03:00
zznty
044813cca2 [FIX] Fix resolution of files with special characters for embedded file server 2024-03-12 20:56:23 +07:00
Gravita
730efae7c7 [ANY] Update modules 2024-03-12 16:08:35 +07:00
Gravita
bc6da641d6 [FEATURE] Support autobuild lwjgl3ify 2024-03-12 16:08:30 +07:00
Gravit'a
5c7f7eedec [FIX] Possible problem with lwjgl 2024-03-10 15:49:01 +07:00
Gravit'a
e72e4ebb92 [ANY] Update modules 2024-03-10 14:54:38 +07:00
Gravit'a
d811a04cba [FIX] DeleteProfileCommand 2024-03-10 14:53:52 +07:00
Gravit'a
572052163b [ANY] Update modules 2024-03-10 14:47:53 +07:00
Gravit'a
666c8a4b3e [ANY] Update modules 2024-03-10 14:03:25 +07:00
microwin7
ef5f932afb [FIX] Sort files in installAuthlib 2024-03-09 14:28:24 +03:00
microwin7
3002371fad [FEATURE] Sort jar files, before patching in installAuthlib 2024-03-09 00:40:28 +03:00
Gravita
998db80837 [ANY] Update modules 2024-03-08 14:46:55 +07:00
Gravita
494b3227b6 [FIX] Remove deprecated 'new URL' 2024-03-08 14:46:43 +07:00
Gravita
d686d9a388 [FEATURE] Add newHttpClientBuilder() 2024-03-08 14:37:15 +07:00
Gravita
e28c9773fc [FIX] file.encoding utf-8 by default 2024-03-05 19:07:02 +07:00
Gravita
7ff062f9e4 [ANY] Update modules 2024-03-05 16:48:35 +07:00
Gravita
29619bb7a4 [FIX] WebSocket dev log 2024-03-03 15:29:58 +07:00
Gravita
3179ee00eb [ANY] Update modules 2024-03-03 13:39:49 +07:00
Gravita
f484f045ca [FEATURE] FxRuntimeOptimizerModule 2024-03-03 13:39:28 +07:00
Gravita
ead4689bcf [FIX] checkServer token 2024-03-01 14:18:09 +07:00
Gravit
7a96e67517
Update push.yml 2024-03-01 14:11:24 +07:00
Gravita
b21082e201 [FIX] Netty in ServerWrapper 2024-02-27 16:50:58 +07:00
Gravita
8cd43b0324 [FEATURE] clearLauncherPackages, LaunchServerControlHolder 2024-02-27 16:33:43 +07:00
Gravita
3e8c1adebe [FIX] GetConnectUUIDCommand 2024-02-27 16:20:55 +07:00
Gravita
666644c9e0 [FEATURE] GetConnectUUIDCommand, shardId 2024-02-27 16:12:50 +07:00
Gravita
3152758d31 [FEATURE] GetConnectUUIDRequest 2024-02-27 16:04:17 +07:00
Gravita
80176ff1e1 [FEATURE] sync updatescache command 2024-02-27 15:53:27 +07:00
Gravita
11382d3465 [FEATURE] Profile delete and profile list commands 2024-02-27 15:34:20 +07:00
microwin7
31285a8066 [ANY] Update modules 2024-02-24 22:15:04 +03:00
Gravita
3ec79e3e93 [ANY] Update modules 2024-02-23 14:42:17 +07:00
Gravita
a4bf033aa8 [FEATURE] Support sudo in mysql/psotgresql/filesystem AuthCoreProvider 2024-02-23 14:42:00 +07:00
Gravita
4e50cea93a [FEATURE] Use encrypted ClientParams 2024-02-22 17:33:44 +07:00
Gravita
d40dc09aca [FEATURE] Handle non 2XX codes in Downloader 2024-02-15 01:31:27 +07:00
Gravita
0b59d6c0ed [FEATURE] Profile clone command 2024-02-10 14:47:13 +07:00
Gravita
80fc2900c8 [FEATURE] Public-Only server token 2024-02-07 14:27:04 +07:00
Gravita
4f47398211 [ANY] Update modules 2024-02-07 13:47:31 +07:00
Gravita
9676e55bcb [FEATURE] Support sudo 2024-02-07 13:47:18 +07:00
Gravita
aa7b007616 [FIX] Launcher log 2024-02-03 17:35:27 +07:00
Gravita
35bdf1607f [FEATURE] Add thread-safe info to responses 2024-02-03 17:11:36 +07:00
Gravita
7060697bad [FEATURE] Support virtual threads and client locks 2024-02-03 17:08:07 +07:00
Gravita
34ac6a0f28 Merge branch 'experimental/nojava8support' into dev 2024-01-31 15:42:19 +07:00
Gravita
5370130c2d Merge tag 'v5.5.4' into dev
5.5.4 stable
2024-01-31 14:36:57 +07:00
Gravita
db6ab061de Merge branch 'release/5.5.4' 2024-01-31 14:36:49 +07:00
Gravita
aeb55470ce [ANY] 5.5.4 stable 2024-01-31 14:36:41 +07:00
Gravita
a0788e4623 [FIX] Remove legacy launch4j in build.gradle 2024-01-23 19:44:19 +07:00
Gravita
0c754ae5e6 [FIX] Remove legacy manifest attributes 2024-01-23 19:35:54 +07:00
Gravita
c9ccf36252 [ANY] Remove reg limiter 2024-01-23 19:34:53 +07:00
Gravita
b7b7afbdbb [ANY] Update modules 2024-01-23 19:11:48 +07:00
Gravita
2fdd7d0199 [FIX] Slf4j fix 2024-01-23 19:11:33 +07:00
Gravit
1e3676778e
Merge pull request #696 from microwin7/dev
[FIX][FEATURE] Merge commits from nojava8support ref. Proguard, with jvmArgs
2024-01-18 02:57:44 +07:00
Gravita
ab884c8d23 [FEATURE] Add ProGuard jvmArgs 2024-01-17 20:24:43 +03:00
dima_dencep
48946d6e74 [FIX] proguard (#694) 2024-01-17 20:24:32 +03:00
microwin7
f075f39954
Merge branch 'GravitLauncher:dev' into dev 2024-01-17 20:22:02 +03:00
Gravita
f1922c52e2 [FEATURE] Add ProGuard jvmArgs 2024-01-18 00:13:36 +07:00
Gravita
f42e6de0b0 [ANY] Update dependencies 2024-01-17 23:51:56 +07:00
Gravita
efe967587c [FIX] Finally remove launch4j 2024-01-14 17:25:55 +07:00
Gravita
f2b92c2bbd [FIX] Update mirror link 2024-01-14 17:21:58 +07:00
Gravit'a
4251725467 [ANY] Update modules 2024-01-13 18:42:42 +07:00
dima_dencep
82bf2fdf56
[FIX] proguard (#694) 2024-01-12 16:02:02 +07:00
Gravita
f1bc0ea28a [ANY] Update modules 2024-01-09 16:59:15 +07:00
microwin7
8762aa470c
[FIX] Backport support arch i586 in release file (#693)
Co-authored-by: Gravita <12893402+gravit0@users.noreply.github.com>
2024-01-08 15:19:17 +07:00
Gravita
cfcd0010a7 [FIX] Support arch i586 in release file 2024-01-07 16:36:53 +03:00
Gravita
b3eb0ebb98 [FIX] Support arch i586 in release file 2024-01-07 20:18:15 +07:00
Gravita
8e82f5cd84 [FEATURE][EXPERIMENTAL] Parse java args method 2024-01-07 20:15:29 +07:00
Gravita
90f6d002d1 [FIX] Wayland support 2024-01-03 13:17:59 +07:00
Gravita
449798d52b [FIX] ServerWrapper 2023-12-29 17:07:03 +07:00
Gravita
f321b8bd27 [FIX] ExitResponse, update SimpleModuleManager 2023-12-29 16:37:14 +07:00
Gravita
64635cbb9b [REFACTOR] Renaming 2023-12-23 17:58:09 +07:00
Gravita
4607ab88bf [ANY] Update modules 2023-12-23 12:25:55 +07:00
Gravita
e6516a8991 [REFACTOR] IDEA mini refactor 2023-12-23 12:25:11 +07:00
Gravita
dfbb6e507a [REFACTOR] Using Java 17 2023-12-23 12:18:10 +07:00
Gravita
0855fc589d [REFACTOR] Remove log4j support, new LaunchServer start 2023-12-23 12:06:54 +07:00
Gravita
c9b6b0279a [REFACTOR] Renaming 2023-12-23 12:05:23 +07:00
Gravita
f8b060422e [FEATURE] Improve LaunchServer start 2023-12-20 04:45:41 +07:00
Gravita
31489a2b24 [FIX] Improve Wayland support 2023-12-20 02:13:39 +07:00
Gravita
6c0500f528 [ANY] 5.5.4-dev 2023-12-14 01:39:21 +07:00
Gravita
5896a12449 [FEATURE] Support option loadNatives 2023-12-14 01:36:07 +07:00
Gravita
240e36aab6 [FIX] Start Forge 1.18+ server 2023-12-14 01:19:36 +07:00
Gravita
f6f6ea13ad [ANY] Experimental branch 2023-12-08 20:24:31 +07:00
Gravita
967b81cc85 Merge tag 'v5.5.3' into dev
5.5.3 stable
2023-12-08 18:25:18 +07:00
Gravita
6dadea1b67 Merge branch 'release/5.5.3' 2023-12-08 18:25:10 +07:00
Gravita
a601a4ceef [ANY] Update modules 2023-12-08 17:53:51 +07:00
Gravita
1c90681b3b [FEATURE] Support Minecraft 1.20.3/1.20.4 2023-12-08 17:53:38 +07:00
Gravita
7b1f449667 [ANY] Update modules 2023-12-08 16:51:26 +07:00
Gravita
ae24fd6ccb [ANY] 5.5.3-stable 2023-12-08 16:51:10 +07:00
Gravita
2ff1d81076 [FEATURE] Support run Minecraft with Wayland 2023-12-01 20:26:06 +07:00
Gravita
ee0a7bc25a [FIX] Support mysql/postgresql 1.20.2 2023-12-01 17:59:05 +07:00
Gravita
ebbd1c87e8 [FIX] ModuleLaunch NPE 2023-11-30 00:05:59 +07:00
Gravita
3754a327b0 [FEATURE] ConfigService 2023-11-28 18:21:32 +07:00
Gravita
d2f34ced28 [FEATURE][EXPERIMENTAL] Add ClientServer and AuthService to ServerWrapper, extended check server, remove deprecated interfaces 2023-11-27 22:41:49 +07:00
Gravita
43626bf1f4 [FEATURE] Support createHackLookupNative 2023-11-26 20:51:58 +07:00
Gravita
380179faa3 [FIX] SystemdNotifyModule 2023-11-26 15:08:05 +07:00
Gravita
2e60d45c63 [FEATURE][EXPERIMENTAL] Support add javafx in LauncherEngineWrapper 2023-11-26 15:07:50 +07:00
Gravita
d678daac7b [FIX] Systemd notify logger 2023-11-25 00:28:00 +07:00
Gravita
1bc9351b0c [FIX] Compile fix 2023-11-21 13:58:10 +07:00
Gravita
54bfc6de9c [FIX] No refresh in client 2023-11-21 13:47:45 +07:00
Gravita
fe374c1f9e [ANY] Update modules 2023-11-21 13:17:48 +07:00
Gravita
3f4bdceb5a Merge tag 'v5.5.2' into dev
5.5.2 stable
2023-11-20 17:15:06 +07:00
Gravita
3d61635c6b Merge branch 'release/5.5.2' 2023-11-20 17:14:57 +07:00
Gravita
0c2779f1c0 [ANY] 5.5.2 stable 2023-11-20 16:47:23 +07:00
Gravita
8f598a40c5 Revert "[FIX] Force exit JVM"
This reverts commit 4720e4d106.
2023-11-19 14:19:56 +07:00
Gravita
4720e4d106 [FIX] Force exit JVM 2023-11-19 14:04:29 +07:00
Gravita
0482cfa9ab [ANY] Update modules 2023-11-10 00:32:40 +07:00
Gravita
a2167d483a [ANY] Update modules 2023-11-09 14:49:25 +07:00
Gravita
84a3845f1a [ANY] 5.5.2-dev 2023-11-04 21:51:18 +07:00
Gravita
0d5d772141 [ANY] Update modules 2023-11-04 21:50:31 +07:00
Gravita
9df116f951 [FIX] MirrorHelper 2023-11-04 21:50:11 +07:00
Gravita
ff3d500cc0 Merge tag 'v5.5.1' into dev
5.5.1 stable
2023-11-01 18:10:09 +07:00
Gravita
2e10c78ad2 Merge branch 'release/5.5.1' 2023-11-01 18:10:02 +07:00
Gravita
b6be2e243d [ANY] 5.5.1 stable 2023-11-01 17:40:52 +07:00
Gravita
44bc8b0bbc [FEATURE] Support modular start 2023-11-01 04:05:14 +07:00
Gravita
6cd5a69149 [FEATURE] ModuleLaunch bug fix 2023-11-01 00:10:19 +07:00
Gravita
b638efc0d1 [FEATURE] Support compat classes in ServerWrapper 2023-10-31 22:57:37 +07:00
Gravita
f2cbf0ed38 [ANY] Update modules 2023-10-31 22:52:21 +07:00
Gravita
0f36dfec16 [FEATURE] Support compat classes in client debug 2023-10-31 22:27:55 +07:00
Gravita
c1df548258 [FEATURE] Unified launch 2023-10-31 22:18:10 +07:00
Gravita
429c7a45c4 [ANY] Update modules 2023-10-31 16:59:23 +07:00
Gravita
90e116720c [FIX] onHardwareReport NPE 2023-10-31 16:22:44 +07:00
Gravita
4b222b9526 Merge tag 'v5.5.0' into dev
5.5.0 stable
2023-10-31 01:53:01 +07:00
Gravita
1df283d659 Merge branch 'release/5.5.0' 2023-10-31 01:52:54 +07:00
Gravita
b45618c0da [ANY] 5.5.0 stable 2023-10-31 01:52:41 +07:00
Gravita
0241a4d887 [ANY] Update modules 2023-10-31 01:29:45 +07:00
Gravita
de5ebe4a74 [ANY] Update modules 2023-10-31 00:59:25 +07:00
Gravita
80862e6116 [FEATURE] NoProtectHandler - disable joinServer protection 2023-10-31 00:20:57 +07:00
Gravita
f22aa36926 [FIX] Support Debug client with memory type 2023-10-31 00:14:56 +07:00
Gravita
198ce95176 [FEATURE] Support Debug client with memory type 2023-10-31 00:14:38 +07:00
Gravita
fca2ed2447 [FEATURE] Support 1.20+ autoenter 2023-10-30 23:11:05 +07:00
Gravita
2792b5a008 [ANY] Update modules 2023-10-30 22:17:24 +07:00
Gravita
a0335bd340 [ANY] Update modules 2023-10-30 22:06:48 +07:00
Gravita
3c6b8322a5 [ANY] Modules update 2023-10-30 19:45:36 +07:00
Gravita
a83f225933 [FIX] File not exist 2023-10-30 19:29:12 +07:00
Gravita
99af83fb06 [ANY] Update modules 2023-10-30 15:57:41 +07:00
Gravita
c4672387ac [ANY] Update modules 2023-10-29 21:50:15 +07:00
Gravita
fe7ae41f65 [FEATURE] Remove deprecated http methods, fix download 2023-10-29 21:18:47 +07:00
Gravita
474d557e3f [FIX] JsonTextureProvider 2023-10-29 20:16:23 +07:00
Gravita
50e6bb3b49 [FIX] Default config 2023-10-29 19:02:04 +07:00
Gravita
e85a12afab [FIX] Change default refresh rate to 5 seconds 2023-10-29 03:34:00 +07:00
Gravita
9e83e8bec8 [FIX] Use custom thread factory in autorefresh 2023-10-29 03:30:13 +07:00
Gravita
fce8453bd1 [FIX] Enable autoRefresh in default 2023-10-29 03:27:43 +07:00
Gravita
224649aa13 [FIX] Enable autoRefresh in default 2023-10-29 03:24:27 +07:00
Gravita
a3bcfed793 [ANY] Update gradle 2023-10-28 19:52:39 +07:00
Gravita
e1429356df [FIX] Build without proguard 2023-10-28 18:49:07 +07:00
Gravita
75f51c7727 [FIX] Use log4j-core annotation processor 2023-10-26 17:39:15 +07:00
Gravita
90162a1a25 [ANY] Update modules 2023-10-25 22:41:17 +07:00
Gravita
62e9276481 [FEATURE] Update build pipeline, fix multi release jar with proguard 2023-10-25 22:41:06 +07:00
Gravita
79f933646c [ANY] Update modules 2023-10-23 23:51:09 +07:00
Gravita
01c963d852 [ANY] Update modules 2023-10-23 23:08:18 +07:00
Gravita
941bf115b1 [FIX] New mirror path 2023-10-22 18:38:15 +07:00
Gravita
2e6d7bd94a [FIX] Improve JsonTextureProvider 2023-10-22 18:07:50 +07:00
Gravita
d262b99be6 [ANY] Update modules 2023-10-22 15:29:25 +07:00
Gravita
b5e10e8f9d [FEATURE] JsonTexture convertMap 2023-10-22 15:27:54 +07:00
Gravita
cebeb55c00 [FIX] Change JsonTextureProvider behavior, delete http method 2023-10-21 20:31:46 +07:00
Gravita
7e16f36ea0 [FIX] DebugMain exit 2023-10-21 19:16:01 +07:00
Gravita
8973a3462b [FEATURE] Support category in optional mods 2023-10-21 16:16:28 +07:00
Gravita
82dc299752 [FIX] Remove javafx.swing module 2023-10-18 18:10:16 +07:00
Gravita
19f966062b [ANY] Update modules 2023-10-10 16:14:28 +07:00
Gravita
93f916192e [FEATURE] MirrorHelper support build script 2023-10-10 16:14:10 +07:00
Gravita
dbeca56b05 [FEATURE] Support custom buttons 2023-10-06 12:55:07 +07:00
Gravita
d7474255da [ANY] Update modules 2023-10-03 19:16:23 +07:00
Gravita
b72fb643d4 [FEATURE] Support 1.20.2 2023-10-03 19:16:02 +07:00
Gravita
52e8f693b5 [FIX] AssetUploadInfo 2023-09-28 12:36:47 +07:00
Gravita
1f3b7e0552 [FEATURE] UploadAssetMixProvider 2023-09-26 14:08:44 +07:00
Gravita
183cfe949f [FEATURE] AssetUploadInfo 2023-09-26 13:55:01 +07:00
Gravita
aff254a875 [FEATURE] Support auth features in API 2023-09-24 00:13:32 +07:00
Gravita
e12f2ef897 [FEATURE] Support properties in ServerWrapper 2023-09-21 22:08:03 +07:00
Gravita
58d8ba2358 [FIX] Reset null config 2023-09-20 19:52:46 +07:00
Gravita
1cc3edd15b [ANY] Update modules 2023-09-20 19:33:15 +07:00
Gravita
606df6cb1a [ANY] IDEA code inspect 2023-09-20 19:32:49 +07:00
Gravita
98a314f697 [ANY] Update modules 2023-09-20 19:22:24 +07:00
Gravita
d03c6120ff [FIX] Sentry module Java 8 2023-09-20 19:22:04 +07:00
Gravita
a34267e902 [FEATURE] Use Java 21 2023-09-20 19:11:33 +07:00
Gravita
0ccef10a93 [FEATURE] Print error for unsupported Java 2023-09-19 16:44:09 +07:00
Gravita
29aee9dd30 [ANY] 5.5.0 dev 2023-09-18 16:26:47 +07:00
Gravita
1d563249d1 [FEATURE] Support build-in javafx in Java 9+ 2023-09-18 16:12:58 +07:00
Gravita
d0d2860317 [ANY] Gradle update 2023-09-18 16:12:58 +07:00
Gravita
faa5189795 [FEATURE][EXPERIMENTAL] Support debug args 2023-09-18 16:12:58 +07:00
Gravita
d9082f21a3 [ANY] Delete old interfaces 2023-09-18 16:12:58 +07:00
Gravita
3475f2f912 [FIX] Compile fix 2023-09-18 16:12:58 +07:00
Gravita
b635447eaf [FEATURE] Support Asset Upload 2023-09-18 16:12:58 +07:00
Gravita
0fb6102c29 [FEATURE] MixProvider init 2023-09-18 16:12:58 +07:00
Gravita
d4cc28f96a [FEATURE] MixProvider 2023-09-18 16:12:58 +07:00
Gravita
49a5215783 [ANY] Update modules 2023-09-18 16:12:58 +07:00
Gravita
f12d13ef58 [FIX] NPE 2023-09-18 16:12:58 +07:00
Gravita
55c77dd343 [FIX] Auto refresh 2023-09-18 16:12:58 +07:00
Gravita
caebd6b5de [FEATURE] Extended Token Expire, auto refresh 2023-09-18 16:12:58 +07:00
Gravita
647c8dba5c [FIX] Client debug 2023-09-18 16:12:58 +07:00
Gravita
6ffbc5515f [FEATURE] Client debug 2023-09-18 16:12:58 +07:00
Gravita
65c6520001 [ANY] Update modules 2023-09-18 16:12:58 +07:00
Gravita
ac9a78cea4 [ANY] Change no runtime module error message 2023-09-18 16:12:58 +07:00
Gravita
00a4a13536 [ANY] Update modules 2023-09-18 16:12:58 +07:00
Gravita
6e45a84c1c [FEATURE] Support lmodule with Java 17 2023-09-18 16:12:58 +07:00
Gravita
b31dd78b2b [ANY[ Update modules 2023-09-18 16:12:58 +07:00
Gravita
9de81095b1 [FEATURE][EXPERIMENTAL] separate branch 2023-09-18 16:12:58 +07:00
microwin7
4d1fd23e84
[FIX] Added Multi Release for LauncherCore (#672) 2023-07-30 14:32:13 +07:00
Gravita
41f00c2310 [FIX] Check certificate expired 2023-07-25 17:19:21 +07:00
Gravita
85986c2916 [FEATURE] Check certificate expired 2023-07-25 17:10:18 +07:00
Gravita
1bc0443dd5 Merge tag 'v5.4.4' into dev
5.4.4 stable
2023-07-25 16:35:06 +07:00
Gravita
50de0b1e44 Merge branch 'release/5.4.4' 2023-07-25 16:34:56 +07:00
Gravita
39d5eee51c [ANY] 5.4.4 stable 2023-07-25 16:34:38 +07:00
Gravita
8153c3a438 [FIX] componentName in config 2023-07-21 21:18:12 +07:00
Gravita
9d81db25d8 [ANY] Update modules 2023-07-21 21:17:37 +07:00
Sevastjan
d4ca612bff
[FEATURE] Фикс зависимостей и возможность взаимоисключения для опциональных модов (#663)
* [FEATURE] Mutually exclusive optional mods (XOR logic)

* [FIX] Bug with missing forgotten dependency/conflict

* [FIX] ArrayIndexOutOfBoundsException if xorConfict is empty

* [STYLE] Rename "xorConflict" to "group"
2023-07-21 21:11:48 +07:00
microwin7
680244e5d1
[FIX] Should no longer go into debugging ServerPinger (#669) 2023-07-21 21:09:05 +07:00
microwin7
2f7b94365a
[FIX] Proguard mapping letter L (#670) 2023-07-21 21:08:45 +07:00
Gravita
60f742b3ef Merge tag 'v5.4.3' into dev
5.4.3 stable
2023-07-16 14:33:42 +07:00
Gravita
7fee478552 Merge branch 'release/5.4.3' 2023-07-16 14:33:33 +07:00
Gravita
7efe7c8611 [ANY] Update modules 2023-07-16 14:32:34 +07:00
Gravita
d4abf27989 [ANY] 5.4.3 stable 2023-07-16 14:12:09 +07:00
Gravita
e887035920 [FIX] Remove experimental warning 2023-07-16 13:59:45 +07:00
Gravita
890591d2d2 [ANY] Update modules 2023-07-16 13:52:22 +07:00
Gravita
82938fe8d4 [FIX] Remove old style assets folder 2023-07-16 13:36:45 +07:00
Gravita
cebe47939a [ANY] Update asm 2023-07-16 13:19:23 +07:00
Gravita
fd24ca0ca7 [ANY] Update depencencides 2023-07-16 13:18:00 +07:00
Gravita
663685934b [ANY] Update gradle wrapper 2023-07-16 13:15:06 +07:00
Gravita
43d944bee1 [ANY] Update modules 2023-07-16 13:13:54 +07:00
microwin7
3a82065889
[FEATURE] JVMHelper class extension (#667) 2023-07-16 13:12:41 +07:00
microwin7
55c0cdfa0d
[FEATURE] Replaced with use TimeUnit (#666) 2023-07-16 13:11:58 +07:00
microwin7
b12c43676b
[FEATURE] Replaced String.format to formatted (#665) 2023-07-16 13:11:45 +07:00
Gravita
9d49eebffe [ANY] Update modules 2023-07-15 14:38:31 +07:00
microwin7
2aa8dffcaa
[REFACTOR] Replaced to pattern matching instanceof (#664) 2023-07-15 14:37:26 +07:00
Gravita
216928f258 [ANY] Update modules 2023-07-12 04:03:17 +07:00
Gravita
ec3775286c [ANY] Update modules 2023-07-03 22:32:18 +07:00
microwin7
7950eea975
Updating .gitattributes to common standards (#648) 2023-06-30 20:23:23 +07:00
microwin7
04dd7d655c
[ANY] Case ignoring for commands in ServerWrapper (#657) 2023-06-30 20:22:22 +07:00
Gravita
bd83e8a4c5 [FEATURE] Support hikariMaxLifetime 2023-06-30 20:21:20 +07:00
Gravita
d4f63a4e19 [ANY] Update modules 2023-06-28 19:21:09 +07:00
Gravita
c458283efb [ANY] Update modules 2023-06-19 01:50:09 +07:00
Gravita
bea7898939 [FEATURE] Change hash type to sha1 2023-06-09 18:25:08 +07:00
Gravit'a
bddf31c94e [FEATURE] LaunchServerLauncherExeInit 2023-05-25 15:33:01 +07:00
Gravita
24d625fd16 Merge tag 'v5.4.2' into dev
5.4.2 stable
2023-05-20 15:54:51 +07:00
Gravita
0c23b59749 Merge branch 'release/5.4.2' 2023-05-20 15:54:32 +07:00
Gravita
9b2c98e10b [ANY] 5.4.2 stable 2023-05-20 13:48:29 +07:00
Gravita
4a538cde61 [ANY] Update modules 2023-05-15 13:22:28 +07:00
Gravita
d9be4bb577 [FEATURE] Support LauncherAuthlib4 2023-05-15 13:22:24 +07:00
microwin7
ae426b2fd0
[FIX] Incorrect send AuthException for User not found (#651) 2023-05-13 15:49:30 +07:00
Gravita
498325f3e8 [ANY] Update modules 2023-05-08 00:42:00 +07:00
Gravita
907e7cc47e [ANY] Update modules 2023-05-07 00:10:23 +07:00
Gravita
d4eabbc4c0 [FIX] Remove digest check 2023-05-06 00:25:48 +07:00
Gravita
540ad0b0da Merge tag 'v5.4.1' into dev
5.4.1 stable
2023-04-28 21:23:09 +07:00
Gravita
3b8c01835d Merge branch 'release/5.4.1' 2023-04-28 21:22:54 +07:00
Gravita
00baf4adf0 [ANY] 5.4.1 stable 2023-04-28 21:22:31 +07:00
Gravita
e338bb9a02 [FIX] Autoupdate mirror link 2023-04-28 21:19:44 +07:00
Gravita
373fc8a255 [ANY] 5.4.1 dev 2023-04-28 20:44:47 +07:00
Gravita
73d8a037d5 Merge tag 'v5.4.0' into dev
5.4.0 stable
2023-04-28 20:42:21 +07:00
Gravita
aa47fd6f1b Merge branch 'release/5.4.0' 2023-04-28 20:42:07 +07:00
Gravita
dec86c9a91 [ANY] 5.4.0 stable 2023-04-28 20:41:48 +07:00
Gravita
74af58bc7a [ANY] Remove deprecated flag 2023-04-28 01:02:40 +07:00
Gravita
d97b856ad6 [FIX] Update mysql connector 2023-04-28 00:08:31 +07:00
Gravita
5436b2a2d6 [FIX] Double read textures 2023-04-27 21:12:21 +07:00
Gravita
5aa4fe8d47 [ANY] Update modules 2023-04-27 21:03:21 +07:00
Gravita
b10535042f [FIX] FileAuthSystem slim support 2023-04-27 21:02:30 +07:00
Gravit
9351f3ca1e
Merge pull request #647 from microwin7/patch-15
[FIX] DownloadAssetCommand.java command
2023-04-25 20:31:50 +07:00
microwin7
e05aa4b204
[FIX] DownloadAssetCommand.java command
ArrayIndexOutOfBoundsException
2023-04-25 16:28:14 +03:00
Gravita
bfa6966ec6 [ANY] Update mirror link 2023-04-22 20:17:02 +07:00
Gravita
50b463b439 [FEATURE] Support custom protocol 2023-04-21 16:14:33 +07:00
Gravita
6caa34e255 [FEATURE] MirrorHelper Quilt support 2023-04-21 15:34:42 +07:00
Gravita
26c017a277 [ANY] Update modules 2023-04-20 21:48:52 +07:00
Gravita
48799cf3c2 [FEATURE] New ClientProfile$Version 2023-04-20 19:06:12 +07:00
Gravita
3bc8040352 [FEATURE] MirrorHelper Modrinth support 2023-04-20 15:54:02 +07:00
Gravita
dcaec54814 [ANY] Update modules 2023-04-18 14:53:35 +07:00
Gravit'a
76f8b4602c [FIX] JAVA_HOME override 2023-04-15 17:40:34 +07:00
Gravit'a
fc7f96d536 [FIX] launcher-pack in Windows 2023-04-15 13:59:32 +07:00
Gravit'a
4ed687087f [FIX] LauncherGuard default config 2023-04-15 13:51:33 +07:00
Gravita
95da394a5d [FEATURE] LauncherGuard module 2023-04-11 13:28:31 +07:00
Gravita
06e9bc8578 [ANY] IDEA code inspect 2023-04-03 13:04:12 +07:00
Gravita
e0b3f3d6a5 [FEATURE] Launcher build number 2023-03-29 23:46:26 +07:00
Gravita
6a057514b2 [FIX] RequestTextureProvider localPath 2023-03-26 23:57:28 +07:00
Gravita
70d102222b [ANY] Upgrade gradle 2023-03-25 23:54:00 +07:00
Gravita
55d2fbd57f [ANY] Update modules 2023-03-23 20:28:03 +07:00
Gravita
111d7616d0 [FIX] NPE 2023-03-23 18:56:23 +07:00
Gravita
fb2883d215 [FIX] Possible multithreading issue 2023-03-22 21:46:40 +07:00
Gravita
dc664c7ee2 [FIX] Forbidden modification info 2023-03-22 21:18:08 +07:00
Gravita
eff739ce12 [ANY] Update modules 2023-03-20 00:25:55 +07:00
Gravita
17f9c28f3d [FEATURE] WebSocket hook rework 2023-03-20 00:25:27 +07:00
Gravita
71739f5670 [ANY] Update modules 2023-03-19 21:27:14 +07:00
Gravita
e4bf8f1e9c [ANY] Update modules 2023-03-19 21:22:37 +07:00
Gravita
a796b82a16 [FIX] JavaHelper NPE 2023-03-16 21:57:33 +07:00
Gravita
cf802fb0b3 [FEATURE] Support 1.19.4 2023-03-15 18:50:15 +07:00
Gravita
57868a7136 [ANY] Update modules 2023-03-07 15:23:49 +07:00
Gravita
8ac9866258 [FEATURE] SentryProguardUpload 2023-03-07 15:23:20 +07:00
Gravita
47392ceec7 Merge tag 'v5.3.6' into dev
5.3.6 stable
2023-03-07 14:22:28 +07:00
Gravita
c6f8793031 Merge branch 'release/5.3.6' 2023-03-07 14:21:15 +07:00
Gravita
0d74d8a671 Merge branch 'experimental/profiles-rework' into dev 2023-03-03 18:06:35 +07:00
Gravita
70012a2a8f [ANY] 5.3.6 release 2023-03-03 18:00:57 +07:00
Gravita
3821fa7e51 [ANY] Update modules 2023-03-03 16:59:50 +07:00
Gravita
930a5caf74 [ANY] Remove old guard/antiinject support 2023-02-26 19:01:43 +07:00
Gravita
ffad29f53b [FEATURE] Isolate Minecraft: Support add packages to ignore 2023-02-26 18:40:11 +07:00
Gravita
bd4e454be9 Merge branch 'experimental/5.3.0' into experimental/profiles-rework 2023-02-26 18:06:50 +07:00
Gravita
0818b3037c [FEATURE] ClientProfile compat flags, remove deprecated 2023-02-26 18:00:44 +07:00
Gravita
43ffacdf5e [FEATURE] Update Sentry module 2023-02-26 13:12:11 +07:00
Gravita
5c374462ef [FIX] Ping error handle 2023-02-25 19:54:05 +07:00
Gravita
ef5695f679 [ANY] Update modules 2023-02-25 18:43:34 +07:00
Gravita
b4331819cb [FEATURE] Update OptionalView 2023-02-25 18:42:26 +07:00
Gravit
ebf25a65f7
Merge pull request #637 from microwin7/patch-16
[FEATURE] Default value for client profile and assets dir
2023-02-22 16:44:57 +07:00
microwin7
5588b4aac1 [FEATURE] Default value for client profile and assets dir 2023-02-18 07:00:35 +03:00
Gravita
fbaf9ab87f [ANY] Update modules 2023-01-30 20:33:16 +07:00
Gravita
6d1440207b [FIX] Launch4j build exe 2023-01-25 18:02:24 +07:00
Gravita
9d719c48e3 [ANY] Update dependencies 2023-01-24 17:17:03 +07:00
Gravita
b0fba84fbb [FEATURE] MergeAuthCoreProvider 2023-01-20 19:57:14 +07:00
Gravita
1ffd36fc82 Merge tag 'v5.3.5' into dev
5.3.5 stable release
2023-01-15 14:50:37 +07:00
Gravita
9c359747ea Merge branch 'release/5.3.5' 2023-01-15 14:50:21 +07:00
Gravita
2c1972c12c [ANY] 5.3.5 stable 2023-01-15 14:49:20 +07:00
Gravita
d30f0b900b [ANY] Change domain 2023-01-11 18:16:51 +07:00
Tenebrius
f71444b8d6
[FIX] Check openjdk 8 on linux (#632)
* Correct check 8 openjdk on linux

* JRE rt lib in jre/lib/rt.jar

* [fix] remove dev log
2023-01-10 18:14:47 +07:00
Gravita
a0ac58f0b5 [FEATURE] printExperimentalBranch info 2022-12-22 19:11:11 +07:00
Gravita
a27d7f1597 [FEATURE] printExperimentalBranch 2022-12-22 19:07:46 +07:00
Gravita
7aa08c1846 Merge tag 'v5.3.4' into dev
5.3.4 hotfix
2022-12-12 15:15:51 +07:00
Gravita
2045f1ac99 Merge branch 'hotfix/5.3.4' 2022-12-12 15:15:39 +07:00
Gravita
292e7d2af7 [ANY] 5.3.4 hotfix 2022-12-12 15:15:22 +07:00
Gravita
5f7808afff [FIX] MySQLAuthCoreProvider 2022-12-12 15:14:12 +07:00
Gravita
3c10a668de Merge tag 'v5.3.3' into dev
5.3.3 stable
2022-12-09 21:47:43 +07:00
Gravita
ec222aed6f Merge branch 'release/5.3.3' 2022-12-09 21:47:32 +07:00
Gravita
7dcc5aef3f [FIX] More details in error when reading launcher module config file 2022-12-09 21:46:05 +07:00
Gravita
ae994ebb4f [FIX] More safe save config and 5.3.3 2022-12-09 21:34:23 +07:00
Gravita
553cdf5250 [FEATURE] Safe config write 2022-12-09 21:17:07 +07:00
Gravita
0d1b32fc1c [FIX] Save config with postgresql 2022-12-09 20:55:58 +07:00
Gravita
7f4fe566de [FIX] ClassCastException 2022-12-09 20:19:22 +07:00
Gravita
cc825df41e Merge tag 'v5.3.2' into dev
5.3.2 stable
2022-12-09 14:02:28 +07:00
Gravita
857901a667 [FEATURE] Isolate minecraft 2022-05-19 00:38:12 +07:00
562 changed files with 10236 additions and 9007 deletions

86
.gitattributes vendored
View file

@ -1,26 +1,78 @@
* text eol=lf
*.bat text eol=crlf
*.sh text eol=lf
* text=auto eol=lf
*.[cC][mM][dD] text eol=crlf
*.[bB][aA][tT] text eol=crlf
*.[pP][sS]1 text eol=crlf
*.[sS][hH] text eol=lf
*.patch text eol=lf
*.java text eol=lf
*.scala text eol=lf
*.groovy text eol=lf
*.gradle text eol=crlf
gradle.properties text eol=crlf
/gradle/wrapper/gradle-wrapper.properties text eol=crlf
*.cfg text eol=lf
*.png binary
*.jar binary
*.war binary
*.lzma binary
*.zip binary
*.gzip binary
*.dll binary
*.so binary
*.exe binary
*.ico binary
*.eot binary
*.ttf binary
*.woff binary
*.woff2 binary
*.a binary
*.lib binary
*.icns binary
*.jpg binary
*.jpeg binary
*.gif binary
*.mov binary
*.mp4 binary
*.mp3 binary
*.flv binary
*.fla binary
*.swf binary
*.gz binary
*.tar binary
*.tar.gz binary
*.7z binary
*.pyc binary
*.gpg binary
*.bin binary
*.gitattributes text eol=crlf
*.gitignore text eol=crlf
*.gitattributes text
.gitignore text
# Java sources
*.java text diff=java
*.kt text diff=kotlin
*.groovy text diff=java
*.scala text diff=java
*.gradle text diff=java
*.gradle.kts text diff=kotlin
# These files are text and should be normalized (Convert crlf => lf)
*.css text diff=css
*.scss text diff=css
*.sass text
*.df text
*.htm text diff=html
*.html text diff=html
*.js text
*.jsp text
*.jspf text
*.jspx text
*.properties text
*.tld text
*.tag text
*.tagx text
*.xml text
# These files are binary and should be left untouched
# (binary is a macro for -text -diff)
*.class binary
*.dll binary
*.ear binary
*.jar binary
*.so binary
*.war binary
*.jks binary
mvnw text eol=lf
gradlew text eol=lf

View file

@ -6,20 +6,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: recursive
- name: Cache Gradle
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ~/.gradle/caches
key: gravit-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}-launcher
- name: Set up JDK 17
uses: actions/setup-java@v1
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: 17
java-version: 21
distribution: temurin
- name: Grant execute permission for gradlew
run: chmod +x gradlew
@ -27,20 +28,27 @@ jobs:
- name: Build with Gradle
run: ./gradlew build
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@417ae3ccd767c252f5661f1ace9f835f9654f2b5
- name: Create artifacts
run: |
mkdir -p artifacts/modules
cd LaunchServer/build/libs
mv proguard proguard-libraries
zip -r -9 ../../../artifacts/libraries.zip * -x "LaunchServer.jar" -x "LaunchServer-clean.jar"
cp LaunchServer.jar ../../../artifacts/LaunchServer.jar
cd ../../..
cp ServerWrapper/build/libs/ServerWrapper.jar artifacts/ServerWrapper.jar
cp ServerWrapper/build/libs/ServerWrapper-inline.jar artifacts/ServerWrapperInline.jar
cp LauncherAuthlib/build/libs/LauncherAuthlib.jar artifacts/LauncherAuthlib.jar || true
cp modules/*_module/build/libs/*.jar artifacts/modules || true
cp modules/*_lmodule/build/libs/*.jar artifacts/modules || true
cp javaargs.txt artifacts/javaargs.txt || true
cp java24args.txt artifacts/java24args.txt || true
- name: Upload artifacts
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v4
with:
name: Launcher
path: artifacts
@ -61,7 +69,7 @@ jobs:
- name: Create release
id: create_release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
if: startsWith(github.event.ref, 'refs/tags')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -1,4 +1,4 @@
def mainClassName = "pro.gravit.launchserver.LaunchServerStarter"
def mainClassName = "pro.gravit.launchserver.Main"
def mainAgentName = "pro.gravit.launchserver.StarterAgent"
evaluationDependsOn(':Launcher')
@ -13,33 +13,37 @@
maven {
url "https://jitpack.io/"
}
maven {
url 'https://maven.gravit-support.ru/repository/jitpack'
credentials {
username = 'gravitlauncher'
password = 'gravitlauncher'
}
}
}
sourceCompatibility = '17'
targetCompatibility = '17'
sourceCompatibility = '21'
targetCompatibility = '21'
configurations {
compileOnlyA
bundleOnly
bundle
hikari
pack
launch4j
proguardPack
bundleOnly.extendsFrom bundle
api.extendsFrom bundle, hikari, pack, launch4j
api.extendsFrom bundle, pack
}
jar {
dependsOn parent.childProjects.Launcher.tasks.assemble
from { configurations.pack.collect { it.isDirectory() ? it : zipTree(it) } }
exclude("module-info.class")
from(parent.childProjects.Launcher.tasks.shadowJar)
from(parent.childProjects.Launcher.tasks.genRuntimeJS)
manifest.attributes("Main-Class": mainClassName,
"Premain-Class": mainAgentName,
"Multi-Release": "true",
"Can-Redefine-Classes": "true",
"Can-Retransform-Classes": "true",
"Can-Set-Native-Method-Prefix": "true"
"Automatic-Module-Name": "launchserver"
)
}
@ -50,128 +54,102 @@
}
}
task sourcesJar(type: Jar) {
tasks.register('sourcesJar', Jar) {
from sourceSets.main.allJava
archiveClassifier.set('sources')
}
task javadocJar(type: Jar) {
tasks.register('javadocJar', Jar) {
from javadoc
archiveClassifier.set('javadoc')
}
task cleanjar(type: Jar, dependsOn: jar) {
tasks.register('cleanjar', Jar) {
dependsOn jar
archiveClassifier.set('clean')
manifest.attributes("Main-Class": mainClassName,
"Premain-Class": mainAgentName,
"Can-Redefine-Classes": "true",
"Can-Retransform-Classes": "true",
"Can-Set-Native-Method-Prefix": "true"
"Automatic-Module-Name": "launchserver"
)
from sourceSets.main.output
}
dependencies {
pack project(':LauncherAPI')
bundle group: 'me.tongfei', name: 'progressbar', version: '0.9.2'
bundle group: 'com.github.Marcono1234', name: 'gson-record-type-adapter-factory', version: 'v0.2.0'
pack(project(':LauncherAPI')) {
exclude group: "com.google.code.gson"
}
bundle group: 'com.google.code.gson', name: 'gson', version: rootProject['verGson']
bundle group: 'me.tongfei', name: 'progressbar', version: '0.10.1'
bundle group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi']
bundle group: 'org.jline', name: 'jline', version: rootProject['verJline']
bundle group: 'org.jline', name: 'jline-native', version: rootProject['verJline']
bundle group: 'org.jline', name: 'jline-reader', version: rootProject['verJline']
bundle group: 'org.jline', name: 'jline-terminal', version: rootProject['verJline']
bundle group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: rootProject['verBcpkix']
bundle group: 'org.jline', name: 'jline-terminal-ffm', version: rootProject['verJline']
bundle group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: rootProject['verBcpkix']
bundle group: 'org.bouncycastle', name: 'bcpkix-jdk18on', version: rootProject['verBcpkix']
bundle group: 'org.ow2.asm', name: 'asm-commons', version: rootProject['verAsm']
bundle group: 'io.netty', name: 'netty-all', version: rootProject['verNetty']
bundle group: 'io.netty', name: 'netty-codec-http', version: rootProject['verNetty']
bundle group: 'io.netty', name: 'netty-transport-classes-epoll', version: rootProject['verNetty']
bundle group: 'io.netty', name: 'netty-transport-native-epoll', version: rootProject['verNetty'], classifier: 'linux-x86_64'
//bundle group: 'io.netty', name: 'netty-transport-native-epoll', version: rootProject['verNetty'], classifier: 'linux-aarch_64'
bundle group: 'io.netty', name: 'netty-transport-classes-io_uring', version: rootProject['verNetty']
bundle group: 'io.netty', name: 'netty-transport-native-io_uring', version: rootProject['verNetty'], classifier: 'linux-x86_64'
//bundle group: 'io.netty', name: 'netty-transport-native-io_uring', version: rootProject['verNetty'], classifier: 'linux-aarch_64'
// Netty
bundle 'org.jboss.marshalling:jboss-marshalling:1.4.11.Final'
bundle 'com.google.protobuf.nano:protobuf-javanano:3.1.0'
//
bundle group: 'org.slf4j', name: 'slf4j-api', version: rootProject['verSlf4j']
bundle group: 'mysql', name: 'mysql-connector-java', version: rootProject['verMySQLConn']
bundle group: 'com.mysql', name: 'mysql-connector-j', version: rootProject['verMySQLConn']
bundle group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: rootProject['verMariaDBConn']
bundle group: 'org.postgresql', name: 'postgresql', version: rootProject['verPostgreSQLConn']
bundle group: 'com.guardsquare', name: 'proguard-base', version: rootProject['verProguard']
bundle group: 'com.h2database', name: 'h2', version: rootProject['verH2Conn']
proguardPack group: 'com.guardsquare', name: 'proguard-base', version: rootProject['verProguard']
bundle group: 'org.apache.logging.log4j', name: 'log4j-core', version: rootProject['verLog4j']
bundle group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: rootProject['verLog4j']
bundle group: 'org.apache.logging.log4j', name: 'log4j-slf4j2-impl', version: rootProject['verLog4j']
bundle group: 'io.jsonwebtoken', name: 'jjwt-api', version: rootProject['verJwt']
bundle group: 'io.jsonwebtoken', name: 'jjwt-impl', version: rootProject['verJwt']
bundle group: 'io.jsonwebtoken', name: 'jjwt-gson', version: rootProject['verJwt']
bundle group: 'com.google.code.gson', name: 'gson', version: rootProject['verGson']
annotationProcessor(group: 'org.apache.logging.log4j', name: 'log4j-core', version: rootProject['verLog4j'])
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: rootProject['verJunit']
hikari 'io.micrometer:micrometer-core:1.8.4'
hikari('com.zaxxer:HikariCP:5.0.1') {
bundle 'io.micrometer:micrometer-core:1.14.4'
bundle('com.zaxxer:HikariCP:6.2.1') {
exclude group: 'javassist'
exclude group: 'io.micrometer'
exclude group: 'org.slf4j'
}
launch4j('net.sf.launch4j:launch4j:' + rootProject['verLaunch4j']) {
exclude group: 'org.apache.ant'
exclude group: 'net.java.abeille'
exclude group: 'foxtrot'
exclude group: 'com.jgoodies'
exclude group: 'org.slf4j'
}
launch4j('net.sf.launch4j:launch4j:' + rootProject['verLaunch4j'] + ':workdir-win32') { transitive = false }
launch4j('net.sf.launch4j:launch4j:' + rootProject['verLaunch4j'] + ':workdir-linux64') { transitive = false }
compileOnlyA group: 'com.google.guava', name: 'guava', version: rootProject['verGuavaC']
// Do not update (laggy deps).
compileOnlyA 'log4j:log4j:1.2.17'
compileOnlyA 'org.apache.logging.log4j:log4j-core:2.14.1'
}
task hikari(type: Copy) {
tasks.register('dumpLibs', Copy) {
duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/libraries/hikaricp"
from configurations.hikari
}
task launch4j(type: Copy) {
duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/libraries/launch4j"
from(configurations.launch4j.collect {
it.isDirectory() ? it : ((it.getName().startsWith("launch4j") && it.getName().contains("workdir")) ? zipTree(it) : it)
})
includeEmptyDirs false
eachFile { FileCopyDetails fcp ->
if (fcp.relativePath.pathString.startsWith("launch4j-") &&
fcp.relativePath.pathString.contains("workdir")) {
def segments = fcp.relativePath.segments
def pathSegments = segments[1..-1] as String[]
fcp.relativePath = new RelativePath(!fcp.file.isDirectory(), pathSegments)
} else if (fcp.relativePath.pathString.contains("META-INF")) fcp.exclude()
fcp.mode = 0755
}
}
task dumpLibs(type: Copy) {
duplicatesStrategy = 'EXCLUDE'
dependsOn tasks.hikari, tasks.launch4j
into "$buildDir/libs/libraries"
from configurations.bundleOnly
}
task dumpCompileOnlyLibs(type: Copy) {
tasks.register('dumpProguard', Copy) {
duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/launcher-libraries-compile"
from configurations.compileOnlyA
into "$buildDir/libs/proguard"
from configurations.proguardPack
}
task bundle(type: Zip) {
tasks.register('bundle', Zip) {
duplicatesStrategy = 'EXCLUDE'
dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.dumpCompileOnlyLibs, tasks.jar
dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.jar
archiveFileName = 'LaunchServer.zip'
destinationDirectory = file("$buildDir")
from(tasks.dumpLibs.destinationDir) { into 'libraries' }
from(tasks.dumpCompileOnlyLibs.destinationDir) { into 'launcher-libraries-compile' }
from(tasks.jar)
from(parent.childProjects.Launcher.tasks.dumpLibs) { into 'launcher-libraries' }
}
task dumpClientLibs(type: Copy) {
tasks.register('dumpClientLibs', Copy) {
dependsOn parent.childProjects.Launcher.tasks.build
into "$buildDir/libs/launcher-libraries"
from parent.childProjects.Launcher.tasks.dumpLibs
}
assemble.dependsOn tasks.dumpLibs, tasks.dumpCompileOnlyLibs, tasks.dumpClientLibs, tasks.bundle, tasks.cleanjar
assemble.dependsOn tasks.dumpLibs, tasks.dumpClientLibs, tasks.bundle, tasks.cleanjar, tasks.dumpProguard
publishing {
@ -186,7 +164,7 @@ task dumpClientLibs(type: Copy) {
pom {
name = 'GravitLauncher LaunchServer API'
description = 'GravitLauncher LaunchServer Module API'
url = 'https://launcher.gravit.pro'
url = 'https://gravitlauncher.com'
licenses {
license {
name = 'GNU General Public License, Version 3.0'
@ -209,7 +187,7 @@ task dumpClientLibs(type: Copy) {
scm {
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
url = 'https://launcher.gravit.pro/'
url = 'https://gravitlauncher.com/'
}
}
}

View file

@ -1,7 +1,7 @@
package pro.gravit.launchserver;
import com.google.gson.JsonElement;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launchserver.helper.HttpHelper;
import java.io.IOException;
@ -21,6 +21,10 @@ public <T> SimpleErrorHandler<T> makeEH(Class<T> clazz) {
return new SimpleErrorHandler<>(clazz);
}
public <T> SimpleErrorHandler<T> makeEH(Type clazz) {
return new SimpleErrorHandler<>(clazz);
}
public <T> HttpRequest get(String url, String token) {
try {
var requestBuilder = HttpRequest.newBuilder()
@ -59,6 +63,10 @@ public <T> HttpHelper.HttpOptional<T, SimpleError> send(HttpRequest request, Cla
return HttpHelper.send(httpClient, request, makeEH(clazz));
}
public <T> HttpHelper.HttpOptional<T, SimpleError> send(HttpRequest request, Type type) throws IOException {
return HttpHelper.send(httpClient, request, makeEH(type));
}
public static class SimpleErrorHandler<T> implements HttpHelper.HttpJsonErrorHandler<T, SimpleError> {
private final Type type;

View file

@ -2,47 +2,43 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.events.RequestEvent;
import pro.gravit.launcher.events.request.ProfilesRequestEvent;
import pro.gravit.launcher.managers.ConfigManager;
import pro.gravit.launcher.modules.events.ClosePhase;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.base.events.request.ProfilesRequestEvent;
import pro.gravit.launcher.base.modules.events.ClosePhase;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider;
import pro.gravit.launchserver.binary.EXEL4JLauncherBinary;
import pro.gravit.launchserver.binary.EXELauncherBinary;
import pro.gravit.launchserver.binary.JARLauncherBinary;
import pro.gravit.launchserver.binary.LauncherBinary;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.helper.SignHelper;
import pro.gravit.launchserver.launchermodules.LauncherModuleLoader;
import pro.gravit.launchserver.manangers.*;
import pro.gravit.launchserver.manangers.hook.AuthHookManager;
import pro.gravit.launchserver.modules.events.*;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.SocketCommandServer;
import pro.gravit.launchserver.socket.handlers.NettyServerSocketHandler;
import pro.gravit.launchserver.socket.response.auth.ProfilesResponse;
import pro.gravit.launchserver.socket.response.auth.RestoreResponse;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.command.CommandHandler;
import pro.gravit.utils.command.SubCommand;
import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.BufferedReader;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.KeyStore;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@ -50,7 +46,6 @@
* Not a singletron
*/
public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurable {
public static final Class<? extends LauncherBinary> defaultLauncherEXEBinaryClass = null;
/**
* Working folder path
*/
@ -71,6 +66,7 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
/**
* The path to the folder with updates/webroot
*/
@Deprecated
public final Path updatesDir;
// Constant paths
@ -81,8 +77,12 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
/**
* The path to the folder with profiles
*/
public final Path profilesDir;
public final Path tmpDir;
public final Path modulesDir;
public final Path launcherModulesDir;
public final Path librariesDir;
public final Path controlFile;
public final Path proguardDir;
/**
* This object contains runtime configuration
*/
@ -95,8 +95,6 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
* Pipeline for building EXE
*/
public final LauncherBinary launcherEXEBinary;
//public static LaunchServer server = null;
public final Class<? extends LauncherBinary> launcherEXEBinaryClass;
// Server config
public final AuthHookManager authHookManager;
public final LaunchServerModulesManager modulesManager;
@ -113,23 +111,21 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
// Server
public final CommandHandler commandHandler;
public final NettyServerSocketHandler nettyServerSocketHandler;
public final SocketCommandServer socketCommandServer;
public final ScheduledExecutorService service;
public final AtomicBoolean started = new AtomicBoolean(false);
public final LauncherModuleLoader launcherModuleLoader;
private final Logger logger = LogManager.getLogger();
public final int shardId;
public LaunchServerConfig config;
// Updates and profiles
private volatile Set<ClientProfile> profilesList;
@SuppressWarnings("deprecation")
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, KeyAgreementManager keyAgreementManager, CommandHandler commandHandler, CertificateManager certificateManager) throws IOException {
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, KeyAgreementManager keyAgreementManager, CommandHandler commandHandler, CertificateManager certificateManager, int shardId) throws IOException {
this.dir = directories.dir;
this.tmpDir = directories.tmpDir;
this.env = env;
this.config = config;
this.launchServerConfigManager = launchServerConfigManager;
this.modulesManager = modulesManager;
this.profilesDir = directories.profilesDir;
this.updatesDir = directories.updatesDir;
this.keyAgreementManager = keyAgreementManager;
this.commandHandler = commandHandler;
@ -139,6 +135,12 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
launcherLibraries = directories.launcherLibrariesDir;
launcherLibrariesCompile = directories.launcherLibrariesCompileDir;
launcherPack = directories.launcherPackDir;
modulesDir = directories.modules;
launcherModulesDir = directories.launcherModules;
librariesDir = directories.librariesDir;
controlFile = directories.controlFile;
proguardDir = directories.proguardDir;
this.shardId = shardId;
if(!Files.isDirectory(launcherPack)) {
Files.createDirectories(launcherPack);
}
@ -149,9 +151,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
// Print keypair fingerprints
// Load class bindings.
launcherEXEBinaryClass = defaultLauncherEXEBinaryClass;
runtime.verify();
config.verify();
@ -192,6 +191,11 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
}
launcherModuleLoader.init();
nettyServerSocketHandler = new NettyServerSocketHandler(this);
socketCommandServer = new SocketCommandServer(commandHandler, controlFile);
if(config.sign.checkCertificateExpired) {
checkCertificateExpired();
service.scheduleAtFixedRate(this::checkCertificateExpired, 24, 24, TimeUnit.HOURS);
}
// post init modules
modulesManager.invokeEvent(new LaunchServerPostInitPhase(this));
}
@ -219,7 +223,14 @@ public void reload(ReloadType type) throws Exception {
});
logger.debug("Init components successful");
}
if(!type.equals(ReloadType.NO_AUTH)) {
nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((channel, wsHandler) -> {
Client client = wsHandler.getClient();
if(client.auth != null) {
client.auth = config.getAuthProviderPair(client.auth_id);
}
});
}
}
@Override
@ -234,9 +245,8 @@ public void invoke(String... args) throws Exception {
}
switch (args[0]) {
case "full" -> reload(ReloadType.FULL);
case "no_auth" -> reload(ReloadType.NO_AUTH);
case "no_components" -> reload(ReloadType.NO_COMPONENTS);
default -> reload(ReloadType.FULL);
default -> reload(ReloadType.NO_AUTH);
}
}
};
@ -262,26 +272,37 @@ public void invoke(String... args) throws Exception {
}
pair.core.close();
pair.core = new RejectAuthCoreProvider();
pair.core.init(instance);
pair.core.init(instance, pair);
}
};
commands.put("resetauth", resetauth);
return commands;
}
private LauncherBinary binary() {
if (launcherEXEBinaryClass != null) {
public void checkCertificateExpired() {
if(!config.sign.enabled) {
return;
}
try {
return (LauncherBinary) MethodHandles.publicLookup().findConstructor(launcherEXEBinaryClass, MethodType.methodType(void.class, LaunchServer.class)).invoke(this);
KeyStore keyStore = SignHelper.getStore(Paths.get(config.sign.keyStore), config.sign.keyStorePass, config.sign.keyStoreType);
Instant date = SignHelper.getCertificateExpired(keyStore, config.sign.keyAlias);
if(date == null) {
logger.debug("The certificate will expire at unlimited");
} else if(date.minus(Duration.ofDays(30)).isBefore(Instant.now())) {
logger.warn("The certificate will expire at {}", date.toString());
} else {
logger.debug("The certificate will expire at {}", date.toString());
}
} catch (Throwable e) {
logger.error(e);
logger.error("Can't get certificate expire date", e);
}
}
try {
Class.forName("net.sf.launch4j.Builder");
if (config.launch4j.enabled) return new EXEL4JLauncherBinary(this);
} catch (ClassNotFoundException ignored) {
logger.warn("Launch4J isn't in classpath.");
private LauncherBinary binary() {
LaunchServerLauncherExeInit event = new LaunchServerLauncherExeInit(this, null);
modulesManager.invokeEvent(event);
if(event.binary != null) {
return event.binary;
}
return new EXELauncherBinary(this);
}
@ -304,12 +325,14 @@ public void close() throws Exception {
logger.info("LaunchServer stopped");
}
@Deprecated
public Set<ClientProfile> getProfiles() {
return profilesList;
return config.profileProvider.getProfiles();
}
@Deprecated
public void setProfiles(Set<ClientProfile> profilesList) {
this.profilesList = Collections.unmodifiableSet(profilesList);
throw new UnsupportedOperationException();
}
public void rebindNettyServerSocket() {
@ -332,17 +355,17 @@ public void run() {
}
}));
CommonHelper.newThread("Command Thread", true, commandHandler).start();
CommonHelper.newThread("Socket Command Thread", true, socketCommandServer).start();
// Sync updates dir
CommonHelper.newThread("Profiles and updates sync", true, () -> {
try {
if (!IOHelper.isDir(updatesDir))
Files.createDirectory(updatesDir);
updatesManager.readUpdatesDir();
// Sync profiles dir
if (!IOHelper.isDir(profilesDir))
Files.createDirectory(profilesDir);
syncProfilesDir();
// Sync updates dir
config.updatesProvider.syncInitially();
modulesManager.invokeEvent(new LaunchServerProfilesSyncEvent(this));
} catch (IOException e) {
logger.error("Updates/Profiles not synced", e);
@ -370,19 +393,14 @@ public void syncLauncherBinaries() throws IOException {
// Syncing launcher EXE binary
logger.info("Syncing launcher EXE binary file");
if (!launcherEXEBinary.sync() && config.launch4j.enabled)
if (!launcherEXEBinary.sync())
logger.warn("Missing launcher EXE binary file");
}
public void syncProfilesDir() throws IOException {
logger.info("Syncing profiles dir");
List<ClientProfile> newProfies = new LinkedList<>();
IOHelper.walk(profilesDir, new ProfilesFileVisitor(newProfies), false);
// Sort and set new profiles
newProfies.sort(Comparator.comparing(a -> a));
profilesList = Set.copyOf(newProfies);
config.profileProvider.sync();
if (config.netty.sendProfileUpdatesEvent) {
sendUpdateProfilesEvent();
}
@ -397,7 +415,7 @@ private void sendUpdateProfilesEvent() {
if (client == null || !client.isAuth) {
return;
}
ProfilesRequestEvent event = new ProfilesRequestEvent(ProfilesResponse.getListVisibleProfiles(this, client));
ProfilesRequestEvent event = new ProfilesRequestEvent(config.profileProvider.getProfiles(client));
event.requestUUID = RequestEvent.eventUUID;
handler.service.sendObject(ch, event);
});
@ -407,21 +425,6 @@ public void syncUpdatesDir(Collection<String> dirs) throws IOException {
updatesManager.syncUpdatesDir(dirs);
}
public void restart() {
ProcessBuilder builder = new ProcessBuilder();
if (config.startScript != null) builder.command(Collections.singletonList(config.startScript));
else throw new IllegalArgumentException("Please create start script and link it as startScript in config.");
builder.directory(this.dir.toFile());
builder.inheritIO();
builder.redirectErrorStream(true);
builder.redirectOutput(Redirect.PIPE);
try {
builder.start();
} catch (IOException e) {
logger.error("Restart failed", e);
}
}
public void registerObject(String name, Object object) {
if (object instanceof Reconfigurable) {
reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object);
@ -434,11 +437,6 @@ public void unregisterObject(String name, Object object) {
}
}
public void fullyRestart() {
restart();
JVMHelper.RUNTIME.exit(0);
}
public enum ReloadType {
NO_AUTH,
@ -463,57 +461,42 @@ public interface LaunchServerConfigManager {
void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException;
}
private static final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
private final Collection<ClientProfile> result;
private final Logger logger = LogManager.getLogger();
private ProfilesFileVisitor(Collection<ClientProfile> result) {
this.result = result;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
logger.info("Syncing '{}' profile", IOHelper.getFileName(file));
// Read profile
ClientProfile profile;
try (BufferedReader reader = IOHelper.newReader(file)) {
profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class);
}
profile.verify();
// Add SIGNED profile to result list
result.add(profile);
return super.visitFile(file, attrs);
}
}
public static class LaunchServerDirectories {
public static final String UPDATES_NAME = "updates", PROFILES_NAME = "profiles",
public static final String UPDATES_NAME = "updates",
TRUSTSTORE_NAME = "truststore", LAUNCHERLIBRARIES_NAME = "launcher-libraries",
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", LAUNCHERPACK_NAME = "launcher-pack", KEY_NAME = ".keys";
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", LAUNCHERPACK_NAME = "launcher-pack",
KEY_NAME = ".keys", MODULES = "modules", LAUNCHER_MODULES = "launcher-modules",
LIBRARIES = "libraries", CONTROL_FILE = "control-file", PROGUARD_DIR = "proguard-libraries";
public Path updatesDir;
public Path profilesDir;
public Path librariesDir;
public Path launcherLibrariesDir;
public Path launcherLibrariesCompileDir;
public Path launcherPackDir;
public Path keyDirectory;
public Path proguardDir;
public Path dir;
public Path trustStore;
public Path tmpDir;
public Path modules;
public Path launcherModules;
public Path controlFile;
public void collect() {
if (updatesDir == null) updatesDir = getPath(UPDATES_NAME);
if (profilesDir == null) profilesDir = getPath(PROFILES_NAME);
if (trustStore == null) trustStore = getPath(TRUSTSTORE_NAME);
if (launcherLibrariesDir == null) launcherLibrariesDir = getPath(LAUNCHERLIBRARIES_NAME);
if (launcherLibrariesCompileDir == null)
launcherLibrariesCompileDir = getPath(LAUNCHERLIBRARIESCOMPILE_NAME);
if(launcherPackDir == null)
if (launcherPackDir == null)
launcherPackDir = getPath(LAUNCHERPACK_NAME);
if (keyDirectory == null) keyDirectory = getPath(KEY_NAME);
if (modules == null) modules = getPath(MODULES);
if (launcherModules == null) launcherModules = getPath(LAUNCHER_MODULES);
if (librariesDir == null) librariesDir = getPath(LIBRARIES);
if (controlFile == null) controlFile = getPath(CONTROL_FILE);
if (proguardDir == null) proguardDir = getPath(PROGUARD_DIR);
if (tmpDir == null)
tmpDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve(String.format("launchserver-%s", SecurityHelper.randomStringToken()));
tmpDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve("launchserver-%s".formatted(SecurityHelper.randomStringToken()));
}
private Path getPath(String dirName) {

View file

@ -19,6 +19,7 @@ public class LaunchServerBuilder {
private KeyAgreementManager keyAgreementManager;
private CertificateManager certificateManager;
private LaunchServer.LaunchServerConfigManager launchServerConfigManager;
private Integer shardId;
public LaunchServerBuilder setConfig(LaunchServerConfig config) {
this.config = config;
@ -55,6 +56,11 @@ public LaunchServerBuilder setDir(Path dir) {
return this;
}
public LaunchServerBuilder setShardId(Integer shardId) {
this.shardId = shardId;
return this;
}
public LaunchServerBuilder setLaunchServerConfigManager(LaunchServer.LaunchServerConfigManager launchServerConfigManager) {
this.launchServerConfigManager = launchServerConfigManager;
return this;
@ -63,7 +69,27 @@ public LaunchServerBuilder setLaunchServerConfigManager(LaunchServer.LaunchServe
public LaunchServer build() throws Exception {
directories.collect();
if (launchServerConfigManager == null) {
launchServerConfigManager = new LaunchServer.LaunchServerConfigManager() {
launchServerConfigManager = new NullLaunchServerConfigManager();
}
if (keyAgreementManager == null) {
keyAgreementManager = new KeyAgreementManager(directories.keyDirectory);
}
if(shardId == null) {
shardId = Integer.parseInt(System.getProperty("launchserver.shardId", "0"));
}
return new LaunchServer(directories, env, config, runtimeConfig, launchServerConfigManager, modulesManager, keyAgreementManager, commandHandler, certificateManager, shardId);
}
public LaunchServerBuilder setCertificateManager(CertificateManager certificateManager) {
this.certificateManager = certificateManager;
return this;
}
public void setKeyAgreementManager(KeyAgreementManager keyAgreementManager) {
this.keyAgreementManager = keyAgreementManager;
}
private static class NullLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
@Override
public LaunchServerConfig readConfig() {
throw new UnsupportedOperationException();
@ -83,20 +109,5 @@ public void writeConfig(LaunchServerConfig config) {
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
throw new UnsupportedOperationException();
}
};
}
if (keyAgreementManager == null) {
keyAgreementManager = new KeyAgreementManager(directories.keyDirectory);
}
return new LaunchServer(directories, env, config, runtimeConfig, launchServerConfigManager, modulesManager, keyAgreementManager, commandHandler, certificateManager);
}
public LaunchServerBuilder setCertificateManager(CertificateManager certificateManager) {
this.certificateManager = certificateManager;
return this;
}
public void setKeyAgreementManager(KeyAgreementManager keyAgreementManager) {
this.keyAgreementManager = keyAgreementManager;
}
}

View file

@ -3,17 +3,20 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherTrustManager;
import pro.gravit.launcher.modules.events.PreConfigPhase;
import pro.gravit.launcher.profiles.optional.actions.OptionalAction;
import pro.gravit.launcher.profiles.optional.triggers.OptionalTrigger;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.core.LauncherTrustManager;
import pro.gravit.launcher.base.modules.events.PreConfigPhase;
import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction;
import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launcher.base.request.auth.GetAvailabilityAuthRequest;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.mix.MixProvider;
import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.auth.profiles.ProfileProvider;
import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.texture.TextureProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.components.Component;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
@ -28,14 +31,12 @@
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Security;
import java.security.cert.CertificateException;
import java.util.List;
public class LaunchServerStarter {
public static final boolean allowUnsigned = Boolean.getBoolean("launchserver.allowUnsigned");
@ -43,27 +44,25 @@ public class LaunchServerStarter {
private static final Logger logger = LogManager.getLogger();
public static void main(String[] args) throws Exception {
JVMHelper.checkStackTrace(LaunchServerStarter.class);
JVMHelper.verifySystemProperties(LaunchServer.class, true);
JVMHelper.verifySystemProperties(LaunchServer.class, false);
//LogHelper.addOutput(IOHelper.WORKING_DIR.resolve("LaunchServer.log"));
LogHelper.printVersion("LaunchServer");
LogHelper.printLicense("LaunchServer");
if (!StarterAgent.isAgentStarted()) {
LogHelper.error("StarterAgent is not started!");
LogHelper.error("You should add to JVM options this option: `-javaagent:LaunchServer.jar`");
}
Path dir = IOHelper.WORKING_DIR;
Path configFile, runtimeConfigFile;
try {
Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Security.addProvider(new BouncyCastleProvider());
} catch (ClassNotFoundException ex) {
} catch (ClassNotFoundException | NoClassDefFoundError ex) {
LogHelper.error("Library BouncyCastle not found! Is directory 'libraries' empty?");
return;
}
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
directories.dir = dir;
directories.collect();
CertificateManager certificateManager = new CertificateManager();
try {
certificateManager.readTrustStore(dir.resolve("truststore"));
certificateManager.readTrustStore(directories.trustStore);
} catch (CertificateException e) {
throw new IOException(e);
}
@ -84,11 +83,12 @@ public static void main(String[] args) throws Exception {
LaunchServerRuntimeConfig runtimeConfig;
LaunchServerConfig config;
LaunchServer.LaunchServerEnv env = LaunchServer.LaunchServerEnv.PRODUCTION;
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(dir.resolve("modules"), dir.resolve("config"), certificateManager.trustManager);
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(directories.modules, dir.resolve("config"), certificateManager.trustManager);
modulesManager.autoload();
modulesManager.initModules(null);
registerAll();
initGson(modulesManager);
printExperimentalBranch();
if (IOHelper.exists(dir.resolve("LaunchServer.conf"))) {
configFile = dir.resolve("LaunchServer.conf");
} else {
@ -127,49 +127,7 @@ public static void main(String[] args) throws Exception {
}
}
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new LaunchServer.LaunchServerConfigManager() {
@Override
public LaunchServerConfig readConfig() throws IOException {
LaunchServerConfig config1;
try (BufferedReader reader = IOHelper.newReader(configFile)) {
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
}
return config1;
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
LaunchServerRuntimeConfig config1;
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
}
return config1;
}
@Override
public void writeConfig(LaunchServerConfig config) throws IOException {
try (Writer writer = IOHelper.newWriter(configFile)) {
if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer);
} else {
logger.error("Error writing LaunchServer runtime config file. Gson is null");
}
}
}
@Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
try (Writer writer = IOHelper.newWriter(runtimeConfigFile)) {
if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer);
} else {
logger.error("Error writing LaunchServer runtime config file. Gson is null");
}
}
}
};
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
directories.dir = dir;
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new BasicLaunchServerConfigManager(configFile, runtimeConfigFile);
LaunchServer server = new LaunchServerBuilder()
.setDirectories(directories)
.setEnv(env)
@ -180,7 +138,24 @@ public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOExcept
.setLaunchServerConfigManager(launchServerConfigManager)
.setCertificateManager(certificateManager)
.build();
if (!prepareMode) {
List<String> allArgs = List.of(args);
boolean isPrepareMode = prepareMode || allArgs.contains("--prepare");
boolean isRunCommand = false;
String runCommand = null;
for(var e : allArgs) {
if(e.equals("--run")) {
isRunCommand = true;
continue;
}
if(isRunCommand) {
runCommand = e;
isRunCommand = false;
}
}
if(runCommand != null) {
localCommandHandler.eval(runCommand, false);
}
if (!isPrepareMode) {
server.run();
} else {
server.close();
@ -192,7 +167,6 @@ public static void initGson(LaunchServerModulesManager modulesManager) {
Launcher.gsonManager.initGson();
}
@SuppressWarnings("deprecation")
public static void registerAll() {
AuthCoreProvider.registerProviders();
PasswordVerifier.registerProviders();
@ -204,6 +178,29 @@ public static void registerAll() {
GetAvailabilityAuthRequest.registerProviders();
OptionalAction.registerProviders();
OptionalTrigger.registerProviders();
MixProvider.registerProviders();
ProfileProvider.registerProviders();
UpdatesProvider.registerProviders();
}
private static void printExperimentalBranch() {
try(Reader reader = IOHelper.newReader(IOHelper.getResourceURL("experimental-build.json"))) {
ExperimentalBuild info = Launcher.gsonManager.configGson.fromJson(reader, ExperimentalBuild.class);
if(info.features == null || info.features.isEmpty()) {
return;
}
logger.warn("This is experimental build. Please do not use this in production");
logger.warn("Experimental features: [{}]", String.join(",", info.features));
for(var e : info.info) {
logger.warn(e);
}
} catch (Throwable e) {
logger.warn("Build information not found");
}
}
record ExperimentalBuild(List<String> features, List<String> info) {
}
public static void generateConfigIfNotExists(Path configFile, CommandHandler commandHandler, LaunchServer.LaunchServerEnv env) throws IOException {
@ -226,7 +223,7 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
address = System.getProperty("launchserver.address", null);
}
if (address == null) {
System.out.println("LaunchServer address(default: localhost): ");
System.out.println("External launchServer address:port (default: localhost:9274): ");
address = commandHandler.readLine();
}
String projectName = System.getenv("PROJECTNAME");
@ -240,18 +237,29 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
newConfig.setProjectName(projectName);
}
if (address == null || address.isEmpty()) {
logger.error("Address null. Using localhost");
address = "localhost";
logger.error("Address null. Using localhost:9274");
address = "localhost:9274";
}
if (newConfig.projectName == null || newConfig.projectName.isEmpty()) {
logger.error("ProjectName null. Using MineCraft");
newConfig.projectName = "MineCraft";
}
newConfig.netty.address = "ws://" + address + ":9274/api";
newConfig.netty.downloadURL = "http://" + address + ":9274/%dirname%/";
newConfig.netty.launcherURL = "http://" + address + ":9274/Launcher.jar";
newConfig.netty.launcherEXEURL = "http://" + address + ":9274/Launcher.exe";
int port = 9274;
if(address.contains(":")) {
String portString = address.substring(address.indexOf(':')+1);
try {
port = Integer.parseInt(portString);
} catch (NumberFormatException e) {
logger.warn("Unknown port {}, using 9274", portString);
}
} else {
logger.info("Address {} doesn't contains port (you want to use nginx?)", address);
}
newConfig.netty.address = "ws://" + address + "/api";
newConfig.netty.downloadURL = "http://" + address + "/%dirname%/";
newConfig.netty.launcherURL = "http://" + address + "/Launcher.jar";
newConfig.netty.launcherEXEURL = "http://" + address + "/Launcher.exe";
newConfig.netty.binds[0].port = port;
// Write LaunchServer config
logger.info("Writing LaunchServer config file");
@ -259,4 +267,64 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
Launcher.gsonManager.configGson.toJson(newConfig, writer);
}
}
private static class BasicLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
private final Path configFile;
private final Path runtimeConfigFile;
public BasicLaunchServerConfigManager(Path configFile, Path runtimeConfigFile) {
this.configFile = configFile;
this.runtimeConfigFile = runtimeConfigFile;
}
@Override
public LaunchServerConfig readConfig() throws IOException {
LaunchServerConfig config1;
try (BufferedReader reader = IOHelper.newReader(configFile)) {
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
}
return config1;
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
LaunchServerRuntimeConfig config1;
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
}
return config1;
}
@Override
public void writeConfig(LaunchServerConfig config) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (Writer writer = IOHelper.newWriter(output)) {
if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer);
} else {
logger.error("Error writing LaunchServer config file. Gson is null");
}
}
byte[] bytes = output.toByteArray();
if(bytes.length > 0) {
IOHelper.write(configFile, bytes);
}
}
@Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (Writer writer = IOHelper.newWriter(output)) {
if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer);
} else {
logger.error("Error writing LaunchServer runtime config file. Gson is null");
}
}
byte[] bytes = output.toByteArray();
if(bytes.length > 0) {
IOHelper.write(runtimeConfigFile, bytes);
}
}
}
}

View file

@ -0,0 +1,96 @@
package pro.gravit.launchserver;
import pro.gravit.launchserver.holder.LaunchServerControlHolder;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.launch.ClassLoaderControl;
import pro.gravit.utils.launch.LaunchOptions;
import pro.gravit.utils.launch.ModuleLaunch;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class Main {
private static final List<String> classpathOnly = List.of("proguard", "progressbar", "kotlin");
private static final String LOG4J_PROPERTY = "log4j2.configurationFile";
private static final String DEBUG_PROPERTY = "launchserver.main.debug";
private static final String LIBRARIES_PROPERTY = "launchserver.dir.libraries";
private static boolean isClasspathOnly(Path path) {
var fileName = path.getFileName().toString();
for(var e : classpathOnly) {
if(fileName.contains(e)) {
return true;
}
}
return false;
}
private static void unpackLog4j() {
String log4jConfigurationFile = System.getProperty(LOG4J_PROPERTY);
if(log4jConfigurationFile == null) {
Path log4jConfigPath = Path.of("log4j2.xml");
if(!Files.exists(log4jConfigPath)) {
try(FileOutputStream output = new FileOutputStream(log4jConfigPath.toFile())) {
try(InputStream input = Main.class.getResourceAsStream("/log4j2.xml")) {
if(input == null) {
return;
}
input.transferTo(output);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
System.setProperty(LOG4J_PROPERTY, log4jConfigPath.toAbsolutePath().toString());
}
}
public static void main(String[] args) throws Throwable {
unpackLog4j();
ModuleLaunch launch = new ModuleLaunch();
LaunchOptions options = new LaunchOptions();
options.moduleConf = new LaunchOptions.ModuleConf();
Path librariesPath = Path.of(System.getProperty(LIBRARIES_PROPERTY, "libraries"));
List<Path> libraries;
try(Stream<Path> files = Files.walk(librariesPath, FileVisitOption.FOLLOW_LINKS)) {
libraries = new ArrayList<>(files.filter(e -> e.getFileName().toString().endsWith(".jar")).toList());
}
List<Path> classpath = new ArrayList<>();
List<String> modulepath = new ArrayList<>();
for(var l : libraries) {
if(isClasspathOnly(l)) {
classpath.add(l);
} else {
modulepath.add(l.toAbsolutePath().toString());
}
}
classpath.add(IOHelper.getCodeSource(LaunchServerStarter.class));
options.moduleConf.modulePath.addAll(modulepath);
options.moduleConf.modules.add("ALL-MODULE-PATH");
options.moduleConf.enableNativeAccess.add("org.fusesource.jansi");
options.moduleConf.enableNativeAccess.add("io.netty.common");
ClassLoaderControl control = launch.init(classpath, "natives", options);
control.clearLauncherPackages();
control.addLauncherPackage("pro.gravit.utils.launch");
control.addLauncherPackage("pro.gravit.launchserver.holder");
ModuleLayer.Controller controller = (ModuleLayer.Controller) control.getJava9ModuleController();
LaunchServerControlHolder.setControl(control);
LaunchServerControlHolder.setController(controller);
if(Boolean.getBoolean(DEBUG_PROPERTY)) {
for(var e : controller.layer().modules()) {
System.out.printf("Module %s\n", e.getName());
for(var p : e.getPackages()) {
System.out.printf("Package %s\n", p);
}
}
}
launch.launch("pro.gravit.launchserver.LaunchServerStarter", null, Arrays.asList(args));
}
}

View file

@ -1,13 +1,7 @@
package pro.gravit.launchserver;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.util.*;
import java.util.jar.JarFile;
public final class StarterAgent {
@ -20,47 +14,6 @@ public static boolean isAgentStarted() {
}
public static void premain(String agentArgument, Instrumentation inst) {
StarterAgent.inst = inst;
libraries = Paths.get(Optional.ofNullable(agentArgument).map(String::trim).filter(e -> !e.isEmpty()).orElse("libraries"));
isStarted = true;
try {
Files.walkFileTree(libraries, Collections.singleton(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new StarterVisitor());
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
private static final class StarterVisitor extends SimpleFileVisitor<Path> {
private static final Set<PosixFilePermission> DPERMS;
static {
Set<PosixFilePermission> perms = new HashSet<>(Arrays.asList(PosixFilePermission.values()));
perms.remove(PosixFilePermission.OTHERS_WRITE);
perms.remove(PosixFilePermission.GROUP_WRITE);
DPERMS = Collections.unmodifiableSet(perms);
}
private final boolean fixLib;
private StarterVisitor() {
Path filef = StarterAgent.libraries.resolve(".libraries_chmoded");
this.fixLib = !Files.exists(filef) && !Boolean.getBoolean("launcher.noLibrariesPosixPermsFix");
if (fixLib) {
try {
Files.deleteIfExists(filef);
Files.createFile(filef);
} catch (Throwable ignored) {
}
}
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (fixLib && Files.getFileAttributeView(file, PosixFileAttributeView.class) != null)
Files.setPosixFilePermissions(file, DPERMS);
if (file.toFile().getName().endsWith(".jar"))
inst.appendToSystemClassLoaderSearch(new JarFile(file.toFile()));
return super.visitFile(file, attrs);
}
throw new UnsupportedOperationException("Please remove -javaagent option from start.sh");
}
}

View file

@ -1,16 +1,17 @@
package pro.gravit.launchserver.asm;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import pro.gravit.utils.helper.IOHelper;
import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.*;
import java.util.jar.JarFile;
/**
@ -18,14 +19,32 @@
* чего угодно. Работает через поиск class-файлов в classpath.
*/
public class ClassMetadataReader implements Closeable {
private final Logger logger = LogManager.getLogger(ClassMetadataReader.class);
private final List<JarFile> cp;
private final Map<String, Module> moduleClassFinder;
public ClassMetadataReader(List<JarFile> cp) {
this.cp = cp;
//var moduleLayer = ClassMetadataReader.class.getModule().getLayer() == null ? ModuleLayer.boot() : ClassMetadataReader.class.getModule().getLayer();
var moduleLayer = ModuleLayer.boot();
moduleClassFinder = collectModulePackages(moduleLayer);
}
public ClassMetadataReader() {
this.cp = new ArrayList<>();
//var moduleLayer = ClassMetadataReader.class.getModule().getLayer() == null ? ModuleLayer.boot() : ClassMetadataReader.class.getModule().getLayer();
var moduleLayer = ModuleLayer.boot();
moduleClassFinder = collectModulePackages(moduleLayer);
}
private Map<String, Module> collectModulePackages(ModuleLayer layer) {
var map = new HashMap<String, Module>();
for(var m : layer.modules()) {
for(var p : m.getPackages()) {
map.put(p, m);
}
}
return map;
}
public List<JarFile> getCp() {
@ -58,7 +77,42 @@ public byte[] getClassData(String className) throws IOException {
return bytes;
}
}
return IOHelper.read(IOHelper.getResourceURL(className + ".class"));
if(ClassMetadataReader.class.getModule().isNamed()) {
String pkg = getClassPackage(className).replace('/', '.');
var module = moduleClassFinder.get(pkg);
if(module != null) {
var cl = module.getClassLoader();
if(cl == null) {
cl = ClassLoader.getPlatformClassLoader();
}
var stream = cl.getResourceAsStream(className+".class");
if(stream != null) {
try(stream) {
return IOHelper.read(stream);
}
} else {
throw new FileNotFoundException("Class "+className + ".class");
}
} else {
throw new FileNotFoundException("Package "+pkg);
}
}
var stream = ClassLoader.getSystemClassLoader().getResourceAsStream(className+".class");
if(stream != null) {
try(stream) {
return IOHelper.read(stream);
}
} else {
throw new FileNotFoundException(className + ".class");
}
}
private String getClassPackage(String type) {
int idx = type.lastIndexOf("/");
if(idx <= 0) {
return type;
}
return type.substring(0, idx);
}
public String getSuperClass(String type) {
@ -66,6 +120,7 @@ public String getSuperClass(String type) {
try {
return getSuperClassASM(type);
} catch (Exception e) {
logger.warn("getSuperClass: type {} not found ({}: {})", type, e.getClass().getName(), e.getMessage());
return "java/lang/Object";
}
}
@ -100,7 +155,7 @@ private static class CheckSuperClassVisitor extends ClassVisitor {
String superClassName;
public CheckSuperClassVisitor() {
super(Opcodes.ASM7);
super(Opcodes.ASM9);
}
@Override

View file

@ -4,14 +4,13 @@
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import pro.gravit.launcher.LauncherInject;
import pro.gravit.launcher.LauncherInjectionConstructor;
import pro.gravit.launcher.core.LauncherInject;
import pro.gravit.launcher.core.LauncherInjectionConstructor;
import pro.gravit.launchserver.binary.BuildContext;
import pro.gravit.launchserver.binary.tasks.MainBuildTask;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
@SuppressWarnings("rawtypes")
public class InjectClassAcceptor implements MainBuildTask.ASMTransformer {
@ -65,7 +64,7 @@ private static void visit(ClassNode classNode, Map<String, Object> values) {
return newClinitMethod;
});
List<MethodNode> constructors = classNode.methods.stream().filter(method -> "<init>".equals(method.name))
.collect(Collectors.toList());
.toList();
MethodNode initMethod = constructors.stream().filter(method -> method.invisibleAnnotations != null
&& method.invisibleAnnotations.stream().anyMatch(annotation -> INJECTED_CONSTRUCTOR_DESC.equals(annotation.desc))).findFirst()
.orElseGet(() -> constructors.stream().filter(method -> method.desc.equals("()V")).findFirst().orElse(null));
@ -92,7 +91,7 @@ public void visit(final String name, final Object value) {
if ("value".equals(name)) {
if (value.getClass() != String.class)
throw new IllegalArgumentException(
String.format("Invalid annotation with value class %s", field.getClass().getName()));
"Invalid annotation with value class %s".formatted(field.getClass().getName()));
valueName.set(value.toString());
}
}
@ -112,7 +111,7 @@ public void visit(final String name, final Object value) {
}
List<FieldInsnNode> putStaticNodes = Arrays.stream(initMethod.instructions.toArray())
.filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTSTATIC).map(p -> (FieldInsnNode) p)
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).collect(Collectors.toList());
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).toList();
InsnList setter = serializeValue(value);
if (putStaticNodes.isEmpty()) {
setter.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, field.name, field.desc));
@ -126,11 +125,11 @@ public void visit(final String name, final Object value) {
}
} else {
if (initMethod == null) {
throw new IllegalArgumentException(String.format("Not found init in target: %s", classNode.name));
throw new IllegalArgumentException("Not found init in target: %s".formatted(classNode.name));
}
List<FieldInsnNode> putFieldNodes = Arrays.stream(initMethod.instructions.toArray())
.filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTFIELD).map(p -> (FieldInsnNode) p)
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).collect(Collectors.toList());
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).toList();
InsnList setter = serializeValue(value);
if (putFieldNodes.isEmpty()) {
setter.insert(new VarInsnNode(Opcodes.ALOAD, 0));
@ -173,8 +172,7 @@ private static InsnList serializeValue(Object value) {
return ((Serializer) serializerEntry.getValue()).serialize(value);
}
}
throw new UnsupportedOperationException(String.format("Serialization of type %s is not supported",
value.getClass()));
throw new UnsupportedOperationException("Serialization of type %s is not supported".formatted(value.getClass()));
}
public static boolean isSerializableValue(Object value) {

View file

@ -149,10 +149,7 @@ public static int opcodeEmulation(AbstractInsnNode e) {
break;
case INVOKEVIRTUAL:
case INVOKESPECIAL:
case INVOKEINTERFACE:
stackSize += doMethodEmulation(((MethodInsnNode) e).desc);
break;
case INVOKESTATIC:
case INVOKEINTERFACE, INVOKESTATIC:
stackSize += doMethodEmulation(((MethodInsnNode) e).desc);
break;
case INVOKEDYNAMIC:

View file

@ -1,12 +1,14 @@
package pro.gravit.launchserver.auth;
import pro.gravit.launcher.events.request.AuthRequestEvent;
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
import java.io.IOException;
import java.io.Serial;
import java.util.List;
import java.util.stream.Collectors;
public final class AuthException extends IOException {
@Serial
private static final long serialVersionUID = -2586107832847245863L;
@ -14,6 +16,10 @@ public AuthException(String message) {
super(message);
}
public AuthException(String message, Throwable cause) {
super(message, cause);
}
public static AuthException need2FA() {
return new AuthException(AuthRequestEvent.TWO_FACTOR_NEED_ERROR_MESSAGE);
}

View file

@ -4,8 +4,7 @@
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.core.MySQLCoreProvider;
import pro.gravit.launchserver.auth.core.PostgresSQLCoreProvider;
import pro.gravit.launchserver.auth.mix.MixProvider;
import pro.gravit.launchserver.auth.texture.TextureProvider;
import java.io.IOException;
@ -18,11 +17,12 @@ public final class AuthProviderPair {
public boolean isDefault = true;
public AuthCoreProvider core;
public TextureProvider textureProvider;
public Map<String, MixProvider> mixes;
public Map<String, String> links;
public transient String name;
public transient Set<String> features;
public String displayName;
private transient boolean warnOAuthShow = false;
public boolean visible = true;
public AuthProviderPair() {
}
@ -38,12 +38,14 @@ public static Set<String> getFeatures(Class<?> clazz) {
return list;
}
public static void getFeatures(Class<?> clazz, Set<String> list) {
Features features = clazz.getAnnotation(Features.class);
if (features != null) {
for (Feature feature : features.value()) {
list.add(feature.value());
public Set<String> getFeatures() {
return features;
}
public static void getFeatures(Class<?> clazz, Set<String> list) {
Feature[] features = clazz.getAnnotationsByType(Feature.class);
for (Feature feature : features) {
list.add(feature.value());
}
Class<?> superClass = clazz.getSuperclass();
if (superClass != null && superClass != Object.class) {
@ -55,48 +57,57 @@ public static void getFeatures(Class<?> clazz, Set<String> list) {
}
}
public void internalShowOAuthWarnMessage() {
if (!warnOAuthShow) {
if (!(core instanceof MySQLCoreProvider) && !(core instanceof PostgresSQLCoreProvider)) { // MySQL and PostgreSQL upgraded later
logger.warn("AuthCoreProvider {} ({}) not supported OAuth. Legacy session system may be removed in next release", name, core.getClass().getName());
}
warnOAuthShow = true;
}
}
public final <T> T isSupport(Class<T> clazz) {
public <T> T isSupport(Class<T> clazz) {
if (core == null) return null;
T result = null;
if (result == null) result = core.isSupport(clazz);
T result = core.isSupport(clazz);
if (result == null && mixes != null) {
for(var m : mixes.values()) {
result = m.isSupport(clazz);
if(result != null) {
break;
}
}
}
return result;
}
public final void init(LaunchServer srv, String name) {
public void init(LaunchServer srv, String name) {
this.name = name;
if (links != null) link(srv);
core.init(srv);
core.init(srv, this);
features = new HashSet<>();
getFeatures(core.getClass(), features);
if(mixes != null) {
for(var m : mixes.values()) {
m.init(srv, core);
getFeatures(m.getClass(), features);
}
}
}
public final void link(LaunchServer srv) {
public void link(LaunchServer srv) {
links.forEach((k, v) -> {
AuthProviderPair pair = srv.config.getAuthProviderPair(v);
if (pair == null) {
throw new NullPointerException(String.format("Auth %s link failed. Pair %s not found", name, v));
throw new NullPointerException("Auth %s link failed. Pair %s not found".formatted(name, v));
}
if ("core".equals(k)) {
if (pair.core == null)
throw new NullPointerException(String.format("Auth %s link failed. %s.core is null", name, v));
throw new NullPointerException("Auth %s link failed. %s.core is null".formatted(name, v));
core = pair.core;
}
});
}
public final void close() throws IOException {
public void close() throws IOException {
core.close();
if (textureProvider != null) {
textureProvider.close();
}
if(mixes != null) {
for(var m : mixes.values()) {
m.close();
}
}
}
}

View file

@ -0,0 +1,62 @@
package pro.gravit.launchserver.auth;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import java.util.function.Consumer;
public class HikariSQLSourceConfig implements SQLSourceConfig {
private transient volatile HikariDataSource dataSource;
private String dsClass;
private Properties dsProps;
private String driverClass;
private String jdbcUrl;
private String username;
private String password;
private boolean initializeAtStart;
public void init() {
if(initializeAtStart) {
initializeConnection();
}
}
private void initializeConnection() {
if (dataSource != null) {
return;
}
HikariConfig config = new HikariConfig();
consumeIfNotNull(config::setDataSourceClassName, dsClass);
consumeIfNotNull(config::setDataSourceProperties, dsProps);
consumeIfNotNull(config::setDriverClassName, driverClass);
consumeIfNotNull(config::setJdbcUrl, jdbcUrl);
consumeIfNotNull(config::setUsername, username);
consumeIfNotNull(config::setPassword, password);
this.dataSource = new HikariDataSource(config);
}
@Override
public Connection getConnection() throws SQLException {
if(dataSource == null && !initializeAtStart) {
synchronized (this) {
initializeConnection();
}
}
return dataSource.getConnection();
}
@Override
public void close() {
dataSource.close();
}
private static <T> void consumeIfNotNull(Consumer<T> consumer, T val) {
if (val != null) {
consumer.accept(val);
}
}
}

View file

@ -11,6 +11,8 @@
import java.sql.Connection;
import java.sql.SQLException;
import static java.util.concurrent.TimeUnit.MINUTES;
public final class MySQLSourceConfig implements AutoCloseable, SQLSourceConfig {
public static final int TIMEOUT = VerifyHelper.verifyInt(
@ -33,6 +35,7 @@ public final class MySQLSourceConfig implements AutoCloseable, SQLSourceConfig {
private String password;
private String database;
private String timezone;
private long hikariMaxLifetime = MINUTES.toMillis(30);
private boolean useHikari;
// Cache
@ -108,8 +111,8 @@ public synchronized Connection getConnection() throws SQLException {
hikariConfig.setMaximumPoolSize(MAX_POOL_SIZE);
hikariConfig.setConnectionTestQuery("SELECT 1");
hikariConfig.setConnectionTimeout(1000);
hikariConfig.setAutoCommit(true);
hikariConfig.setLeakDetectionThreshold(2000);
hikariConfig.setMaxLifetime(hikariMaxLifetime);
// Set HikariCP pool
// Replace source with hds
source = new HikariDataSource(hikariConfig);

View file

@ -10,6 +10,9 @@
import java.sql.Connection;
import java.sql.SQLException;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
public final class PostgreSQLSourceConfig implements AutoCloseable, SQLSourceConfig {
public static final int TIMEOUT = VerifyHelper.verifyInt(
Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.idleTimeout", Integer.toString(5000))),
@ -27,9 +30,11 @@ public final class PostgreSQLSourceConfig implements AutoCloseable, SQLSourceCon
private String password;
private String database;
private long hikariMaxLifetime = MINUTES.toMillis(30); // 30 minutes
// Cache
private DataSource source;
private boolean hikari;
private transient DataSource source;
private transient boolean hikari;
@Override
public synchronized void close() {
@ -65,7 +70,8 @@ public synchronized Connection getConnection() throws SQLException {
hikariSource.setPoolName(poolName);
hikariSource.setMinimumIdle(0);
hikariSource.setMaximumPoolSize(MAX_POOL_SIZE);
hikariSource.setIdleTimeout(TIMEOUT * 1000L);
hikariSource.setIdleTimeout(SECONDS.toMillis(TIMEOUT));
hikariSource.setMaxLifetime(hikariMaxLifetime);
// Replace source with hds
source = hikariSource;

View file

@ -4,16 +4,19 @@
import io.jsonwebtoken.JwtException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launcher.base.request.auth.password.AuthPlainPassword;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportSudo;
import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.helper.LegacySessionHelper;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.SecurityHelper;
@ -28,9 +31,12 @@
import java.util.List;
import java.util.UUID;
public abstract class AbstractSQLCoreProvider extends AuthCoreProvider {
public transient Logger logger = LogManager.getLogger();
public int expireSeconds = 3600;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.SECONDS;
public abstract class AbstractSQLCoreProvider extends AuthCoreProvider implements AuthSupportSudo {
public final transient Logger logger = LogManager.getLogger();
public long expireSeconds = HOURS.toSeconds(1);
public String uuidColumn;
public String usernameColumn;
public String accessTokenColumn;
@ -62,7 +68,6 @@ public abstract class AbstractSQLCoreProvider extends AuthCoreProvider {
public transient String updateAuthSQL;
public transient String updateServerIDSQL;
public transient LaunchServer server;
public abstract SQLSourceConfig getSQLConfig();
@ -104,7 +109,7 @@ public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws O
if (user == null) {
return null;
}
return new SQLUserSession(user);
return createSession(user);
} catch (ExpiredJwtException e) {
throw new OAuthAccessTokenExpired();
} catch (JwtException e) {
@ -129,39 +134,67 @@ public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthRespon
return null;
}
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
return new AuthManager.AuthReport(null, accessToken, refreshToken, expireSeconds * 1000L, new SQLUserSession(user));
return new AuthManager.AuthReport(null, accessToken, refreshToken, SECONDS.toMillis(expireSeconds), createSession(user));
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
SQLUser SQLUser = (SQLUser) getUserByLogin(login);
if (SQLUser == null) {
throw AuthException.wrongPassword();
SQLUser user = (SQLUser) getUserByLogin(login);
if (user == null) {
throw AuthException.userNotFound();
}
if (context != null) {
AuthPlainPassword plainPassword = (AuthPlainPassword) password;
if (plainPassword == null) {
throw AuthException.wrongPassword();
}
if (!passwordVerifier.check(SQLUser.password, plainPassword.password)) {
if (!passwordVerifier.check(user.password, plainPassword.password)) {
throw AuthException.wrongPassword();
}
}
SQLUserSession session = new SQLUserSession(SQLUser);
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(SQLUser, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
var refreshToken = SQLUser.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(SQLUser.username, SQLUser.password, server.keyAgreementManager.legacySalt));
SQLUserSession session = createSession(user);
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
var refreshToken = user.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(user.username, user.password, server.keyAgreementManager.legacySalt));
if (minecraftAccess) {
String minecraftAccessToken = SecurityHelper.randomStringToken();
updateAuth(SQLUser, minecraftAccessToken);
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session);
updateAuth(user, minecraftAccessToken);
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, SECONDS.toMillis(expireSeconds), session);
} else {
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session);
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, SECONDS.toMillis(expireSeconds), session);
}
}
@Override
public void init(LaunchServer server) {
this.server = server;
public AuthManager.AuthReport sudo(User user, boolean shadow) throws IOException {
SQLUser sqlUser = (SQLUser) user;
SQLUserSession session = createSession(sqlUser);
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(sqlUser, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
var refreshToken = sqlUser.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(sqlUser.username, sqlUser.password, server.keyAgreementManager.legacySalt));
String minecraftAccessToken = SecurityHelper.randomStringToken();
updateAuth(user, minecraftAccessToken);
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, SECONDS.toMillis(expireSeconds), session);
}
@Override
public User checkServer(Client client, String username, String serverID) {
SQLUser user = (SQLUser) getUserByUsername(username);
if (user == null) {
return null;
}
if (user.getUsername().equals(username) && user.getServerId().equals(serverID)) {
return user;
}
return null;
}
@Override
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) throws IOException {
SQLUser user = (SQLUser) client.getUser();
if (user == null) return false;
return (uuid == null ? user.getUsername().equals(username) : user.getUUID().equals(uuid)) && user.getAccessToken().equals(accessToken) && updateServerID(user, serverID);
}
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
super.init(server, pair);
if (getSQLConfig() == null) logger.error("SQLHolder cannot be null");
if (uuidColumn == null) logger.error("uuidColumn cannot be null");
if (usernameColumn == null) logger.error("usernameColumn cannot be null");
@ -169,21 +202,21 @@ public void init(LaunchServer server) {
if (serverIDColumn == null) logger.error("serverIDColumn cannot be null");
if (table == null) logger.error("table cannot be null");
// Prepare SQL queries
String userInfoCols = String.format("%s, %s, %s, %s, %s", uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn);
queryByUUIDSQL = customQueryByUUIDSQL != null ? customQueryByUUIDSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", userInfoCols,
table, uuidColumn);
queryByUsernameSQL = customQueryByUsernameSQL != null ? customQueryByUsernameSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1",
userInfoCols, table, usernameColumn);
String userInfoCols = makeUserCols();
queryByUUIDSQL = customQueryByUUIDSQL != null ? customQueryByUUIDSQL :
"SELECT %s FROM %s WHERE %s=? LIMIT 1".formatted(userInfoCols, table, uuidColumn);
queryByUsernameSQL = customQueryByUsernameSQL != null ? customQueryByUsernameSQL :
"SELECT %s FROM %s WHERE %s=? LIMIT 1".formatted(userInfoCols, table, usernameColumn);
queryByLoginSQL = customQueryByLoginSQL != null ? customQueryByLoginSQL : queryByUsernameSQL;
updateAuthSQL = customUpdateAuthSQL != null ? customUpdateAuthSQL : String.format("UPDATE %s SET %s=?, %s=NULL WHERE %s=?",
table, accessTokenColumn, serverIDColumn, uuidColumn);
updateServerIDSQL = customUpdateServerIdSQL != null ? customUpdateServerIdSQL : String.format("UPDATE %s SET %s=? WHERE %s=?",
table, serverIDColumn, uuidColumn);
updateAuthSQL = customUpdateAuthSQL != null ? customUpdateAuthSQL :
"UPDATE %s SET %s=?, %s=NULL WHERE %s=?".formatted(table, accessTokenColumn, serverIDColumn, uuidColumn);
updateServerIDSQL = customUpdateServerIdSQL != null ? customUpdateServerIdSQL :
"UPDATE %s SET %s=? WHERE %s=?".formatted(table, serverIDColumn, uuidColumn);
if (isEnabledPermissions()) {
if(isEnabledRoles()) {
queryPermissionsByUUIDSQL = customQueryPermissionsByUUIDSQL != null ? customQueryPermissionsByUUIDSQL :
@ -198,13 +231,17 @@ public void init(LaunchServer server) {
"INNER JOIN " + permissionsTable + " pr ON r." + rolesUUIDColumn + "=substring(pr." + permissionsPermissionColumn + " from 6) or r." + rolesNameColumn + "=substring(pr." + permissionsPermissionColumn + " from 6)\n" +
"WHERE pr." + permissionsUUIDColumn + " = ?";
} else {
queryPermissionsByUUIDSQL = customQueryPermissionsByUUIDSQL != null ? customQueryPermissionsByUUIDSQL : String.format("SELECT (%s) FROM %s WHERE %s=?",
permissionsPermissionColumn, permissionsTable, permissionsUUIDColumn);
queryPermissionsByUUIDSQL = customQueryPermissionsByUUIDSQL != null ? customQueryPermissionsByUUIDSQL :
"SELECT (%s) FROM %s WHERE %s=?".formatted(permissionsPermissionColumn, permissionsTable, permissionsUUIDColumn);
}
}
}
protected boolean updateAuth(User user, String accessToken) throws IOException {
protected String makeUserCols() {
return "%s, %s, %s, %s, %s".formatted(uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn);
}
protected void updateAuth(User user, String accessToken) throws IOException {
try (Connection c = getSQLConfig().getConnection()) {
SQLUser SQLUser = (SQLUser) user;
SQLUser.accessToken = accessToken;
@ -212,13 +249,12 @@ protected boolean updateAuth(User user, String accessToken) throws IOException {
s.setString(1, accessToken);
s.setString(2, user.getUUID().toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
s.executeUpdate();
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override
protected boolean updateServerID(User user, String serverID) throws IOException {
try (Connection c = getSQLConfig().getConnection()) {
SQLUser SQLUser = (SQLUser) user;
@ -234,13 +270,13 @@ protected boolean updateServerID(User user, String serverID) throws IOException
}
@Override
public void close() throws IOException {
public void close() {
getSQLConfig().close();
}
private SQLUser constructUser(ResultSet set) throws SQLException {
protected SQLUser constructUser(ResultSet set) throws SQLException {
return set.next() ? new SQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), requestPermissions(set.getString(uuidColumn))) : null;
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn)) : null;
}
public ClientPermissions requestPermissions (String uuid) throws SQLException
@ -250,14 +286,19 @@ public ClientPermissions requestPermissions (String uuid) throws SQLException
}
private SQLUser queryUser(String sql, String value) throws SQLException {
SQLUser user;
try (Connection c = getSQLConfig().getConnection()) {
PreparedStatement s = c.prepareStatement(sql);
s.setString(1, value);
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
return constructUser(set);
user = constructUser(set);
}
}
if(user != null) {
user.permissions = requestPermissions(user.uuid.toString());
}
return user;
}
private List<String> queryPermissions(String sql, String value) throws SQLException {
@ -273,6 +314,10 @@ private List<String> queryPermissions(String sql, String value) throws SQLExcept
}
}
protected SQLUserSession createSession(SQLUser user) {
return new SQLUserSession(user);
}
public boolean isEnabledPermissions() {
return permissionsPermissionColumn != null;
}
@ -295,20 +340,19 @@ private List<String> queryRolesNames(String sql, String value) throws SQLExcepti
}
public static class SQLUser implements User {
protected UUID uuid;
protected String username;
protected final UUID uuid;
protected final String username;
protected String accessToken;
protected String serverId;
protected String password;
protected final String password;
protected ClientPermissions permissions;
public SQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions) {
public SQLUser(UUID uuid, String username, String accessToken, String serverId, String password) {
this.uuid = uuid;
this.username = username;
this.accessToken = accessToken;
this.serverId = serverId;
this.password = password;
this.permissions = permissions;
}
@Override
@ -321,12 +365,10 @@ public UUID getUUID() {
return uuid;
}
@Override
public String getServerId() {
return serverId;
}
@Override
public String getAccessToken() {
return accessToken;
}
@ -365,6 +407,11 @@ public User getUser() {
return user;
}
@Override
public String getMinecraftAccessToken() {
return user.getAccessToken();
}
@Override
public long getExpireIn() {
return 0;

View file

@ -3,20 +3,25 @@
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.details.AuthPasswordDetails;
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
import pro.gravit.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.base.profiles.PlayerProfile;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launcher.base.request.auth.details.AuthPasswordDetails;
import pro.gravit.launcher.base.request.auth.password.AuthPlainPassword;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.Reconfigurable;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportGetAllUsers;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportRegistration;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportSudo;
import pro.gravit.launchserver.auth.core.openid.OpenIDAuthCoreProvider;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
@ -30,6 +35,7 @@
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
/*
All-In-One provider
@ -38,6 +44,8 @@ public abstract class AuthCoreProvider implements AutoCloseable, Reconfigurable
public static final ProviderMap<AuthCoreProvider> providers = new ProviderMap<>("AuthCoreProvider");
private static final Logger logger = LogManager.getLogger();
private static boolean registredProviders = false;
protected transient LaunchServer server;
protected transient AuthProviderPair pair;
public static void registerProviders() {
if (!registredProviders) {
@ -45,7 +53,9 @@ public static void registerProviders() {
providers.register("mysql", MySQLCoreProvider.class);
providers.register("postgresql", PostgresSQLCoreProvider.class);
providers.register("memory", MemoryAuthCoreProvider.class);
providers.register("http", HttpAuthCoreProvider.class);
providers.register("merge", MergeAuthCoreProvider.class);
providers.register("openid", OpenIDAuthCoreProvider.class);
providers.register("sql", SQLCoreProvider.class);
registredProviders = true;
}
}
@ -72,11 +82,9 @@ public AuthManager.AuthReport authorize(User user, AuthResponse.AuthContext cont
return authorize(user.getUsername(), context, password, minecraftAccess);
}
public abstract void init(LaunchServer server);
// Auth Handler methods
protected boolean updateServerID(User user, String serverID) throws IOException {
throw new UnsupportedOperationException();
public void init(LaunchServer server, AuthProviderPair pair) {
this.server = server;
this.pair = pair;
}
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
@ -139,7 +147,7 @@ public void invoke(String... args) throws Exception {
if (instance != null) {
map.put("getallusers", new SubCommand("(limit)", "print all users information") {
@Override
public void invoke(String... args) throws Exception {
public void invoke(String... args) {
int max = Integer.MAX_VALUE;
if (args.length > 0) max = Integer.parseInt(args[0]);
Iterable<User> users = instance.getAllUsers();
@ -181,28 +189,6 @@ public void invoke(String... args) throws Exception {
}
}
});
map.put("getuserhardware", new SubCommand("[username]", "get hardware by username") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
User user = getUserByUUID(UUID.fromString(args[0]));
if (user == null) {
logger.info("User {} not found", args[0]);
}
UserSupportHardware hardware = instance.fetchUserHardware(user);
if (hardware == null) {
logger.error("Method fetchUserHardware return null");
return;
}
UserHardware userHardware = hardware.getHardware();
if (userHardware == null) {
logger.info("User {} not contains hardware info", args[0]);
} else {
logger.info("UserHardware: {}", userHardware);
logger.info("HardwareInfo(JSON): {}", Launcher.gsonManager.gson.toJson(userHardware.getHardwareInfo()));
}
}
});
map.put("findmulti", new SubCommand("[hardware id]", "get all users in one hardware id") {
@Override
public void invoke(String... args) throws Exception {
@ -288,25 +274,78 @@ public void invoke(String... args) throws Exception {
});
}
}
{
var instance = isSupport(AuthSupportSudo.class);
if(instance != null) {
map.put("sudo", new SubCommand("[connectUUID] [username/uuid] [isShadow] (CLIENT/API)", "Authorize connectUUID as another user without password") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 3);
UUID connectUUID = UUID.fromString(args[0]);
String login = args[1];
boolean isShadow = Boolean.parseBoolean(args[2]);
AuthResponse.ConnectTypes type;
if(args.length > 3) {
type = AuthResponse.ConnectTypes.valueOf(args[3]);
} else {
type = AuthResponse.ConnectTypes.CLIENT;
}
User user;
if(login.length() == 36) {
UUID uuid = UUID.fromString(login);
user = getUserByUUID(uuid);
} else {
user = getUserByUsername(login);
}
if(user == null) {
logger.error("User {} not found", login);
return;
}
AtomicBoolean founded = new AtomicBoolean();
server.nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((ch, fh) -> {
var client = fh.getClient();
if(client == null || !connectUUID.equals(fh.getConnectUUID())) {
return;
}
logger.info("Found connectUUID {} with IP {}", fh.getConnectUUID(), fh.context == null ? "null" : fh.context.ip);
var lock = server.config.netty.performance.disableThreadSafeClientObject ? null : client.writeLock();
if(lock != null) {
lock.lock();
}
try {
var report = instance.sudo(user, isShadow);
User user1 = report.session().getUser();
server.authManager.internalAuth(client, type, pair, user1.getUsername(), user1.getUUID(), user1.getPermissions(), true);
client.sessionObject = report.session();
client.coreObject = report.session().getUser();
PlayerProfile playerProfile = server.authManager.getPlayerProfile(client);
AuthRequestEvent request = new AuthRequestEvent(user1.getPermissions(), playerProfile,
report.minecraftAccessToken(), null, null,
new AuthRequestEvent.OAuthRequestEvent(report.oauthAccessToken(), report.oauthRefreshToken(), report.oauthExpire()));
request.requestUUID = RequestEvent.eventUUID;
server.nettyServerSocketHandler.nettyServer.service.sendObject(ch, request);
} catch (Throwable e) {
logger.error("Sudo error", e);
} finally {
if(lock != null) {
lock.unlock();
}
founded.set(true);
}
});
if(!founded.get()) {
logger.error("ConnectUUID {} not found", connectUUID);
}
}
});
}
}
return map;
}
public User checkServer(Client client, String username, String serverID) throws IOException {
User user = getUserByUsername(username);
if (user == null) {
return null;
}
if (user.getUsername().equals(username) && user.getServerId().equals(serverID)) {
return user;
}
return null;
}
public abstract User checkServer(Client client, String username, String serverID) throws IOException;
public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException {
User user = client.getUser();
if (user == null) return false;
return user.getUsername().equals(username) && user.getAccessToken().equals(accessToken) && updateServerID(user, serverID);
}
public abstract boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) throws IOException;
@SuppressWarnings("unchecked")
public <T> T isSupport(Class<T> clazz) {
@ -315,7 +354,7 @@ public <T> T isSupport(Class<T> clazz) {
}
@Override
public abstract void close() throws IOException;
public abstract void close();
public static class PasswordVerifyReport {
public static final PasswordVerifyReport REQUIRED_2FA = new PasswordVerifyReport(-1);

View file

@ -1,668 +0,0 @@
package pro.gravit.launchserver.auth.core;
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.events.request.AuthRequestEvent;
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.profiles.Texture;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.HttpRequester;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportRemoteClientAccess;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportProperties;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportTextures;
import pro.gravit.launchserver.helper.HttpHelper;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.CommonHelper;
import java.io.IOException;
import java.util.*;
public class HttpAuthCoreProvider extends AuthCoreProvider implements AuthSupportHardware, AuthSupportRemoteClientAccess {
private transient final Logger logger = LogManager.getLogger();
public String bearerToken;
public String getUserByUsernameUrl;
public String getUserByLoginUrl;
public String getUserByUUIDUrl;
public String getUserByTokenUrl;
public String getAuthDetailsUrl;
public String refreshTokenUrl;
public String authorizeUrl;
public String joinServerUrl;
public String checkServerUrl;
public String updateServerIdUrl;
//below fields can be empty if advanced protect handler disabled
public String getHardwareInfoByPublicKeyUrl;
public String getHardwareInfoByDataUrl;
public String getHardwareInfoByIdUrl;
public String createHardwareInfoUrl;
public String connectUserAndHardwareUrl;
public String addPublicKeyToHardwareInfoUrl;
public String getUsersByHardwareInfoUrl;
public String banHardwareUrl;
public String unbanHardwareUrl;
public String apiUrl;
public List<String> apiFeatures;
private transient HttpRequester requester;
@Override
public User getUserByUsername(String username) {
try {
return requester.send(requester.get(CommonHelper.replace(getUserByUsernameUrl, "username", username), null), HttpUser.class).getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public User getUserByLogin(String login) {
if (getUserByLoginUrl != null) {
try {
return requester.send(requester.get(CommonHelper.replace(getUserByLoginUrl, "login", login), null), HttpUser.class).getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
return super.getUserByLogin(login);
}
@Override
public User getUserByUUID(UUID uuid) {
try {
return requester.send(requester.get(CommonHelper.replace(getUserByUUIDUrl, "uuid", uuid.toString()), null), HttpUser.class).getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
if (getAuthDetailsUrl == null) {
return super.getDetails(client);
}
try {
var result = requester.send(requester.get(getAuthDetailsUrl, bearerToken), GetAuthDetailsResponse.class).getOrThrow();
return result.details;
} catch (IOException e) {
logger.error(e);
return super.getDetails(client);
}
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
if (getUserByTokenUrl == null) {
return null;
}
try {
var result = requester.send(requester.get(getUserByTokenUrl, accessToken), HttpUserSession.class);
if (!result.isSuccessful()) {
var error = result.error().error;
if (error.equals(AuthRequestEvent.OAUTH_TOKEN_EXPIRE)) {
throw new OAuthAccessTokenExpired();
}
return null;
}
return result.getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
if (refreshTokenUrl == null) {
return null;
}
try {
return requester.send(requester.post(refreshTokenUrl, new RefreshTokenRequest(refreshToken, context),
null), HttpAuthReport.class).getOrThrow().toAuthReport();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
var result = requester.send(requester.post(authorizeUrl, new AuthorizeRequest(login, context, password, minecraftAccess),
bearerToken), HttpAuthReport.class);
if (!result.isSuccessful()) {
var error = result.error().error;
if (error != null) {
throw new AuthException(error);
}
}
return result.getOrThrow().toAuthReport();
}
@Override
public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) {
if (getHardwareInfoByPublicKeyUrl == null) {
return null;
}
try {
return requester.send(requester.post(getHardwareInfoByPublicKeyUrl, new HardwareRequest(publicKey),
bearerToken), HttpUserHardware.class).getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public UserHardware getHardwareInfoByData(HardwareReportRequest.HardwareInfo info) {
if (getHardwareInfoByDataUrl == null) {
return null;
}
try {
HardwareRequest request = new HardwareRequest(new HttpUserHardware(info));
HttpHelper.HttpOptional<HttpUserHardware, HttpRequester.SimpleError> hardware =
requester.send(requester.post(getHardwareInfoByDataUrl, request,
bearerToken), HttpUserHardware.class);
//should return null if not found
return hardware.isSuccessful() ? hardware.getOrThrow() : null;
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public UserHardware getHardwareInfoById(String id) {
if (getHardwareInfoByIdUrl == null) {
return null;
}
try {
return requester.send(requester.post(getHardwareInfoByIdUrl, new HardwareRequest(new HttpUserHardware(Long.parseLong(id))),
bearerToken), HttpUserHardware.class).getOrThrow();
} catch (IOException | NumberFormatException e) {
logger.error(e);
return null;
}
}
@Override
public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo info, byte[] publicKey) {
if (createHardwareInfoUrl == null) {
return null;
}
try {
return requester.send(requester.post(createHardwareInfoUrl, new HardwareRequest(new HttpUserHardware(info,
publicKey, false)), bearerToken), HttpUserHardware.class).getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
if (connectUserAndHardwareUrl == null) {
return;
}
try {
requester.send(requester.post(connectUserAndHardwareUrl, new HardwareRequest((HttpUserHardware) hardware, (HttpUserSession) userSession), bearerToken), Void.class);
} catch (IOException e) {
logger.error(e);
}
}
@Override
public void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey) {
if (addPublicKeyToHardwareInfoUrl == null) {
return;
}
try {
requester.send(requester.post(addPublicKeyToHardwareInfoUrl, new HardwareRequest((HttpUserHardware) hardware, publicKey), bearerToken), Void.class);
} catch (IOException e) {
logger.error(e);
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Iterable<User> getUsersByHardwareInfo(UserHardware hardware) {
if (getUsersByHardwareInfoUrl == null) {
return null;
}
try {
return (List<User>) (List) requester.send(requester
.post(getUsersByHardwareInfoUrl, new HardwareRequest((HttpUserHardware) hardware), bearerToken), GetHardwareListResponse.class).getOrThrow().list;
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public void banHardware(UserHardware hardware) {
if (banHardwareUrl == null) {
return;
}
try {
requester.send(requester.post(banHardwareUrl, new HardwareRequest((HttpUserHardware) hardware), bearerToken), Void.class);
} catch (IOException e) {
logger.error(e);
}
}
@Override
public void unbanHardware(UserHardware hardware) {
if (unbanHardwareUrl == null) {
return;
}
try {
requester.send(requester.post(unbanHardwareUrl, new HardwareRequest((HttpUserHardware) hardware), bearerToken), Void.class);
} catch (IOException e) {
logger.error(e);
}
}
@Override
public String getClientApiUrl() {
return apiUrl;
}
@Override
public List<String> getClientApiFeatures() {
return apiFeatures;
}
@Override
protected boolean updateServerID(User user, String serverID) throws IOException {
var result = requester.send(requester.post(updateServerIdUrl, new UpdateServerIdRequest(user.getUsername(), user.getUUID(), serverID),
null), Void.class);
return result.isSuccessful();
}
@Override
public User checkServer(Client client, String username, String serverID) throws IOException {
return requester.send(requester.post(checkServerUrl, new CheckServerRequest(username, serverID), bearerToken), HttpUser.class).getOrThrow();
}
@Override
public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException {
var result = requester.send(requester.post(joinServerUrl, new JoinServerRequest(username, accessToken, serverID), bearerToken), Void.class);
return result.isSuccessful();
}
@Override
public void init(LaunchServer server) {
requester = new HttpRequester();
if (getUserByUsernameUrl == null) {
throw new IllegalArgumentException("'getUserByUsernameUrl' can't be null");
}
if (getUserByUUIDUrl == null) {
throw new IllegalArgumentException("'getUserByUUIDUrl' can't be null");
}
if (authorizeUrl == null) {
throw new IllegalArgumentException("'authorizeUrl' can't be null");
}
if (checkServerUrl == null && joinServerUrl == null && updateServerIdUrl == null) {
throw new IllegalArgumentException("Please set 'checkServerUrl' and 'joinServerUrl' or 'updateServerIdUrl'");
}
}
@Override
public void close() throws IOException {
}
public record HttpAuthReport(String minecraftAccessToken, String oauthAccessToken,
String oauthRefreshToken, long oauthExpire,
HttpUserSession session) {
public AuthManager.AuthReport toAuthReport() {
return new AuthManager.AuthReport(minecraftAccessToken, oauthAccessToken, oauthRefreshToken, oauthExpire, session);
}
}
public static class UpdateServerIdRequest {
public String username;
public UUID uuid;
public String serverId;
public UpdateServerIdRequest(String username, UUID uuid, String serverId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
}
}
public static class CheckServerRequest {
public String username;
public String serverId;
public CheckServerRequest(String username, String serverId) {
this.username = username;
this.serverId = serverId;
}
}
public static class GetAuthDetailsResponse {
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> details;
}
public static class GetHardwareListResponse {
public List<HttpUser> list;
}
public static class JoinServerRequest {
public String username;
public String accessToken;
public String serverId;
public JoinServerRequest(String username, String accessToken, String serverId) {
this.username = username;
this.accessToken = accessToken;
this.serverId = serverId;
}
}
public static class AuthorizeRequest {
public String login;
public AuthResponse.AuthContext context;
public AuthRequest.AuthPasswordInterface password;
public boolean minecraftAccess;
public AuthorizeRequest() {
}
public AuthorizeRequest(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) {
this.login = login;
this.context = context;
this.password = password;
this.minecraftAccess = minecraftAccess;
}
}
public static class RefreshTokenRequest {
public String refreshToken;
public AuthResponse.AuthContext context;
public RefreshTokenRequest(String refreshToken, AuthResponse.AuthContext context) {
this.refreshToken = refreshToken;
this.context = context;
}
}
public record HardwareRequest(HttpUserHardware userHardware, byte[] key, HttpUserSession userSession) {
public HardwareRequest(HttpUserHardware userHardware) {
this(userHardware, null, null);
}
public HardwareRequest(HttpUserHardware userHardware, byte[] key) {
this(userHardware, key, null);
}
public HardwareRequest(HttpUserHardware userHardware, HttpUserSession userSession) {
this(userHardware, null, userSession);
}
public HardwareRequest(byte[] key) {
this(null, key, null);
}
}
public static class HttpUserSession implements UserSession {
private String id;
private HttpUser user;
private long expireIn;
public HttpUserSession() {
}
public HttpUserSession(String id, HttpUser user, long expireIn) {
this.id = id;
this.user = user;
this.expireIn = expireIn;
}
@Override
public String getID() {
return id;
}
@Override
public User getUser() {
return user;
}
@Override
public long getExpireIn() {
return expireIn;
}
@Override
public String toString() {
return "HttpUserSession{" +
"id='" + id + '\'' +
", user=" + user +
", expireIn=" + expireIn +
'}';
}
}
public static class HttpUserHardware implements UserHardware {
private final HardwareReportRequest.HardwareInfo hardwareInfo;
private final long id;
private byte[] publicKey;
private boolean banned;
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, long id, boolean banned) {
this.hardwareInfo = hardwareInfo;
this.publicKey = publicKey;
this.id = id;
this.banned = banned;
}
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo) {
this.hardwareInfo = hardwareInfo;
this.id = Long.MIN_VALUE;
}
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, boolean banned) {
this.hardwareInfo = hardwareInfo;
this.publicKey = publicKey;
this.banned = banned;
this.id = Long.MIN_VALUE;
}
public HttpUserHardware(long id) {
this.id = id;
this.hardwareInfo = null;
}
@Override
public HardwareReportRequest.HardwareInfo getHardwareInfo() {
return hardwareInfo;
}
@Override
public byte[] getPublicKey() {
return publicKey;
}
@Override
public String getId() {
return String.valueOf(id);
}
@Override
public boolean isBanned() {
return banned;
}
@Override
public String toString() {
return "HttpUserHardware{" +
"hardwareInfo=" + hardwareInfo +
", publicKey=" + (publicKey == null ? null : new String(Base64.getEncoder().encode(publicKey))) +
", id=" + id +
", banned=" + banned +
'}';
}
}
public class HttpUser implements User, UserSupportTextures, UserSupportProperties, UserSupportHardware {
private String username;
private UUID uuid;
private String serverId;
private String accessToken;
private ClientPermissions permissions;
@Deprecated
private Texture skin;
@Deprecated
private Texture cloak;
private Map<String, Texture> assets;
private Map<String, String> properties;
private long hwidId;
private transient HttpUserHardware hardware;
public HttpUser() {
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, long hwidId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.hwidId = hwidId;
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Texture skin, Texture cloak, long hwidId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.skin = skin;
this.cloak = cloak;
this.hwidId = hwidId;
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Texture skin, Texture cloak, Map<String, String> properties, long hwidId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.skin = skin;
this.cloak = cloak;
this.properties = properties;
this.hwidId = hwidId;
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Map<String, Texture> assets, Map<String, String> properties, long hwidId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.assets = assets;
this.properties = properties;
this.hwidId = hwidId;
}
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public String getServerId() {
return serverId;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public Texture getSkinTexture() {
if (assets == null) {
return skin;
}
return assets.get("SKIN");
}
@Override
public Texture getCloakTexture() {
if (assets == null) {
return cloak;
}
return assets.get("CAPE");
}
public Map<String, Texture> getAssets() {
if (assets == null) {
Map<String, Texture> map = new HashMap<>();
if (skin != null) {
map.put("SKIN", skin);
}
if (cloak != null) {
map.put("CAPE", cloak);
}
return map;
}
return assets;
}
@Override
public Map<String, String> getProperties() {
if (properties == null) {
return new HashMap<>();
}
return properties;
}
@Override
public String toString() {
return "HttpUser{" +
"username='" + username + '\'' +
", uuid=" + uuid +
", serverId='" + serverId + '\'' +
", accessToken='" + accessToken + '\'' +
", permissions=" + permissions +
", assets=" + getAssets() +
", properties=" + properties +
", hwidId=" + hwidId +
'}';
}
@Override
public UserHardware getHardware() {
if (hardware != null) return hardware;
HttpAuthCoreProvider.HttpUserHardware result = (HttpUserHardware) getHardwareInfoById(String.valueOf(hwidId));
hardware = result;
return result;
}
}
}

View file

@ -1,11 +1,11 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.details.AuthLoginOnlyDetails;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launcher.base.request.auth.details.AuthLoginOnlyDetails;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportSudo;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
@ -18,7 +18,7 @@
import java.util.Objects;
import java.util.UUID;
public class MemoryAuthCoreProvider extends AuthCoreProvider {
public class MemoryAuthCoreProvider extends AuthCoreProvider implements AuthSupportSudo {
private transient final List<MemoryUser> memory = new ArrayList<>(16);
@Override
@ -53,7 +53,7 @@ public User getUserByUUID(UUID uuid) {
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
public UserSession getUserSessionByOAuthAccessToken(String accessToken) {
synchronized (memory) {
for (MemoryUser u : memory) {
if (u.accessToken.equals(accessToken)) {
@ -95,14 +95,7 @@ public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext c
}
@Override
protected boolean updateServerID(User user, String serverID) throws IOException {
MemoryUser memoryUser = (MemoryUser) user;
memoryUser.serverId = serverID;
return true;
}
@Override
public User checkServer(Client client, String username, String serverID) throws IOException {
public User checkServer(Client client, String username, String serverID) {
synchronized (memory) {
for (MemoryUser u : memory) {
if (u.username.equals(username)) {
@ -116,26 +109,26 @@ public User checkServer(Client client, String username, String serverID) throws
}
@Override
public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException {
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
return true;
}
@Override
public void init(LaunchServer server) {
public void close() {
}
@Override
public void close() throws IOException {
public AuthManager.AuthReport sudo(User user, boolean shadow) throws IOException {
return authorize(user.getUsername(), null, null, true);
}
public static class MemoryUser implements User {
private String username;
private UUID uuid;
private final String username;
private final UUID uuid;
private String serverId;
private String accessToken;
private ClientPermissions permissions;
private final String accessToken;
private final ClientPermissions permissions;
public MemoryUser(String username) {
this.username = username;
@ -158,16 +151,6 @@ public UUID getUUID() {
return uuid;
}
@Override
public String getServerId() {
return serverId;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
@ -188,9 +171,9 @@ public int hashCode() {
}
public static class MemoryUserSession implements UserSession {
private String id;
private MemoryUser user;
private long expireIn;
private final String id;
private final MemoryUser user;
private final long expireIn;
public MemoryUserSession(MemoryUser user) {
this.id = SecurityHelper.randomStringToken();
@ -208,6 +191,11 @@ public User getUser() {
return user;
}
@Override
public String getMinecraftAccessToken() {
return "IGNORED";
}
@Override
public long getExpireIn() {
return expireIn;

View file

@ -0,0 +1,91 @@
package pro.gravit.launchserver.auth.core;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class MergeAuthCoreProvider extends AuthCoreProvider {
private transient final Logger logger = LogManager.getLogger(MergeAuthCoreProvider.class);
public List<String> list = new ArrayList<>();
private final transient List<AuthCoreProvider> providers = new ArrayList<>();
@Override
public User getUserByUsername(String username) {
for(var core : providers) {
var result = core.getUserByUsername(username);
if(result != null) {
return result;
}
}
return null;
}
@Override
public User getUserByUUID(UUID uuid) {
for(var core : providers) {
var result = core.getUserByUUID(uuid);
if(result != null) {
return result;
}
}
return null;
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
throw new OAuthAccessTokenExpired(); // Authorization not supported
}
@Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
return null;
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
throw new AuthException("Authorization not supported");
}
@Override
public User checkServer(Client client, String username, String serverID) throws IOException {
for(var core : providers) {
var result = core.checkServer(client, username, serverID);
if(result != null) {
return result;
}
}
return null;
}
@Override
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
return false; // Authorization not supported
}
@Override
public void init(LaunchServer server, AuthProviderPair pair1) {
for(var e : list) {
var pair = server.config.auth.get(e);
if(pair != null) {
providers.add(pair.core);
} else {
logger.warn("Provider {} not found", e);
}
}
}
@Override
public void close() {
// Providers closed automatically
}
}

View file

@ -1,13 +1,13 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportHardware;
import pro.gravit.utils.helper.IOHelper;
import java.io.ByteArrayInputStream;
@ -41,31 +41,38 @@ public SQLSourceConfig getSQLConfig() {
}
@Override
public void init(LaunchServer server) {
super.init(server);
String userInfoCols = String.format("%s, %s, %s, %s, %s, %s", uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn, hardwareIdColumn);
public void init(LaunchServer server, AuthProviderPair pair) {
super.init(server, pair);
logger.warn("Method 'mysql' deprecated and may be removed in future release. Please use new 'sql' method: https://gravitlauncher.com/auth");
String userInfoCols = makeUserCols();
String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey";
if (sqlFindHardwareByPublicKey == null)
sqlFindHardwareByPublicKey = String.format("SELECT %s FROM %s WHERE `publicKey` = ?", hardwareInfoCols, tableHWID);
sqlFindHardwareByPublicKey = "SELECT %s FROM %s WHERE `publicKey` = ?".formatted(hardwareInfoCols, tableHWID);
if (sqlFindHardwareById == null)
sqlFindHardwareById = String.format("SELECT %s FROM %s WHERE `id` = ?", hardwareInfoCols, tableHWID);
sqlFindHardwareById = "SELECT %s FROM %s WHERE `id` = ?".formatted(hardwareInfoCols, tableHWID);
if (sqlUsersByHwidId == null)
sqlUsersByHwidId = String.format("SELECT %s FROM %s WHERE `%s` = ?", userInfoCols, table, hardwareIdColumn);
sqlUsersByHwidId = "SELECT %s FROM %s WHERE `%s` = ?".formatted(userInfoCols, table, hardwareIdColumn);
if (sqlFindHardwareByData == null)
sqlFindHardwareByData = String.format("SELECT %s FROM %s", hardwareInfoCols, tableHWID);
sqlFindHardwareByData = "SELECT %s FROM %s".formatted(hardwareInfoCols, tableHWID);
if (sqlCreateHardware == null)
sqlCreateHardware = String.format("INSERT INTO `%s` (`publickey`, `hwDiskId`, `baseboardSerialNumber`, `displayId`, `bitness`, `totalMemory`, `logicalProcessors`, `physicalProcessors`, `processorMaxFreq`, `graphicCard`, `battery`, `banned`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '0')", tableHWID);
sqlCreateHardware = "INSERT INTO `%s` (`publickey`, `hwDiskId`, `baseboardSerialNumber`, `displayId`, `bitness`, `totalMemory`, `logicalProcessors`, `physicalProcessors`, `processorMaxFreq`, `graphicCard`, `battery`, `banned`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '0')".formatted(tableHWID);
if (sqlCreateHWIDLog == null)
sqlCreateHWIDLog = String.format("INSERT INTO %s (`hwidId`, `newPublicKey`) VALUES (?, ?)", tableHWIDLog);
sqlCreateHWIDLog = "INSERT INTO %s (`hwidId`, `newPublicKey`) VALUES (?, ?)".formatted(tableHWIDLog);
if (sqlUpdateHardwarePublicKey == null)
sqlUpdateHardwarePublicKey = String.format("UPDATE %s SET `publicKey` = ? WHERE `id` = ?", tableHWID);
sqlUpdateHardwareBanned = String.format("UPDATE %s SET `banned` = ? WHERE `id` = ?", tableHWID);
sqlUpdateUsers = String.format("UPDATE %s SET `%s` = ? WHERE `%s` = ?", table, hardwareIdColumn, uuidColumn);
sqlUpdateHardwarePublicKey = "UPDATE %s SET `publicKey` = ? WHERE `id` = ?".formatted(tableHWID);
sqlUpdateHardwareBanned = "UPDATE %s SET `banned` = ? WHERE `id` = ?".formatted(tableHWID);
sqlUpdateUsers = "UPDATE %s SET `%s` = ? WHERE `%s` = ?".formatted(table, hardwareIdColumn, uuidColumn);
}
private MySQLUser constructUser(ResultSet set) throws SQLException {
@Override
protected String makeUserCols() {
return super.makeUserCols().concat(", ").concat(hardwareIdColumn);
}
@Override
protected MySQLUser constructUser(ResultSet set) throws SQLException {
return set.next() ? new MySQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), requestPermissions(set.getString(uuidColumn)), set.getLong(hardwareIdColumn)) : null;
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), set.getLong(hardwareIdColumn)) : null;
}
private MySQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException {
@ -254,6 +261,34 @@ public void unbanHardware(UserHardware hardware) {
}
}
@Override
protected SQLUserSession createSession(SQLUser user) {
return new MySQLUserSession(user);
}
public class MySQLUserSession extends SQLUserSession implements UserSessionSupportHardware {
private transient MySQLUser mySQLUser;
protected transient MySQLUserHardware hardware;
public MySQLUserSession(SQLUser user) {
super(user);
mySQLUser = (MySQLUser) user;
}
@Override
public String getHardwareId() {
return mySQLUser.hwidId == 0 ? null : String.valueOf(mySQLUser.hwidId);
}
@Override
public UserHardware getHardware() {
if(hardware == null) {
hardware = (MySQLUserHardware) getHardwareInfoById(String.valueOf(mySQLUser.hwidId));
}
return hardware;
}
}
public static class MySQLUserHardware implements UserHardware {
private final HardwareReportRequest.HardwareInfo hardwareInfo;
private final long id;
@ -298,23 +333,14 @@ public String toString() {
}
}
public class MySQLUser extends SQLUser implements UserSupportHardware {
public static class MySQLUser extends SQLUser {
protected long hwidId;
protected transient MySQLUserHardware hardware;
public MySQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions, long hwidId) {
super(uuid, username, accessToken, serverId, password, permissions);
public MySQLUser(UUID uuid, String username, String accessToken, String serverId, String password, long hwidId) {
super(uuid, username, accessToken, serverId, password);
this.hwidId = hwidId;
}
@Override
public UserHardware getHardware() {
if (hardware != null) return hardware;
MySQLUserHardware result = (MySQLUserHardware) getHardwareInfoById(String.valueOf(hwidId));
hardware = result;
return result;
}
@Override
public String toString() {
return "MySQLUser{" +

View file

@ -1,5 +1,7 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.PostgreSQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig;
@ -10,4 +12,10 @@ public class PostgresSQLCoreProvider extends AbstractSQLCoreProvider {
public SQLSourceConfig getSQLConfig() {
return postgresSQLHolder;
}
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
super.init(server, pair);
logger.warn("Method 'postgresql' deprecated and may be removed in future release. Please use new 'sql' method: https://gravitlauncher.com/auth");
}
}

View file

@ -1,9 +1,9 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import java.io.IOException;
@ -21,7 +21,7 @@ public User getUserByUUID(UUID uuid) {
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
public UserSession getUserSessionByOAuthAccessToken(String accessToken) {
return null;
}
@ -41,17 +41,17 @@ public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext c
}
@Override
public void init(LaunchServer server) {
public User checkServer(Client client, String username, String serverID) {
return null;
}
@Override
protected boolean updateServerID(User user, String serverID) throws IOException {
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
return false;
}
@Override
public void close() throws IOException {
public void close() {
}
}

View file

@ -0,0 +1,391 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.HikariSQLSourceConfig;
import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportExtendedCheckServer;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportHardware;
import pro.gravit.launchserver.socket.Client;
import java.io.IOException;
import java.sql.*;
import java.util.Base64;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
public class SQLCoreProvider extends AbstractSQLCoreProvider implements AuthSupportHardware, AuthSupportExtendedCheckServer {
public HikariSQLSourceConfig holder;
@Override
public void close() {
super.close();
holder.close();
}
@Override
public SQLSourceConfig getSQLConfig() {
return holder;
}
public String hardwareIdColumn;
public String tableHWID = "hwids";
public String tableHWIDLog = "hwidLog";
public double criticalCompareLevel = 1.0;
private transient String sqlFindHardwareByPublicKey;
private transient String sqlFindHardwareByData;
private transient String sqlFindHardwareById;
private transient String sqlCreateHardware;
private transient String sqlCreateHWIDLog;
private transient String sqlUpdateHardwarePublicKey;
private transient String sqlUpdateHardwareBanned;
private transient String sqlUpdateUsers;
private transient String sqlUsersByHwidId;
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
holder.init();
super.init(server, pair);
String userInfoCols = makeUserCols();
String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey";
if (sqlFindHardwareByPublicKey == null)
sqlFindHardwareByPublicKey = "SELECT %s FROM %s WHERE publicKey = ?".formatted(hardwareInfoCols, tableHWID);
if (sqlFindHardwareById == null)
sqlFindHardwareById = "SELECT %s FROM %s WHERE id = ?".formatted(hardwareInfoCols, tableHWID);
if (sqlUsersByHwidId == null)
sqlUsersByHwidId = "SELECT %s FROM %s WHERE %s = ?".formatted(userInfoCols, table, hardwareIdColumn);
if (sqlFindHardwareByData == null)
sqlFindHardwareByData = "SELECT %s FROM %s".formatted(hardwareInfoCols, tableHWID);
if (sqlCreateHardware == null)
sqlCreateHardware = "INSERT INTO %s (publickey, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, graphicCard, battery, banned) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '0')".formatted(tableHWID);
if (sqlCreateHWIDLog == null)
sqlCreateHWIDLog = "INSERT INTO %s (hwidId, newPublicKey) VALUES (?, ?)".formatted(tableHWIDLog);
if (sqlUpdateHardwarePublicKey == null)
sqlUpdateHardwarePublicKey = "UPDATE %s SET publicKey = ? WHERE id = ?".formatted(tableHWID);
sqlUpdateHardwareBanned = "UPDATE %s SET banned = ? WHERE id = ?".formatted(tableHWID);
sqlUpdateUsers = "UPDATE %s SET %s = ? WHERE %s = ?".formatted(table, hardwareIdColumn, uuidColumn);
}
@Override
protected String makeUserCols() {
return super.makeUserCols().concat(", ").concat(hardwareIdColumn);
}
@Override
protected SQLUser constructUser(ResultSet set) throws SQLException {
return set.next() ? new SQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), set.getLong(hardwareIdColumn)) : null;
}
private SQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException {
HardwareReportRequest.HardwareInfo hardwareInfo = new HardwareReportRequest.HardwareInfo();
hardwareInfo.hwDiskId = set.getString("hwDiskId");
hardwareInfo.baseboardSerialNumber = set.getString("baseboardSerialNumber");
byte[] displayId = set.getBytes("displayId");
hardwareInfo.displayId = displayId == null ? null : displayId;
hardwareInfo.bitness = set.getInt("bitness");
hardwareInfo.totalMemory = set.getLong("totalMemory");
hardwareInfo.logicalProcessors = set.getInt("logicalProcessors");
hardwareInfo.physicalProcessors = set.getInt("physicalProcessors");
hardwareInfo.processorMaxFreq = set.getLong("processorMaxFreq");
hardwareInfo.battery = set.getBoolean("battery");
hardwareInfo.graphicCard = set.getString("graphicCard");
byte[] publicKey = set.getBytes("publicKey");
long id = set.getLong("id");
boolean banned = set.getBoolean("banned");
return new SQLUserHardware(hardwareInfo, publicKey, id, banned);
}
private void setUserHardwareId(Connection connection, UUID uuid, long hwidId) throws SQLException {
PreparedStatement s = connection.prepareStatement(sqlUpdateUsers);
s.setLong(1, hwidId);
s.setString(2, uuid.toString());
s.executeUpdate();
}
@Override
public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) {
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlFindHardwareByPublicKey);
s.setBytes(1, publicKey);
try (ResultSet set = s.executeQuery()) {
if (set.next()) {
connection.commit();
return fetchHardwareInfo(set);
} else {
connection.commit();
return null;
}
}
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
return null;
}
}
@Override
public UserHardware getHardwareInfoByData(HardwareReportRequest.HardwareInfo info) {
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlFindHardwareByData);
try (ResultSet set = s.executeQuery()) {
while (set.next()) {
SQLUserHardware hw = fetchHardwareInfo(set);
AuthSupportHardware.HardwareInfoCompareResult result = compareHardwareInfo(hw.getHardwareInfo(), info);
if (result.compareLevel > criticalCompareLevel) {
connection.commit();
return hw;
} else {
connection.commit();
}
}
}
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
}
return null;
}
@Override
public UserHardware getHardwareInfoById(String id) {
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlFindHardwareById);
s.setLong(1, Long.parseLong(id));
try (ResultSet set = s.executeQuery()) {
if (set.next()) {
connection.commit();
return fetchHardwareInfo(set);
} else {
connection.commit();
return null;
}
}
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
return null;
}
}
@Override
public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey) {
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlCreateHardware, Statement.RETURN_GENERATED_KEYS);
s.setBytes(1, publicKey);
s.setString(2, hardwareInfo.hwDiskId);
s.setString(3, hardwareInfo.baseboardSerialNumber);
s.setBytes(4, hardwareInfo.displayId == null ? null : hardwareInfo.displayId);
s.setInt(5, hardwareInfo.bitness);
s.setLong(6, hardwareInfo.totalMemory);
s.setInt(7, hardwareInfo.logicalProcessors);
s.setInt(8, hardwareInfo.physicalProcessors);
s.setLong(9, hardwareInfo.processorMaxFreq);
s.setString(10, hardwareInfo.graphicCard);
s.setBoolean(11, hardwareInfo.battery);
s.executeUpdate();
try (ResultSet generatedKeys = s.getGeneratedKeys()) {
if (generatedKeys.next()) {
//writeHwidLog(connection, generatedKeys.getLong(1), publicKey);
long id = generatedKeys.getLong(1);
connection.commit();
return new SQLUserHardware(hardwareInfo, publicKey, id, false);
}
}
connection.commit();
return null;
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
return null;
}
}
@Override
public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
AbstractSQLCoreProvider.SQLUserSession SQLUserSession = (AbstractSQLCoreProvider.SQLUserSession) userSession;
SQLUser SQLUser = (SQLUser) SQLUserSession.getUser();
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
if (SQLUser.hwidId == SQLUserHardware.id) return;
SQLUser.hwidId = SQLUserHardware.id;
try (Connection connection = holder.getConnection()) {
setUserHardwareId(connection, SQLUser.getUUID(), SQLUserHardware.id);
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
}
}
@Override
public void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey) {
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
SQLUserHardware.publicKey = publicKey;
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwarePublicKey);
s.setBytes(1, publicKey);
s.setLong(2, SQLUserHardware.id);
s.executeUpdate();
connection.commit();
} catch (SQLException e) {
logger.error("SQL error", e);
}
}
@Override
public Iterable<User> getUsersByHardwareInfo(UserHardware hardware) {
List<User> users = new LinkedList<>();
try (Connection c = holder.getConnection()) {
c.setAutoCommit(false);
PreparedStatement s = c.prepareStatement(sqlUsersByHwidId);
s.setLong(1, Long.parseLong(hardware.getId()));
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
while (!set.isLast()) {
users.add(constructUser(set));
}
}
c.commit();
} catch (SQLException e) {
logger.error("SQL error", e);
return null;
}
return users;
}
@Override
public void banHardware(UserHardware hardware) {
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
SQLUserHardware.banned = true;
try (Connection connection = holder.getConnection()) {
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned);
s.setBoolean(1, true);
s.setLong(2, SQLUserHardware.id);
s.executeUpdate();
} catch (SQLException e) {
logger.error("SQL Error", e);
}
}
@Override
public void unbanHardware(UserHardware hardware) {
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
SQLUserHardware.banned = false;
try (Connection connection = holder.getConnection()) {
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned);
s.setBoolean(1, false);
s.setLong(2, SQLUserHardware.id);
s.executeUpdate();
} catch (SQLException e) {
logger.error("SQL error", e);
}
}
@Override
protected AbstractSQLCoreProvider.SQLUserSession createSession(AbstractSQLCoreProvider.SQLUser user) {
return new SQLUserSession(user);
}
@Override
public UserSession extendedCheckServer(Client client, String username, String serverID) {
AbstractSQLCoreProvider.SQLUser user = (AbstractSQLCoreProvider.SQLUser) getUserByUsername(username);
if (user == null) {
return null;
}
if (user.getUsername().equals(username) && user.getServerId().equals(serverID)) {
return createSession(user);
}
return null;
}
public class SQLUserSession extends AbstractSQLCoreProvider.SQLUserSession implements UserSessionSupportHardware {
private transient SQLUser SQLUser;
protected transient SQLUserHardware hardware;
public SQLUserSession(AbstractSQLCoreProvider.SQLUser user) {
super(user);
SQLUser = (SQLUser) user;
}
@Override
public String getHardwareId() {
return SQLUser.hwidId == 0 ? null : String.valueOf(SQLUser.hwidId);
}
@Override
public UserHardware getHardware() {
if(hardware == null) {
hardware = (SQLUserHardware) getHardwareInfoById(String.valueOf(SQLUser.hwidId));
}
return hardware;
}
}
public static class SQLUserHardware implements UserHardware {
private final HardwareReportRequest.HardwareInfo hardwareInfo;
private final long id;
private byte[] publicKey;
private boolean banned;
public SQLUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, long id, boolean banned) {
this.hardwareInfo = hardwareInfo;
this.publicKey = publicKey;
this.id = id;
this.banned = banned;
}
@Override
public HardwareReportRequest.HardwareInfo getHardwareInfo() {
return hardwareInfo;
}
@Override
public byte[] getPublicKey() {
return publicKey;
}
@Override
public String getId() {
return String.valueOf(id);
}
@Override
public boolean isBanned() {
return banned;
}
@Override
public String toString() {
return "SQLUserHardware{" +
"hardwareInfo=" + hardwareInfo +
", publicKey=" + (publicKey == null ? null : new String(Base64.getEncoder().encode(publicKey))) +
", id=" + id +
", banned=" + banned +
'}';
}
}
public static class SQLUser extends AbstractSQLCoreProvider.SQLUser {
protected long hwidId;
public SQLUser(UUID uuid, String username, String accessToken, String serverId, String password, long hwidId) {
super(uuid, username, accessToken, serverId, password);
this.hwidId = hwidId;
}
@Override
public String toString() {
return "SQLUser{" +
"uuid=" + uuid +
", username='" + username + '\'' +
", permissions=" + permissions +
", hwidId=" + hwidId +
'}';
}
}
}

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.base.ClientPermissions;
import java.util.UUID;
@ -9,10 +9,6 @@ public interface User {
UUID getUUID();
String getServerId();
String getAccessToken();
ClientPermissions getPermissions();
default boolean isBanned() {

View file

@ -5,5 +5,7 @@ public interface UserSession {
User getUser();
String getMinecraftAccessToken();
long getExpireIn();
}

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.core.interfaces;
import pro.gravit.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
public interface UserHardware {
HardwareReportRequest.HardwareInfo getHardwareInfo();

View file

@ -0,0 +1,22 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launcher.base.events.request.AssetUploadInfoRequestEvent;
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
import pro.gravit.launcher.base.events.request.GetAssetUploadUrlRequestEvent;
import pro.gravit.launchserver.auth.Feature;
import pro.gravit.launchserver.auth.core.User;
import java.util.Set;
@Feature(GetAssetUploadUrlRequestEvent.FEATURE_NAME)
public interface AuthSupportAssetUpload extends AuthSupport {
String getAssetUploadUrl(String name, User user);
default AuthRequestEvent.OAuthRequestEvent getAssetUploadToken(String name, User user) {
return null;
}
default AssetUploadInfoRequestEvent getAssetUploadInfo(User user) {
return new AssetUploadInfoRequestEvent(Set.of("SKIN", "CAPE"), AssetUploadInfoRequestEvent.SlimSupportConf.USER);
}
}

View file

@ -4,7 +4,7 @@
import pro.gravit.launchserver.auth.core.UserSession;
public interface AuthSupportExit extends AuthSupport {
boolean deleteSession(UserSession session);
void deleteSession(UserSession session);
boolean exitUser(User user);
void exitUser(User user);
}

View file

@ -0,0 +1,10 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.socket.Client;
import java.io.IOException;
public interface AuthSupportExtendedCheckServer {
UserSession extendedCheckServer(Client client, String username, String serverID);
}

View file

@ -1,12 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launchserver.auth.Feature;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
import java.util.List;
@Feature("sessions")
public interface AuthSupportGetSessionsFromUser extends AuthSupport {
List<UserSession> getSessionsByUser(User user);
}

View file

@ -1,10 +1,9 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
import pro.gravit.launchserver.helper.DamerauHelper;
import java.util.Arrays;
@ -28,10 +27,6 @@ public interface AuthSupportHardware extends AuthSupport {
void unbanHardware(UserHardware hardware);
default UserSupportHardware fetchUserHardware(User user) {
return (UserSupportHardware) user;
}
default void normalizeHardwareInfo(HardwareReportRequest.HardwareInfo hardwareInfo) {
if (hardwareInfo.baseboardSerialNumber != null)
hardwareInfo.baseboardSerialNumber = hardwareInfo.baseboardSerialNumber.trim();

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launchserver.auth.Feature;
import pro.gravit.launchserver.auth.core.User;

View file

@ -1,9 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import java.util.List;
public interface AuthSupportRemoteClientAccess {
String getClientApiUrl();
List<String> getClientApiFeatures();
}

View file

@ -0,0 +1,10 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.manangers.AuthManager;
import java.io.IOException;
public interface AuthSupportSudo {
AuthManager.AuthReport sudo(User user, boolean shadow) throws IOException;
}

View file

@ -1,20 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportBanInfo;
import java.time.LocalDateTime;
public interface AuthSupportUserBan extends AuthSupport {
UserSupportBanInfo.UserBanInfo banUser(User user, String reason, String moderator, LocalDateTime startTime, LocalDateTime endTime);
default UserSupportBanInfo.UserBanInfo banUser(User user) {
return banUser(user, null, null, LocalDateTime.now(), null);
}
void unbanUser(User user);
default UserSupportBanInfo fetchUserBanInfo(User user) {
return (UserSupportBanInfo) user;
}
}

View file

@ -0,0 +1,8 @@
package pro.gravit.launchserver.auth.core.interfaces.session;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
public interface UserSessionSupportHardware {
String getHardwareId();
UserHardware getHardware();
}

View file

@ -0,0 +1,7 @@
package pro.gravit.launchserver.auth.core.interfaces.session;
import java.util.Map;
public interface UserSessionSupportProperties {
Map<String, String> getProperties();
}

View file

@ -1,27 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.user;
import java.time.LocalDateTime;
public interface UserSupportBanInfo {
UserBanInfo getBanInfo();
interface UserBanInfo {
String getId();
default String getReason() {
return null;
}
default String getModerator() {
return null;
}
default LocalDateTime getStartDate() {
return null;
}
default LocalDateTime getEndDate() {
return null;
}
}
}

View file

@ -1,7 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.user;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
public interface UserSupportHardware {
UserHardware getHardware();
}

View file

@ -1,7 +1,7 @@
package pro.gravit.launchserver.auth.core.interfaces.user;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launcher.profiles.Texture;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.Texture;
import java.util.HashMap;
import java.util.Map;

View file

@ -0,0 +1,14 @@
package pro.gravit.launchserver.auth.core.openid;
import com.google.gson.annotations.SerializedName;
public record AccessTokenResponse(@SerializedName("access_token") String accessToken,
@SerializedName("expires_in") Long expiresIn,
@SerializedName("refresh_expires_in") Long refreshExpiresIn,
@SerializedName("refresh_token") String refreshToken,
@SerializedName("token_type") String tokenType,
@SerializedName("id_token") String idToken,
@SerializedName("not-before-policy") Integer notBeforePolicy,
@SerializedName("session_state") String sessionState,
@SerializedName("scope") String scope) {
}

View file

@ -0,0 +1,178 @@
package pro.gravit.launchserver.auth.core.openid;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launcher.base.request.auth.password.AuthCodePassword;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.HikariSQLSourceConfig;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.LogHelper;
import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class OpenIDAuthCoreProvider extends AuthCoreProvider {
private transient SQLUserStore sqlUserStore;
private transient SQLServerSessionStore sqlSessionStore;
private transient OpenIDAuthenticator openIDAuthenticator;
private OpenIDConfig openIDConfig;
private HikariSQLSourceConfig sqlSourceConfig;
@Override
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
return openIDAuthenticator.getDetails();
}
@Override
public User getUserByUsername(String username) {
return sqlUserStore.getByUsername(username);
}
@Override
public User getUserByUUID(UUID uuid) {
return sqlUserStore.getUserByUUID(uuid);
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
return openIDAuthenticator.getUserSessionByOAuthAccessToken(accessToken);
}
@Override
public AuthManager.AuthReport refreshAccessToken(String oldRefreshToken, AuthResponse.AuthContext context) {
var tokens = openIDAuthenticator.refreshAccessToken(oldRefreshToken);
var accessToken = tokens.accessToken();
var refreshToken = tokens.refreshToken();
long expiresIn = TimeUnit.SECONDS.toMillis(tokens.accessTokenExpiresIn());
UserSession session;
try {
session = openIDAuthenticator.getUserSessionByOAuthAccessToken(accessToken);
} catch (OAuthAccessTokenExpired e) {
throw new RuntimeException("invalid token", e);
}
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken,
expiresIn, session);
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
if (password == null) {
throw AuthException.wrongPassword();
}
var authCodePassword = (AuthCodePassword) password;
var tokens = openIDAuthenticator.authorize(authCodePassword);
var accessToken = tokens.accessToken();
var refreshToken = tokens.refreshToken();
var user = openIDAuthenticator.createUserFromToken(accessToken);
long expiresIn = TimeUnit.SECONDS.toMillis(tokens.accessTokenExpiresIn());
sqlUserStore.createOrUpdateUser(user);
UserSession session;
try {
session = openIDAuthenticator.getUserSessionByOAuthAccessToken(accessToken);
} catch (OAuthAccessTokenExpired e) {
throw new AuthException("invalid token", e);
}
if (minecraftAccess) {
var minecraftToken = generateMinecraftToken(user);
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftToken, accessToken, refreshToken,
expiresIn, session);
} else {
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken,
expiresIn, session);
}
}
private String generateMinecraftToken(User user) {
return Jwts.builder()
.issuer("LaunchServer")
.subject(user.getUUID().toString())
.claim("preferred_username", user.getUsername())
.expiration(Date.from(Instant.now().plus(24, ChronoUnit.HOURS)))
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
.compact();
}
private User createUserFromMinecraftToken(String accessToken) throws AuthException {
try {
var parser = Jwts.parser()
.requireIssuer("LaunchServer")
.verifyWith(server.keyAgreementManager.ecdsaPublicKey)
.build();
var claims = parser.parseSignedClaims(accessToken);
var username = claims.getPayload().get("preferred_username", String.class);
var uuid = UUID.fromString(claims.getPayload().getSubject());
return new UserEntity(username, uuid, new ClientPermissions());
} catch (JwtException e) {
throw new AuthException("Bad minecraft token", e);
}
}
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
super.init(server, pair);
this.sqlSourceConfig.init();
this.sqlUserStore = new SQLUserStore(sqlSourceConfig);
this.sqlUserStore.init();
this.sqlSessionStore = new SQLServerSessionStore(sqlSourceConfig);
this.sqlSessionStore.init();
this.openIDAuthenticator = new OpenIDAuthenticator(openIDConfig);
}
@Override
public User checkServer(Client client, String username, String serverID) {
var savedServerId = sqlSessionStore.getServerIdByUsername(username);
if (!serverID.equals(savedServerId)) {
return null;
}
return sqlUserStore.getByUsername(username);
}
@Override
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
User user;
try {
user = createUserFromMinecraftToken(accessToken);
} catch (AuthException e) {
LogHelper.error(e);
return false;
}
if (!user.getUUID().equals(uuid)) {
return false;
}
sqlUserStore.createOrUpdateUser(user);
return sqlSessionStore.joinServer(user.getUUID(), user.getUsername(), serverID);
}
@Override
public void close() {
sqlSourceConfig.close();
}
}

View file

@ -0,0 +1,232 @@
package pro.gravit.launchserver.auth.core.openid;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.JwkSet;
import io.jsonwebtoken.security.Jwks;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.base.request.auth.details.AuthWebViewDetails;
import pro.gravit.launcher.base.request.auth.password.AuthCodePassword;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.QueryHelper;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.Key;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
public class OpenIDAuthenticator {
private static final HttpClient CLIENT = HttpClient.newBuilder().build();
private final OpenIDConfig openIDConfig;
private final JwtParser jwtParser;
public OpenIDAuthenticator(OpenIDConfig openIDConfig) {
this.openIDConfig = openIDConfig;
var keyLocator = loadKeyLocator(openIDConfig);
this.jwtParser = Jwts.parser()
.keyLocator(keyLocator)
.requireIssuer(openIDConfig.issuer())
.require("azp", openIDConfig.clientId())
.build();
}
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails() {
var state = UUID.randomUUID().toString();
var uri = QueryBuilder.get(openIDConfig.authorizationEndpoint())
.addQuery("response_type", "code")
.addQuery("client_id", openIDConfig.clientId())
.addQuery("redirect_uri", openIDConfig.redirectUri())
.addQuery("scope", openIDConfig.scopes())
.addQuery("state", state)
.toUriString();
return List.of(new AuthWebViewDetails(uri, openIDConfig.redirectUri()));
}
public TokenResponse refreshAccessToken(String oldRefreshToken) {
var postBody = QueryBuilder.post()
.addQuery("grant_type", "refresh_token")
.addQuery("refresh_token", oldRefreshToken)
.addQuery("client_id", openIDConfig.clientId())
.addQuery("client_secret", openIDConfig.clientSecret())
.toString();
var accessTokenResponse = requestToken(postBody);
var accessToken = accessTokenResponse.accessToken();
var refreshToken = accessTokenResponse.refreshToken();
try {
readAndVerifyToken(accessToken);
} catch (AuthException e) {
throw new RuntimeException(e);
}
var accessTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.expiresIn(), 0L);
var refreshTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.refreshExpiresIn(), 0L);
return new TokenResponse(accessToken, accessTokenExpiresIn,
refreshToken, refreshTokenExpiresIn);
}
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws AuthCoreProvider.OAuthAccessTokenExpired {
Jws<Claims> token;
try {
token = readAndVerifyToken(accessToken);
} catch (AuthException e) {
throw new AuthCoreProvider.OAuthAccessTokenExpired("Can't read token", e);
}
var user = createUserFromToken(token);
long expiresIn = 0;
var expDate = token.getPayload().getExpiration();
if (expDate != null) {
expiresIn = expDate.toInstant().toEpochMilli();
}
return new OpenIDUserSession(user, accessToken, expiresIn);
}
public TokenResponse authorize(AuthCodePassword authCode) throws IOException {
var uri = URI.create(authCode.uri);
var queries = QueryHelper.splitUriQuery(uri);
String code = CommonHelper.multimapFirstOrNullValue("code", queries);
String error = CommonHelper.multimapFirstOrNullValue("error", queries);
String errorDescription = CommonHelper.multimapFirstOrNullValue("error_description", queries);
if (error != null && !error.isBlank()) {
throw new AuthException("Auth error. Error: %s, description: %s".formatted(error, errorDescription));
}
var postBody = QueryBuilder.post()
.addQuery("grant_type", "authorization_code")
.addQuery("code", code)
.addQuery("redirect_uri", openIDConfig.redirectUri())
.addQuery("client_id", openIDConfig.clientId())
.addQuery("client_secret", openIDConfig.clientSecret())
.toString();
var accessTokenResponse = requestToken(postBody);
var accessToken = accessTokenResponse.accessToken();
var refreshToken = accessTokenResponse.refreshToken();
readAndVerifyToken(accessToken);
var accessTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.expiresIn(), 0L);
var refreshTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.refreshExpiresIn(), 0L);
return new TokenResponse(accessToken, accessTokenExpiresIn,
refreshToken, refreshTokenExpiresIn);
}
public User createUserFromToken(String accessToken) throws AuthException {
return createUserFromToken(readAndVerifyToken(accessToken));
}
private Jws<Claims> readAndVerifyToken(String accessToken) throws AuthException {
if (accessToken == null) {
throw new AuthException("Token is null");
}
try {
return jwtParser.parseSignedClaims(accessToken);
} catch (JwtException e) {
throw new AuthException("Bad token", e);
}
}
private User createUserFromToken(Jws<Claims> token) {
var username = token.getPayload().get(openIDConfig.extractorConfig().usernameClaim(), String.class);
var uuidStr = token.getPayload().get(openIDConfig.extractorConfig().uuidClaim(), String.class);
var uuid = UUID.fromString(uuidStr);
return new UserEntity(username, uuid, new ClientPermissions());
}
private AccessTokenResponse requestToken(String postBody) {
var request = HttpRequest.newBuilder()
.uri(openIDConfig.tokenUri())
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(postBody))
.build();
HttpResponse<String> resp;
try {
resp = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
return Launcher.gsonManager.gson.fromJson(resp.body(), AccessTokenResponse.class);
}
private static KeyLocator loadKeyLocator(OpenIDConfig openIDConfig) {
var request = HttpRequest.newBuilder(openIDConfig.jwksUri()).GET().build();
HttpResponse<String> response;
try {
response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
var jwks = Jwks.setParser().build().parse(response.body());
return new KeyLocator(jwks);
}
private static class KeyLocator extends LocatorAdapter<Key> {
private final Map<String, Key> keys;
public KeyLocator(JwkSet jwks) {
this.keys = jwks.getKeys().stream().collect(
Collectors.toMap(jwk -> String.valueOf(jwk.get("kid")), Jwk::toKey));
}
@Override
protected Key locate(JweHeader header) {
return super.locate(header);
}
@Override
protected Key locate(JwsHeader header) {
return keys.get(header.getKeyId());
}
@Override
protected Key doLocate(Header header) {
return super.doLocate(header);
}
}
record OpenIDUserSession(User user, String token, long expiresIn) implements UserSession {
@Override
public String getID() {
return user.getUsername();
}
@Override
public User getUser() {
return user;
}
@Override
public String getMinecraftAccessToken() {
return token;
}
@Override
public long getExpireIn() {
return expiresIn;
}
}
}

View file

@ -0,0 +1,10 @@
package pro.gravit.launchserver.auth.core.openid;
import java.net.URI;
public record OpenIDConfig(URI tokenUri, String authorizationEndpoint, String clientId, String clientSecret,
String redirectUri, URI jwksUri, String scopes, String issuer,
ClaimExtractorConfig extractorConfig) {
public record ClaimExtractorConfig(String usernameClaim, String uuidClaim) {}
}

View file

@ -0,0 +1,59 @@
package pro.gravit.launchserver.auth.core.openid;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* @author Xakep_SDK
*/
public class QueryBuilder {
private final String uri;
private final StringBuilder query = new StringBuilder();
public QueryBuilder(String uri) {
this.uri = uri;
}
public static QueryBuilder get(String uri) {
Objects.requireNonNull(uri, "uri");
if (uri.endsWith("/")) {
uri = uri.substring(0, uri.length() - 1);
}
return new QueryBuilder(uri);
}
public static QueryBuilder post() {
return new QueryBuilder(null);
}
public QueryBuilder addQuery(String key, String value) {
if (!query.isEmpty()) {
query.append('&');
}
query.append(URLEncoder.encode(key, StandardCharsets.UTF_8))
.append('=')
.append(URLEncoder.encode(value, StandardCharsets.UTF_8));
return this;
}
public String toUriString() {
if (uri != null) {
if (query. isEmpty()) {
return uri;
}
return uri + '?' + query;
}
return toQueryString();
}
public String toQueryString() {
return query.toString();
}
@Override
public String toString() {
return toUriString();
}
}

View file

@ -0,0 +1,97 @@
package pro.gravit.launchserver.auth.core.openid;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import pro.gravit.utils.helper.LogHelper;
import java.sql.SQLException;
import java.util.UUID;
public class SQLServerSessionStore implements ServerSessionStore {
private static final String CREATE_TABLE = """
create table if not exists `gravit_server_session` (
id int auto_increment,
uuid varchar(36),
username varchar(255),
server_id varchar(41),
primary key (id),
unique (uuid),
unique (username)
);
""";
private static final String DELETE_SERVER_ID = """
delete from `gravit_server_session` where uuid = ?
""";
private static final String INSERT_SERVER_ID = """
insert into `gravit_server_session` (uuid, username, server_id) values (?, ?, ?)
""";
private static final String SELECT_SERVER_ID_BY_USERNAME = """
select server_id from `gravit_server_session` where username = ?
""";
private final SQLSourceConfig sqlSourceConfig;
public SQLServerSessionStore(SQLSourceConfig sqlSourceConfig) {
this.sqlSourceConfig = sqlSourceConfig;
}
@Override
public boolean joinServer(UUID uuid, String username, String serverId) {
try (var connection = sqlSourceConfig.getConnection()) {
connection.setAutoCommit(false);
var savepoint = connection.setSavepoint();
try (var deleteServerIdStmt = connection.prepareStatement(DELETE_SERVER_ID);
var insertServerIdStmt = connection.prepareStatement(INSERT_SERVER_ID)) {
deleteServerIdStmt.setString(1, uuid.toString());
deleteServerIdStmt.execute();
insertServerIdStmt.setString(1, uuid.toString());
insertServerIdStmt.setString(2, username);
insertServerIdStmt.setString(3, serverId);
insertServerIdStmt.execute();
connection.commit();
return true;
} catch (Exception e) {
connection.rollback(savepoint);
throw e;
}
} catch (SQLException e) {
LogHelper.debug("Can't join server. Username: %s".formatted(username));
LogHelper.error(e);
}
return false;
}
@Override
public String getServerIdByUsername(String username) {
try (var connection = sqlSourceConfig.getConnection();
var selectServerId = connection.prepareStatement(SELECT_SERVER_ID_BY_USERNAME)) {
selectServerId.setString(1, username);
try (var rs = selectServerId.executeQuery()) {
if (!rs.next()) {
return null;
}
return rs.getString("server_id");
}
} catch (SQLException e) {
LogHelper.debug("Can't find server id by username. Username: %s".formatted(username));
LogHelper.error(e);
}
return null;
}
public void init() {
try (var connection = sqlSourceConfig.getConnection()) {
connection.setAutoCommit(false);
var savepoint = connection.setSavepoint();
try (var createTableStmt = connection.prepareStatement(CREATE_TABLE)) {
createTableStmt.execute();
connection.commit();
} catch (Exception e) {
connection.rollback(savepoint);
throw e;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,124 @@
package pro.gravit.launchserver.auth.core.openid;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launchserver.auth.HikariSQLSourceConfig;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.utils.helper.LogHelper;
import java.sql.SQLException;
import java.util.UUID;
public class SQLUserStore implements UserStore {
private static final String CREATE_USER_TABLE = """
create table if not exists `gravit_user` (
id int auto_increment,
uuid varchar(36),
username varchar(255),
primary key (id),
unique (uuid),
unique (username)
)
""";
private static final String INSERT_USER = """
insert into `gravit_user` (uuid, username) values (?, ?)
""";
private static final String DELETE_USER_BY_NAME = """
delete `gravit_user` where username = ?
""";
private static final String SELECT_USER_BY_NAME = """
select uuid, username from `gravit_user` where username = ?
""";
private static final String SELECT_USER_BY_UUID = """
select uuid, username from `gravit_user` where uuid = ?
""";
private final HikariSQLSourceConfig sqlSourceConfig;
public SQLUserStore(HikariSQLSourceConfig sqlSourceConfig) {
this.sqlSourceConfig = sqlSourceConfig;
}
@Override
public User getByUsername(String username) {
try (var connection = sqlSourceConfig.getConnection();
var selectUserStmt = connection.prepareStatement(SELECT_USER_BY_NAME)) {
selectUserStmt.setString(1, username);
try (var rs = selectUserStmt.executeQuery()) {
if (!rs.next()) {
LogHelper.debug("User not found, username: %s".formatted(username));
return null;
}
return new UserEntity(rs.getString("username"),
UUID.fromString(rs.getString("uuid")),
new ClientPermissions());
}
} catch (SQLException e) {
LogHelper.error(e);
}
return null;
}
@Override
public User getUserByUUID(UUID uuid) {
try (var connection = sqlSourceConfig.getConnection();
var selectUserStmt = connection.prepareStatement(SELECT_USER_BY_UUID)) {
selectUserStmt.setString(1, uuid.toString());
try (var rs = selectUserStmt.executeQuery()) {
if (!rs.next()) {
LogHelper.debug("User not found, UUID: %s".formatted(uuid));
return null;
}
return new UserEntity(rs.getString("username"),
UUID.fromString(rs.getString("uuid")),
new ClientPermissions());
}
} catch (SQLException e) {
LogHelper.error(e);
}
return null;
}
@Override
public void createOrUpdateUser(User user) {
try (var connection = sqlSourceConfig.getConnection()) {
connection.setAutoCommit(false);
var savepoint = connection.setSavepoint();
try (var deleteUserStmt = connection.prepareStatement(DELETE_USER_BY_NAME);
var insertUserStmt = connection.prepareStatement(INSERT_USER)) {
deleteUserStmt.setString(1, user.getUsername());
deleteUserStmt.execute();
insertUserStmt.setString(1, user.getUUID().toString());
insertUserStmt.setString(2, user.getUsername());
insertUserStmt.execute();
connection.commit();
LogHelper.debug("User saved. UUID: %s, username: %s".formatted(user.getUUID(), user.getUsername()));
} catch (Exception e) {
connection.rollback(savepoint);
throw e;
}
} catch (SQLException e) {
LogHelper.debug("Failed to save user");
LogHelper.error(e);
throw new RuntimeException("Failed to save user", e);
}
}
public void init() {
try (var connection = sqlSourceConfig.getConnection()) {
connection.setAutoCommit(false);
var savepoint = connection.setSavepoint();
try (var createUserTableStmt = connection.prepareStatement(CREATE_USER_TABLE)) {
createUserTableStmt.execute();
connection.commit();
} catch (Exception e) {
connection.rollback(savepoint);
throw e;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,8 @@
package pro.gravit.launchserver.auth.core.openid;
import java.util.UUID;
public interface ServerSessionStore {
boolean joinServer(UUID uuid, String username, String serverId);
String getServerIdByUsername(String username);
}

View file

@ -0,0 +1,5 @@
package pro.gravit.launchserver.auth.core.openid;
public record TokenResponse(String accessToken, long accessTokenExpiresIn,
String refreshToken, long refreshTokenExpiresIn) {
}

View file

@ -0,0 +1,23 @@
package pro.gravit.launchserver.auth.core.openid;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launchserver.auth.core.User;
import java.util.UUID;
record UserEntity(String username, UUID uuid, ClientPermissions permissions) implements User {
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
}

View file

@ -0,0 +1,13 @@
package pro.gravit.launchserver.auth.core.openid;
import pro.gravit.launchserver.auth.core.User;
import java.util.UUID;
public interface UserStore {
User getByUsername(String username);
User getUserByUUID(UUID uuid);
void createOrUpdateUser(User user);
}

View file

@ -0,0 +1,31 @@
package pro.gravit.launchserver.auth.mix;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.utils.ProviderMap;
public abstract class MixProvider implements AutoCloseable{
public static final ProviderMap<MixProvider> providers = new ProviderMap<>("MixProvider");
private static final Logger logger = LogManager.getLogger();
private static boolean registredProviders = false;
public static void registerProviders() {
if (!registredProviders) {
providers.register("uploadAsset", UploadAssetMixProvider.class);
registredProviders = true;
}
}
public abstract void init(LaunchServer server, AuthCoreProvider core);
@SuppressWarnings("unchecked")
public <T> T isSupport(Class<T> clazz) {
if (clazz.isAssignableFrom(getClass())) return (T) this;
return null;
}
@Override
public abstract void close();
}

View file

@ -0,0 +1,34 @@
package pro.gravit.launchserver.auth.mix;
import pro.gravit.launcher.base.events.request.AssetUploadInfoRequestEvent;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportAssetUpload;
import java.util.Map;
public class UploadAssetMixProvider extends MixProvider implements AuthSupportAssetUpload {
public Map<String, String> urls;
public AssetUploadInfoRequestEvent.SlimSupportConf slimSupportConf;
@Override
public String getAssetUploadUrl(String name, User user) {
return urls.get(name);
}
@Override
public AssetUploadInfoRequestEvent getAssetUploadInfo(User user) {
return new AssetUploadInfoRequestEvent(urls.keySet(), slimSupportConf);
}
@Override
public void init(LaunchServer server, AuthCoreProvider core) {
}
@Override
public void close() {
}
}

View file

@ -2,7 +2,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.base.Launcher;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -14,7 +14,7 @@
import java.time.Duration;
public class JsonPasswordVerifier extends PasswordVerifier {
private static transient final Logger logger = LogManager.getLogger();
private static final Logger logger = LogManager.getLogger();
private transient final HttpClient client = HttpClient.newBuilder().build();
public String url;
public String bearerToken;

View file

@ -0,0 +1,108 @@
package pro.gravit.launchserver.auth.profiles;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.utils.helper.IOHelper;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
public class LocalProfileProvider extends ProfileProvider {
public String profilesDir = "profiles";
private transient volatile Map<Path, ClientProfile> profilesMap;
private transient volatile Set<ClientProfile> profilesList; // Cache
@Override
public void sync() throws IOException {
Path profilesDirPath = Path.of(profilesDir);
if (!IOHelper.isDir(profilesDirPath))
Files.createDirectory(profilesDirPath);
Map<Path, ClientProfile> newProfiles = new HashMap<>();
IOHelper.walk(profilesDirPath, new ProfilesFileVisitor(newProfiles), false);
Set<ClientProfile> newProfilesList = new HashSet<>(newProfiles.values());
profilesMap = newProfiles;
profilesList = newProfilesList;
}
@Override
public Set<ClientProfile> getProfiles() {
return profilesList;
}
@Override
public void addProfile(ClientProfile profile) throws IOException {
Path profilesDirPath = Path.of(profilesDir);
ClientProfile oldProfile;
Path target = null;
for(var e : profilesMap.entrySet()) {
if(e.getValue().getUUID().equals(profile.getUUID())) {
target = e.getKey();
}
}
if(target == null) {
target = profilesDirPath.resolve(profile.getTitle()+".json");
oldProfile = profilesMap.get(target);
if(oldProfile != null && !oldProfile.getUUID().equals(profile.getUUID())) {
throw new FileAlreadyExistsException(target.toString());
}
}
try (BufferedWriter writer = IOHelper.newWriter(target)) {
Launcher.gsonManager.configGson.toJson(profile, writer);
}
addProfile(target, profile);
}
@Override
public void deleteProfile(ClientProfile profile) throws IOException {
for(var e : profilesMap.entrySet()) {
if(e.getValue().getUUID().equals(profile.getUUID())) {
Files.deleteIfExists(e.getKey());
profilesMap.remove(e.getKey());
profilesList.remove(e.getValue());
break;
}
}
}
private void addProfile(Path path, ClientProfile profile) {
for(var e : profilesMap.entrySet()) {
if(e.getValue().getUUID().equals(profile.getUUID())) {
profilesMap.remove(e.getKey());
profilesList.remove(e.getValue());
break;
}
}
profilesMap.put(path, profile);
profilesList.add(profile);
}
private static final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
private final Map<Path, ClientProfile> result;
private final Logger logger = LogManager.getLogger();
private ProfilesFileVisitor(Map<Path, ClientProfile> result) {
this.result = result;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
logger.info("Syncing '{}' profile", IOHelper.getFileName(file));
// Read profile
ClientProfile profile;
try (BufferedReader reader = IOHelper.newReader(file)) {
profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class);
}
profile.verify();
// Add SIGNED profile to result list
result.put(file.toAbsolutePath(), profile);
return super.visitFile(file, attrs);
}
}
}

View file

@ -0,0 +1,76 @@
package pro.gravit.launchserver.auth.profiles;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.utils.ProviderMap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public abstract class ProfileProvider {
public static final ProviderMap<ProfileProvider> providers = new ProviderMap<>("ProfileProvider");
private static boolean registredProviders = false;
protected transient LaunchServer server;
public static void registerProviders() {
if (!registredProviders) {
providers.register("local", LocalProfileProvider.class);
registredProviders = true;
}
}
public void init(LaunchServer server) {
this.server = server;
}
public abstract void sync() throws IOException;
public abstract Set<ClientProfile> getProfiles();
public abstract void addProfile(ClientProfile profile) throws IOException;
public abstract void deleteProfile(ClientProfile profile) throws IOException;
public void close() {
}
public ClientProfile getProfile(UUID uuid) {
for(var e : getProfiles()) {
if(e.getUUID().equals(uuid)) {
return e;
}
}
return null;
}
public ClientProfile getProfile(String title) {
for(var e : getProfiles()) {
if(e.getTitle().equals(title)) {
return e;
}
}
return null;
}
public List<ClientProfile> getProfiles(Client client) {
List<ClientProfile> profileList;
Set<ClientProfile> serverProfiles = getProfiles();
if (server.config.protectHandler instanceof ProfilesProtectHandler protectHandler) {
profileList = new ArrayList<>(4);
for (ClientProfile profile : serverProfiles) {
if (protectHandler.canGetProfile(profile, client)) {
profileList.add(profile);
}
}
} else {
profileList = List.copyOf(serverProfiles);
}
return profileList;
}
}

View file

@ -4,9 +4,9 @@
import io.jsonwebtoken.Jwts;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.events.request.GetSecureLevelInfoRequestEvent;
import pro.gravit.launcher.events.request.HardwareReportRequestEvent;
import pro.gravit.launcher.events.request.VerifySecureLevelKeyRequestEvent;
import pro.gravit.launcher.base.events.request.GetSecureLevelInfoRequestEvent;
import pro.gravit.launcher.base.events.request.HardwareReportRequestEvent;
import pro.gravit.launcher.base.events.request.VerifySecureLevelKeyRequestEvent;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
@ -15,28 +15,20 @@
import pro.gravit.launchserver.auth.protect.interfaces.JoinServerProtectHandler;
import pro.gravit.launchserver.auth.protect.interfaces.SecureProtectHandler;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.launchserver.socket.response.auth.RestoreResponse;
import pro.gravit.launchserver.socket.response.secure.HardwareReportResponse;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
import static java.util.concurrent.TimeUnit.SECONDS;
public class AdvancedProtectHandler extends StdProtectHandler implements SecureProtectHandler, HardwareProtectHandler, JoinServerProtectHandler {
private transient final Logger logger = LogManager.getLogger();
public boolean enableHardwareFeature;
private transient LaunchServer server;
@Override
public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign;
}
@Override
public void checkLaunchServerLicense() {
}
@Override
public GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event) {
return event;
@ -50,13 +42,17 @@ public boolean allowGetSecureLevelInfo(Client client) {
@Override
public void onHardwareReport(HardwareReportResponse response, Client client) {
if (!enableHardwareFeature) {
response.sendResult(new HardwareReportRequestEvent(null));
response.sendResult(new HardwareReportRequestEvent());
return;
}
if (!client.isAuth || client.trustLevel == null || client.trustLevel.publicKey == null) {
response.sendError("Access denied");
return;
}
if(client.trustLevel.hardwareInfo != null) {
response.sendResult(new HardwareReportRequestEvent(createHardwareToken(client.username, client.trustLevel.hardwareInfo), SECONDS.toMillis(server.config.netty.security.hardwareTokenExpire)));
return;
}
logger.debug("HardwareInfo received");
{
var authSupportHardware = client.auth.isSupport(AuthSupportHardware.class);
@ -71,13 +67,11 @@ public void onHardwareReport(HardwareReportResponse response, Client client) {
if (hardware.isBanned()) {
throw new SecurityException("Your hardware banned");
}
client.trustLevel.hardwareInfo = hardware.getHardwareInfo();
response.sendResult(new HardwareReportRequestEvent(createHardwareToken(client.username, hardware)));
return;
client.trustLevel.hardwareInfo = hardware;
response.sendResult(new HardwareReportRequestEvent(createHardwareToken(client.username, hardware), SECONDS.toMillis(server.config.netty.security.hardwareTokenExpire)));
} else {
logger.error("AuthCoreProvider not supported hardware");
response.sendError("AuthCoreProvider not supported hardware");
return;
}
}
}
@ -89,22 +83,22 @@ public VerifySecureLevelKeyRequestEvent onSuccessVerify(Client client) {
if (authSupportHardware != null) {
UserHardware hardware = authSupportHardware.getHardwareInfoByPublicKey(client.trustLevel.publicKey);
if (hardware == null) //HWID not found?
return new VerifySecureLevelKeyRequestEvent(true, false, createPublicKeyToken(client.username, client.trustLevel.publicKey));
return new VerifySecureLevelKeyRequestEvent(true, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire));
if (hardware.isBanned()) {
throw new SecurityException("Your hardware banned");
}
client.trustLevel.hardwareInfo = hardware.getHardwareInfo();
client.trustLevel.hardwareInfo = hardware;
authSupportHardware.connectUserAndHardware(client.sessionObject, hardware);
return new VerifySecureLevelKeyRequestEvent(false, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), createHardwareToken(client.username, hardware));
return new VerifySecureLevelKeyRequestEvent(false, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire));
} else {
logger.warn("AuthCoreProvider not supported hardware. HardwareInfo not checked!");
}
}
return new VerifySecureLevelKeyRequestEvent(false, false, createPublicKeyToken(client.username, client.trustLevel.publicKey));
return new VerifySecureLevelKeyRequestEvent(false, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire));
}
@Override
public boolean onJoinServer(String serverID, String username, Client client) {
public boolean onJoinServer(String serverID, String username, UUID uuid, Client client) {
return !enableHardwareFeature || (client.trustLevel != null && client.trustLevel.hardwareInfo != null);
}
@ -113,15 +107,11 @@ public void init(LaunchServer server) {
this.server = server;
}
@Override
public void close() {
}
public String createHardwareToken(String username, UserHardware hardware) {
return Jwts.builder()
.setIssuer("LaunchServer")
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + 1000 * server.config.netty.security.hardwareTokenExpire))
.setExpiration(new Date(System.currentTimeMillis() + SECONDS.toMillis(server.config.netty.security.hardwareTokenExpire)))
.claim("hardware", hardware.getId())
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
.compact();
@ -131,22 +121,20 @@ public String createPublicKeyToken(String username, byte[] publicKey) {
return Jwts.builder()
.setIssuer("LaunchServer")
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + 1000 * server.config.netty.security.publicKeyTokenExpire))
.setExpiration(new Date(System.currentTimeMillis() + SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire)))
.claim("publicKey", Base64.getEncoder().encodeToString(publicKey))
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
.compact();
}
public static class HardwareInfoTokenVerifier implements RestoreResponse.ExtendedTokenProvider {
private transient final LaunchServer server;
private transient final Logger logger = LogManager.getLogger();
private final JwtParser parser;
public HardwareInfoTokenVerifier(LaunchServer server) {
this.server = server;
this.parser = Jwts.parserBuilder()
this.parser = Jwts.parser()
.requireIssuer("LaunchServer")
.setSigningKey(server.keyAgreementManager.ecdsaPublicKey)
.verifyWith(server.keyAgreementManager.ecdsaPublicKey)
.build();
}
@ -161,7 +149,7 @@ public boolean accept(Client client, AuthProviderPair pair, String extendedToken
if (hardwareSupport == null) return false;
UserHardware hardware = hardwareSupport.getHardwareInfoById(hardwareInfoId);
if (client.trustLevel == null) client.trustLevel = new Client.TrustLevel();
client.trustLevel.hardwareInfo = hardware.getHardwareInfo();
client.trustLevel.hardwareInfo = hardware;
return true;
} catch (Throwable e) {
logger.error("Hardware JWT error", e);
@ -172,15 +160,13 @@ public boolean accept(Client client, AuthProviderPair pair, String extendedToken
}
public static class PublicKeyTokenVerifier implements RestoreResponse.ExtendedTokenProvider {
private transient final LaunchServer server;
private transient final Logger logger = LogManager.getLogger();
private final JwtParser parser;
public PublicKeyTokenVerifier(LaunchServer server) {
this.server = server;
this.parser = Jwts.parserBuilder()
this.parser = Jwts.parser()
.requireIssuer("LaunchServer")
.setSigningKey(server.keyAgreementManager.ecdsaPublicKey)
.verifyWith(server.keyAgreementManager.ecdsaPublicKey)
.build();
}

View file

@ -1,5 +1,6 @@
package pro.gravit.launchserver.auth.protect;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
public class NoProtectHandler extends ProtectHandler {
@ -10,7 +11,7 @@ public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
}
@Override
public void checkLaunchServerLicense() {
// None
public boolean allowJoinServer(Client client) {
return true;
}
}

View file

@ -1,6 +1,7 @@
package pro.gravit.launchserver.auth.protect;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.ProviderMap;
@ -19,8 +20,9 @@ public static void registerHandlers() {
}
public abstract boolean allowGetAccessToken(AuthResponse.AuthContext context);
public abstract void checkLaunchServerLicense(); //Выдает SecurityException при ошибке проверки лицензии
public boolean allowJoinServer(Client client) {
return client.isAuth && client.type == AuthResponse.ConnectTypes.CLIENT;
}
public void init(LaunchServer server) {

View file

@ -2,7 +2,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.socket.Client;
@ -20,26 +20,21 @@ public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign;
}
@Override
public void checkLaunchServerLicense() {
}
@Override
public void init(LaunchServer server) {
if (profileWhitelist != null && profileWhitelist.size() > 0) {
if (profileWhitelist != null && !profileWhitelist.isEmpty()) {
logger.warn("profileWhitelist deprecated. Please use permission 'launchserver.profile.PROFILE_UUID.show' and 'launchserver.profile.PROFILE_UUID.enter'");
}
}
@Override
public boolean canGetProfile(ClientProfile profile, Client client) {
return !profile.isLimited() || isWhitelisted("launchserver.profile.%s.show", profile, client);
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.show", profile, client);
}
@Override
public boolean canChangeProfile(ClientProfile profile, Client client) {
return !profile.isLimited() || isWhitelisted("launchserver.profile.%s.enter", profile, client);
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.enter", profile, client);
}
@Override
@ -49,17 +44,16 @@ public boolean canGetUpdates(String updatesDirName, Client client) {
private boolean isWhitelisted(String property, ClientProfile profile, Client client) {
if (client.permissions != null) {
String permByUUID = String.format(property, profile.getUUID());
String permByUUID = property.formatted(profile.getUUID());
if (client.permissions.hasPerm(permByUUID)) {
return true;
}
String permByTitle = String.format(property, profile.getTitle().toLowerCase(Locale.ROOT));
String permByTitle = property.formatted(profile.getTitle().toLowerCase(Locale.ROOT));
if (client.permissions.hasPerm(permByTitle)) {
return true;
}
}
List<String> allowedUsername = profileWhitelist.get(profile.getTitle());
if (allowedUsername != null && allowedUsername.contains(client.username)) return true;
return false;
return allowedUsername != null && allowedUsername.contains(client.username);
}
}

View file

@ -2,8 +2,10 @@
import pro.gravit.launchserver.socket.Client;
import java.util.UUID;
public interface JoinServerProtectHandler {
default boolean onJoinServer(String serverID, String username, Client client) {
default boolean onJoinServer(String serverID, String username, UUID uuid, Client client) {
return true;
}
}

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.protect.interfaces;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.socket.Client;
public interface ProfilesProtectHandler {

View file

@ -1,8 +1,8 @@
package pro.gravit.launchserver.auth.protect.interfaces;
import pro.gravit.launcher.events.request.GetSecureLevelInfoRequestEvent;
import pro.gravit.launcher.events.request.SecurityReportRequestEvent;
import pro.gravit.launcher.events.request.VerifySecureLevelKeyRequestEvent;
import pro.gravit.launcher.base.events.request.GetSecureLevelInfoRequestEvent;
import pro.gravit.launcher.base.events.request.SecurityReportRequestEvent;
import pro.gravit.launcher.base.events.request.VerifySecureLevelKeyRequestEvent;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.secure.SecurityReportResponse;
import pro.gravit.utils.helper.SecurityHelper;

View file

@ -3,36 +3,37 @@
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.HTTPRequest;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.profiles.Texture;
import pro.gravit.launcher.base.profiles.Texture;
import pro.gravit.launchserver.HttpRequester;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class JsonTextureProvider extends TextureProvider {
private transient static final Type MAP_TYPE = new TypeToken<Map<String, Texture>>() {
private static final Type MAP_TYPE = new TypeToken<Map<String, JsonTexture>>() {
}.getType();
private transient final Logger logger = LogManager.getLogger();
private transient final HttpRequester requester = new HttpRequester();
public String url;
public String bearerToken;
@Override
public void close() throws IOException {
public void close() {
//None
}
@Override
public Texture getCloakTexture(UUID uuid, String username, String client) throws IOException {
public Texture getCloakTexture(UUID uuid, String username, String client) {
logger.warn("Ineffective get cloak texture for {}", username);
return getAssets(uuid, username, client).get("CAPE");
}
@Override
public Texture getSkinTexture(UUID uuid, String username, String client) throws IOException {
public Texture getSkinTexture(UUID uuid, String username, String client) {
logger.warn("Ineffective get skin texture for {}", username);
return getAssets(uuid, username, client).get("SKIN");
}
@ -40,24 +41,28 @@ public Texture getSkinTexture(UUID uuid, String username, String client) throws
@Override
public Map<String, Texture> getAssets(UUID uuid, String username, String client) {
try {
var result = HTTPRequest.jsonRequest(null, "GET", new URL(RequestTextureProvider.getTextureURL(url, uuid, username, client)));
Map<String, Texture> map = Launcher.gsonManager.gson.fromJson(result, MAP_TYPE);
if (map == null) {
return new HashMap<>();
}
if (map.get("skin") != null) { // Legacy script
map.put("SKIN", map.get("skin"));
map.remove("skin");
}
if (map.get("cloak") != null) {
map.put("CAPE", map.get("cloak"));
map.remove("cloak");
}
return map;
Map<String, JsonTexture> map = requester.<Map<String, JsonTexture>>send(requester.get(RequestTextureProvider.getTextureURL(url, uuid, username, client), bearerToken), MAP_TYPE).getOrThrow();
return JsonTexture.convertMap(map);
} catch (IOException e) {
logger.error("JsonTextureProvider", e);
return new HashMap<>();
}
}
public record JsonTexture(String url, String digest, Map<String, String> metadata) {
public Texture toTexture() {
return new Texture(url, digest == null ? null : SecurityHelper.fromHex(digest), metadata);
}
public static Map<String, Texture> convertMap(Map<String, JsonTexture> map) {
if (map == null) {
return new HashMap<>();
}
Map<String, Texture> res = new HashMap<>();
for(var e : map.entrySet()) {
res.put(e.getKey(), e.getValue().toTexture());
}
return res;
}
}
}

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.texture;
import pro.gravit.launcher.profiles.Texture;
import pro.gravit.launcher.base.profiles.Texture;
import pro.gravit.utils.helper.VerifyHelper;
import java.io.IOException;

View file

@ -1,7 +1,7 @@
package pro.gravit.launchserver.auth.texture;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.profiles.Texture;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.profiles.Texture;
import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.IOHelper;
@ -29,7 +29,7 @@ public RequestTextureProvider(String skinURL, String cloakURL) {
private static Texture getTexture(String url, boolean cloak) throws IOException {
try {
return new Texture(url, cloak);
return new Texture(url, cloak, null);
} catch (FileNotFoundException ignored) {
return null; // Simply not found
}
@ -37,7 +37,7 @@ private static Texture getTexture(String url, boolean cloak) throws IOException
private static Texture getTexture(String url, Path local, boolean cloak) throws IOException {
try {
return new Texture(url, local, cloak);
return new Texture(url, local, cloak, null);
} catch (FileNotFoundException ignored) {
return null; // Simply not found
}
@ -60,7 +60,8 @@ public Texture getCloakTexture(UUID uuid, String username, String client) throws
if (cloakLocalPath == null) {
return getTexture(textureUrl, true);
} else {
return getTexture(textureUrl, Paths.get(cloakLocalPath), true);
String path = getTextureURL(cloakLocalPath, uuid, username, client);
return getTexture(textureUrl, Paths.get(path), true);
}
}
@ -70,7 +71,8 @@ public Texture getSkinTexture(UUID uuid, String username, String client) throws
if (skinLocalPath == null) {
return getTexture(textureUrl, false);
} else {
return getTexture(textureUrl, Paths.get(skinLocalPath), false);
String path = getTextureURL(skinLocalPath, uuid, username, client);
return getTexture(textureUrl, Paths.get(path), false);
}
}
}

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.texture;
import pro.gravit.launcher.profiles.Texture;
import pro.gravit.launcher.base.profiles.Texture;
import pro.gravit.utils.ProviderMap;
import java.io.IOException;

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.texture;
import pro.gravit.launcher.profiles.Texture;
import pro.gravit.launcher.base.profiles.Texture;
import java.util.UUID;

View file

@ -0,0 +1,186 @@
package pro.gravit.launchserver.auth.updates;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launcher.core.serialize.HInput;
import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.stream.Stream;
public class LocalUpdatesProvider extends UpdatesProvider {
private final transient Logger logger = LogManager.getLogger();
public String cacheFile = ".updates-cache";
public String updatesDir = "updates";
public boolean cacheUpdates = true;
private volatile transient Map<String, HashedDir> updatesDirMap;
private void writeCache(Path file) throws IOException {
try (HOutput output = new HOutput(IOHelper.newOutput(file))) {
output.writeLength(updatesDirMap.size(), 0);
for (Map.Entry<String, HashedDir> entry : updatesDirMap.entrySet()) {
output.writeString(entry.getKey(), 0);
entry.getValue().write(output);
}
}
logger.debug("Saved {} updates to cache", updatesDirMap.size());
}
private void readCache(Path file) throws IOException {
Map<String, HashedDir> updatesDirMap = new HashMap<>(16);
try (HInput input = new HInput(IOHelper.newInput(file))) {
int size = input.readLength(0);
for (int i = 0; i < size; ++i) {
String name = input.readString(0);
HashedDir dir = new HashedDir(input);
updatesDirMap.put(name, dir);
}
}
logger.debug("Found {} updates from cache", updatesDirMap.size());
this.updatesDirMap = Collections.unmodifiableMap(updatesDirMap);
}
public void readUpdatesFromCache() throws IOException {
readCache(Path.of(cacheFile));
}
public void readUpdatesDir() throws IOException {
var cacheFilePath = Path.of(cacheFile);
if (cacheUpdates) {
if (Files.exists(cacheFilePath)) {
try {
readCache(cacheFilePath);
return;
} catch (Throwable e) {
logger.error("Read updates cache failed", e);
}
}
}
sync(null);
}
@Override
public void init(LaunchServer server) {
super.init(server);
try {
if (!IOHelper.isDir(Path.of(updatesDir)))
Files.createDirectory(Path.of(updatesDir));
} catch (IOException e) {
logger.error("Updates not synced", e);
}
}
@Override
public void syncInitially() throws IOException {
readUpdatesDir();
}
public void sync(Collection<String> dirs) throws IOException {
logger.info("Syncing updates dir");
Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(Path.of(updatesDir))) {
for (final Path updateDir : dirStream) {
if (Files.isHidden(updateDir))
continue; // Skip hidden
// Resolve name and verify is dir
String name = IOHelper.getFileName(updateDir);
if (!IOHelper.isDir(updateDir)) {
if (!IOHelper.isFile(updateDir) && Stream.of(".jar", ".exe", ".hash").noneMatch(e -> updateDir.toString().endsWith(e)))
logger.warn("Not update dir: '{}'", name);
continue;
}
// Add from previous map (it's guaranteed to be non-null)
if (dirs != null && !dirs.contains(name)) {
HashedDir hdir = updatesDirMap.get(name);
if (hdir != null) {
newUpdatesDirMap.put(name, hdir);
continue;
}
}
// Sync and sign update dir
logger.info("Syncing '{}' update dir", name);
HashedDir updateHDir = new HashedDir(updateDir, null, true, true);
newUpdatesDirMap.put(name, updateHDir);
}
}
updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap);
if (cacheUpdates) {
try {
writeCache(Path.of(cacheFile));
} catch (Throwable e) {
logger.error("Write updates cache failed", e);
}
}
server.modulesManager.invokeEvent(new LaunchServerUpdatesSyncEvent(server));
}
@Override
public HashedDir getUpdatesDir(String updateName) {
return updatesDirMap.get(updateName);
}
private Path resolveUpdateName(String updateName) {
if(updateName == null) {
return Path.of(updatesDir);
}
return Path.of(updatesDir).resolve(updateName);
}
@Override
public void upload(String updateName, Map<String, Path> files, boolean deleteAfterUpload) throws IOException {
var path = resolveUpdateName(updateName);
for(var e : files.entrySet()) {
var target = path.resolve(e.getKey());
var source = e.getValue();
IOHelper.createParentDirs(target);
if(deleteAfterUpload) {
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
} else {
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
}
}
}
@Override
public Map<String, Path> download(String updateName, List<String> files) {
var path = resolveUpdateName(updateName);
Map<String, Path> map = new HashMap<>();
for(var e : files) {
map.put(e, path.resolve(e));
}
return map;
}
@Override
public void delete(String updateName, List<String> files) throws IOException {
var path = resolveUpdateName(updateName);
for(var e : files) {
var target = path.resolve(e);
Files.delete(target);
}
}
@Override
public void delete(String updateName) throws IOException {
var path = resolveUpdateName(updateName);
IOHelper.deleteDir(path, true);
}
@Override
public void create(String updateName) throws IOException {
var path = resolveUpdateName(updateName);
Files.createDirectories(path);
}
}

View file

@ -0,0 +1,52 @@
package pro.gravit.launchserver.auth.updates;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.ProviderMap;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public abstract class UpdatesProvider {
public static final ProviderMap<UpdatesProvider> providers = new ProviderMap<>("UpdatesProvider");
private static boolean registredProviders = false;
protected transient LaunchServer server;
public static void registerProviders() {
if (!registredProviders) {
providers.register("local", LocalUpdatesProvider.class);
registredProviders = true;
}
}
public void init(LaunchServer server) {
this.server = server;
}
public void sync() throws IOException {
sync(null);
}
public abstract void syncInitially() throws IOException;
public abstract void sync(Collection<String> updateNames) throws IOException;
public abstract HashedDir getUpdatesDir(String updateName);
public abstract void upload(String updateName, Map<String, Path> files, boolean deleteAfterUpload) throws IOException;
public abstract Map<String, Path> download(String updateName, List<String> files);
public abstract void delete(String updateName, List<String> files) throws IOException;
public abstract void delete(String updateName) throws IOException;
public abstract void create(String updateName) throws IOException;
public void close() {
}
}

View file

@ -4,24 +4,19 @@
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class BinaryPipeline {
public abstract class BinaryPipeline {
public final List<LauncherBuildTask> tasks = new ArrayList<>();
public final AtomicLong count = new AtomicLong(0);
public final Path buildDir;
public final String nameFormat;
private transient final Logger logger = LogManager.getLogger();
protected transient final Logger logger = LogManager.getLogger();
public BinaryPipeline(Path buildDir, String nameFormat) {
this.buildDir = buildDir;
@ -72,33 +67,19 @@ public <T extends LauncherBuildTask> Optional<T> getTaskByClass(Class<T> taskCla
return tasks.stream().filter(taskClass::isInstance).map(taskClass::cast).findFirst();
}
public void build(Path target, boolean deleteTempFiles) throws IOException {
logger.info("Building launcher binary file");
count.set(0); // set jar number
Path thisPath = null;
boolean isNeedDelete = false;
long time_start = System.currentTimeMillis();
long time_this = time_start;
for (LauncherBuildTask task : tasks) {
logger.info("Task {}", task.getName());
Path oldPath = thisPath;
thisPath = task.process(oldPath);
long time_task_end = System.currentTimeMillis();
long time_task = time_task_end - time_this;
time_this = time_task_end;
if (isNeedDelete && deleteTempFiles) Files.deleteIfExists(oldPath);
isNeedDelete = task.allowDelete();
logger.info("Task {} processed from {} millis", task.getName(), time_task);
public Optional<LauncherBuildTask> getTaskBefore(Predicate<LauncherBuildTask> pred) {
LauncherBuildTask last = null;
for(var e : tasks) {
if(pred.test(e)) {
return Optional.ofNullable(last);
}
long time_end = System.currentTimeMillis();
if (isNeedDelete && deleteTempFiles) IOHelper.move(thisPath, target);
else IOHelper.copy(thisPath, target);
IOHelper.deleteDir(buildDir, false);
logger.info("Build successful from {} millis", time_end - time_start);
last = e;
}
return Optional.empty();
}
public String nextName(String taskName) {
return String.format(nameFormat, taskName, count.getAndIncrement());
return nameFormat.formatted(taskName);
}
public Path nextPath(String taskName) {

View file

@ -2,9 +2,9 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.serialize.HOutput;
import pro.gravit.launcher.serialize.stream.StreamObject;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.launcher.core.serialize.stream.StreamObject;
import pro.gravit.launchserver.binary.tasks.MainBuildTask;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
@ -45,13 +45,18 @@ public class BuildContext {
public final MainBuildTask task;
public final HashSet<String> fileList;
public final HashSet<String> clientModules;
public final HashSet<String> legacyClientModules;
private Path runtimeDir;
private boolean deleteRuntimeDir;
public BuildContext(ZipOutputStream output, List<JarFile> readerClassPath, MainBuildTask task) {
public BuildContext(ZipOutputStream output, List<JarFile> readerClassPath, MainBuildTask task, Path runtimeDir) {
this.output = output;
this.readerClassPath = readerClassPath;
this.task = task;
this.runtimeDir = runtimeDir;
fileList = new HashSet<>(1024);
clientModules = new HashSet<>();
legacyClientModules = new HashSet<>();
}
public void pushFile(String filename, InputStream inputStream) throws IOException {
@ -101,6 +106,14 @@ public void pushJarFile(Path jarfile, Predicate<ZipEntry> filter, Predicate<Stri
pushJarFile(jarfile.toUri().toURL(), filter, needTransform);
}
public Path getRuntimeDir() {
return runtimeDir;
}
public void setRuntimeDir(Path runtimeDir) {
this.runtimeDir = runtimeDir;
}
public void pushJarFile(URL jarfile, Predicate<ZipEntry> filter, Predicate<String> needTransform) throws IOException {
try (ZipInputStream input = new ZipInputStream(IOHelper.newInput(jarfile))) {
ZipEntry e = input.getNextEntry();
@ -127,6 +140,16 @@ public void pushJarFile(URL jarfile, Predicate<ZipEntry> filter, Predicate<Strin
e = input.getNextEntry();
}
}
}
public boolean isDeleteRuntimeDir() {
return deleteRuntimeDir;
}
public void setDeleteRuntimeDir(boolean deleteRuntimeDir) {
this.deleteRuntimeDir = deleteRuntimeDir;
}
private final static class RuntimeDirVisitor extends SimpleFileVisitor<Path> {

View file

@ -1,17 +0,0 @@
package pro.gravit.launchserver.binary;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.tasks.exe.Launch4JTask;
public final class EXEL4JLauncherBinary extends LauncherBinary {
public EXEL4JLauncherBinary(LaunchServer server) {
super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s-%d.exe");
}
@Override
public void init() {
tasks.add(new Launch4JTask(server));
}
}

View file

@ -9,7 +9,7 @@
public class EXELauncherBinary extends LauncherBinary {
public EXELauncherBinary(LaunchServer server) {
super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s-%d.exe");
super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s.exe");
}
@Override

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.binary;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.tasks.*;
@ -16,7 +16,6 @@
public final class JARLauncherBinary extends LauncherBinary {
public final AtomicLong count;
public final Path runtimeDir;
public final Path guardDir;
public final Path buildDir;
public final List<Path> coreLibs;
public final List<Path> addonLibs;
@ -24,10 +23,9 @@ public final class JARLauncherBinary extends LauncherBinary {
public final Map<String, Path> files;
public JARLauncherBinary(LaunchServer server) throws IOException {
super(server, resolve(server, ".jar"), "Launcher-%s-%d.jar");
super(server, resolve(server, ".jar"), "Launcher-%s.jar");
count = new AtomicLong(0);
runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR);
guardDir = server.dir.resolve(Launcher.GUARD_DIR);
buildDir = server.dir.resolve("build");
coreLibs = new ArrayList<>();
addonLibs = new ArrayList<>();

View file

@ -1,11 +1,14 @@
package pro.gravit.launchserver.binary;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
public abstract class LauncherBinary extends BinaryPipeline {
public final LaunchServer server;
@ -19,11 +22,27 @@ protected LauncherBinary(LaunchServer server, Path binaryFile, String nameFormat
}
public static Path resolve(LaunchServer server, String ext) {
return server.config.copyBinaries ? server.updatesDir.resolve(server.config.binaryName + ext) : server.dir.resolve(server.config.binaryName + ext);
return Path.of(server.config.binaryName + ext);
}
public void build() throws IOException {
build(syncBinaryFile, server.config.launcher.deleteTempFiles);
logger.info("Building launcher binary file");
Path thisPath = null;
long time_start = System.currentTimeMillis();
long time_this = time_start;
for (LauncherBuildTask task : tasks) {
logger.info("Task {}", task.getName());
Path oldPath = thisPath;
thisPath = task.process(oldPath);
long time_task_end = System.currentTimeMillis();
long time_task = time_task_end - time_this;
time_this = time_task_end;
logger.info("Task {} processed from {} millis", task.getName(), time_task);
}
long time_end = System.currentTimeMillis();
server.config.updatesProvider.upload(null, Map.of(syncBinaryFile.toString(), thisPath), true);
IOHelper.deleteDir(buildDir, false);
logger.info("Build successful from {} millis", time_end - time_start);
}
public final boolean exists() {
@ -37,10 +56,14 @@ public final byte[] getDigest() {
public void init() {
}
public final boolean sync() throws IOException {
boolean exists = exists();
digest = exists ? SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, IOHelper.read(syncBinaryFile)) : null;
return exists;
public final boolean sync() {
try {
var target = syncBinaryFile.toString();
var path = server.config.updatesProvider.download(null, List.of(target)).get(target);
digest = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, IOHelper.read(path));
return true;
} catch (Throwable e) {
return false;
}
}
}

View file

@ -69,7 +69,6 @@ public SignerJar(ZipOutputStream out, Supplier<CMSSignedDataGenerator> gen, Stri
*
* @param filename name of the file to add (use forward slash as a path separator)
* @param contents contents of the file
* @throws IOException
* @throws NullPointerException if any of the arguments is {@code null}
*/
public void addFileContents(String filename, byte[] contents) throws IOException {
@ -82,7 +81,6 @@ public void addFileContents(String filename, byte[] contents) throws IOException
*
* @param filename name of the file to add (use forward slash as a path separator)
* @param contents contents of the file
* @throws IOException
* @throws NullPointerException if any of the arguments is {@code null}
*/
public void addFileContents(String filename, InputStream contents) throws IOException {
@ -95,7 +93,6 @@ public void addFileContents(String filename, InputStream contents) throws IOExce
*
* @param entry name of the file to add (use forward slash as a path separator)
* @param contents contents of the file
* @throws IOException
* @throws NullPointerException if any of the arguments is {@code null}
*/
public void addFileContents(ZipEntry entry, byte[] contents) throws IOException {
@ -108,7 +105,6 @@ public void addFileContents(ZipEntry entry, byte[] contents) throws IOException
*
* @param entry name of the file to add (use forward slash as a path separator)
* @param contents contents of the file
* @throws IOException
* @throws NullPointerException if any of the arguments is {@code null}
*/
public void addFileContents(ZipEntry entry, InputStream contents) throws IOException {
@ -134,7 +130,6 @@ public void addManifestAttribute(String name, String value) {
* Closes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It closes the
* underlying stream.
*
* @throws IOException
* @throws RuntimeException if the signing goes wrong
*/
@Override
@ -148,7 +143,6 @@ public void close() throws IOException {
* Finishes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It leaves the
* underlying stream open.
*
* @throws IOException
* @throws RuntimeException if the signing goes wrong
*/
public void finish() throws IOException {
@ -205,7 +199,6 @@ private byte[] signSigFile(byte[] sigContents) throws Exception {
* Writes the manifest to the JAR. It also calculates the digests that are required to be placed in the the signature
* file.
*
* @throws IOException
*/
private void writeManifest() throws IOException {
zos.putNextEntry(IOHelper.newZipEntry(MANIFEST_FN));
@ -268,7 +261,6 @@ private byte[] writeSigFile() throws IOException {
/**
* Signs the .SIG file and writes the signature (.RSA file) to the JAR.
*
* @throws IOException
* @throws RuntimeException if the signing failed
*/
private void writeSignature(byte[] sigFile) throws IOException {

View file

@ -54,7 +54,7 @@ public static void apply(Path inputFile, Path addFile, ZipOutputStream output, L
private static byte[] classFix(InputStream input, ClassMetadataReader reader, boolean stripNumbers) throws IOException {
ClassReader cr = new ClassReader(input);
ClassNode cn = new ClassNode();
cr.accept(cn, stripNumbers ? (ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) : ClassReader.SKIP_FRAMES);
cr.accept(cn, stripNumbers ? (ClassReader.SKIP_DEBUG) : 0);
ClassWriter cw = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
@ -74,9 +74,4 @@ public Path process(Path inputFile) throws IOException {
return out;
}
@Override
public boolean allowDelete() {
return true;
}
}

View file

@ -4,12 +4,9 @@
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
@ -71,11 +68,6 @@ private boolean filter(String name) {
return exclusions.stream().anyMatch(name::startsWith);
}
@Override
public boolean allowDelete() {
return true;
}
public List<Path> getJars() {
return jars;
}

View file

@ -81,9 +81,4 @@ public Path process(Path inputFile) throws IOException {
}
return inputFile;
}
@Override
public boolean allowDelete() {
return false;
}
}

View file

@ -43,9 +43,4 @@ public Path process(Path inputFile) throws IOException {
}
return output;
}
@Override
public boolean allowDelete() {
return true;
}
}

View file

@ -7,6 +7,4 @@ public interface LauncherBuildTask {
String getName();
Path process(Path inputFile) throws IOException;
boolean allowDelete();
}

View file

@ -8,8 +8,8 @@
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherConfig;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.LauncherConfig;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.asm.ClassMetadataReader;
import pro.gravit.launchserver.asm.InjectClassAcceptor;
@ -51,11 +51,12 @@ public String getName() {
@Override
public Path process(Path inputJar) throws IOException {
Path outputJar = server.launcherBinary.nextPath("main");
Path outputJar = server.launcherBinary.nextPath(this);
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(outputJar))) {
BuildContext context = new BuildContext(output, reader.getCp(), this);
BuildContext context = new BuildContext(output, reader.getCp(), this, server.launcherBinary.runtimeDir);
initProps();
preBuildHook.hook(context);
properties.put("launcher.legacymodules", context.legacyClientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
properties.put("launcher.modules", context.clientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
postInitProps();
reader.getCp().add(new JarFile(inputJar.toFile()));
@ -68,11 +69,13 @@ public Path process(Path inputJar) throws IOException {
Map<String, byte[]> runtime = new HashMap<>(256);
// Write launcher guard dir
if (server.config.launcher.encryptRuntime) {
context.pushEncryptedDir(server.launcherBinary.runtimeDir, Launcher.RUNTIME_DIR, server.runtime.runtimeEncryptKey, runtime, false);
context.pushEncryptedDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, server.runtime.runtimeEncryptKey, runtime, false);
} else {
context.pushDir(server.launcherBinary.runtimeDir, Launcher.RUNTIME_DIR, runtime, false);
context.pushDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, runtime, false);
}
if(context.isDeleteRuntimeDir()) {
IOHelper.deleteDir(context.getRuntimeDir(), true);
}
context.pushDir(server.launcherBinary.guardDir, Launcher.GUARD_DIR, runtime, false);
LauncherConfig launcherConfig = new LauncherConfig(server.config.netty.address, server.keyAgreementManager.ecdsaPublicKey, server.keyAgreementManager.rsaPublicKey, runtime, server.config.projectName);
context.pushFile(Launcher.CONFIG_FILE, launcherConfig);
@ -108,7 +111,6 @@ protected void initProps() {
properties.put("launcher.projectName", server.config.projectName);
properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey());
properties.put("launcher.port", 32148 + SecurityHelper.newRandom().nextInt(512));
properties.put("launcher.guardType", server.config.launcher.guardType);
properties.put("launchercore.env", server.config.env);
properties.put("launcher.memory", server.config.launcher.memoryLimit);
properties.put("launcher.customJvmOptions", server.config.launcher.customJvmOptions);
@ -126,7 +128,8 @@ protected void initProps() {
properties.put("runtimeconfig.secureCheckSalt", launcherSalt);
if (server.runtime.unlockSecret == null) server.runtime.unlockSecret = SecurityHelper.randomStringToken();
properties.put("runtimeconfig.unlockSecret", server.runtime.unlockSecret);
server.runtime.buildNumber++;
properties.put("runtimeconfig.buildNumber", server.runtime.buildNumber);
}
public byte[] transformClass(byte[] bytes, String classname, BuildContext context) {
@ -143,7 +146,7 @@ public byte[] transformClass(byte[] bytes, String classname, BuildContext contex
asmTransformer.transform(cn, classname, context);
continue;
} else if (cn != null) {
writer = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
writer = new SafeClassWriter(reader, 0);
cn.accept(writer);
result = writer.toByteArray();
}
@ -154,18 +157,13 @@ public byte[] transformClass(byte[] bytes, String classname, BuildContext contex
}
}
if (cn != null) {
writer = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
writer = new SafeClassWriter(reader, 0);
cn.accept(writer);
result = writer.toByteArray();
}
return result;
}
@Override
public boolean allowDelete() {
return true;
}
@FunctionalInterface
public interface Transformer {
byte[] transform(byte[] input, String classname, BuildContext context);
@ -177,7 +175,7 @@ default byte[] transform(byte[] input, String classname, BuildContext context) {
ClassNode cn = new ClassNode();
reader.accept(cn, 0);
transform(cn, classname, context);
SafeClassWriter writer = new SafeClassWriter(context.task.reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
SafeClassWriter writer = new SafeClassWriter(context.task.reader, 0);
cn.accept(writer);
return writer.toByteArray();
}

View file

@ -7,10 +7,7 @@
import pro.gravit.utils.helper.UnpackHelper;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.stream.Collectors;
@ -37,15 +34,17 @@ public Path process(Path inputFile) throws IOException {
server.launcherBinary.addonLibs.clear();
server.launcherBinary.files.clear();
IOHelper.walk(server.launcherLibraries, new ListFileVisitor(server.launcherBinary.coreLibs), false);
if(Files.isDirectory(server.launcherLibrariesCompile)) {
IOHelper.walk(server.launcherLibrariesCompile, new ListFileVisitor(server.launcherBinary.addonLibs), false);
try(Stream<Path> stream = Files.walk(server.launcherPack).filter((e) -> {
}
try(Stream<Path> stream = Files.walk(server.launcherPack, FileVisitOption.FOLLOW_LINKS).filter((e) -> {
try {
return !Files.isDirectory(e) && !Files.isHidden(e);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
})) {
var map = stream.collect(Collectors.toMap(k -> server.launcherPack.relativize(k).toString(), (v) -> v));
var map = stream.collect(Collectors.toMap(k -> server.launcherPack.relativize(k).toString().replace("\\", "/"), (v) -> v));
server.launcherBinary.files.putAll(map);
}
UnpackHelper.unpack(IOHelper.getResourceURL("Launcher.jar"), result);
@ -53,14 +52,8 @@ public Path process(Path inputFile) throws IOException {
return result;
}
@Override
public boolean allowDelete() {
return false;
}
public void tryUnpack() throws IOException {
logger.info("Unpacking launcher native guard list and runtime");
UnpackHelper.unpackZipNoCheck("guard.zip", server.launcherBinary.guardDir);
UnpackHelper.unpackZipNoCheck("runtime.zip", server.launcherBinary.runtimeDir);
}

View file

@ -26,7 +26,7 @@
public class SignJarTask implements LauncherBuildTask {
private transient static final Logger logger = LogManager.getLogger();
private static final Logger logger = LogManager.getLogger();
private final LaunchServerConfig.JarSignerConf config;
private final LaunchServer srv;
@ -104,9 +104,4 @@ private void autoSign(Path inputFile, Path signedFile) throws IOException {
}
}
}
@Override
public boolean allowDelete() {
return true;
}
}

View file

@ -1,131 +0,0 @@
package pro.gravit.launchserver.binary.tasks.exe;
import net.sf.launch4j.Builder;
import net.sf.launch4j.Log;
import net.sf.launch4j.config.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.Version;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.Path;
public class Launch4JTask implements LauncherBuildTask, BuildExeMainTask {
public static final String DOWNLOAD_URL = "https://bell-sw.com/pages/downloads/?version=java-8-lts&os=Windows&package=jre-full"; // BellSoft
private static final String VERSION = Version.getVersion().getVersionString();
private static final int BUILD = Version.getVersion().build;
private final Path faviconFile;
private final LaunchServer server;
private transient final Logger logger = LogManager.getLogger();
public Launch4JTask(LaunchServer launchServer) {
this.server = launchServer;
faviconFile = launchServer.dir.resolve("favicon.ico");
}
public static String formatVars(String mask) {
return String.format(mask, VERSION, BUILD);
}
@Override
public String getName() {
return "launch4j";
}
@Override
public Path process(Path inputFile) throws IOException {
logger.info("Building launcher EXE binary file (Using Launch4J)");
Path output = setConfig();
// Set favicon path
Config config = ConfigPersister.getInstance().getConfig();
if (IOHelper.isFile(faviconFile))
config.setIcon(faviconFile.toFile());
else {
config.setIcon(null);
logger.warn("Missing favicon.ico file");
}
// Start building
Builder builder = new Builder(Launch4JLog.INSTANCE);
try {
builder.build();
} catch (Throwable e) {
throw new IOException(e);
}
return output;
}
@Override
public boolean allowDelete() {
return true;
}
private Path setConfig() {
Path path = server.launcherEXEBinary.nextPath(getName());
Config config = new Config();
// Set file options
config.setChdir(".");
config.setErrTitle("JVM Error");
config.setDownloadUrl(server.config.launch4j.downloadUrl);
if (server.config.launch4j.supportURL != null) config.setSupportUrl(server.config.launch4j.supportURL);
// Set boolean options
config.setPriorityIndex(0);
config.setHeaderType(Config.GUI_HEADER);
config.setStayAlive(false);
config.setRestartOnCrash(false);
// Prepare JRE
Jre jre = new Jre();
jre.setMinVersion(server.config.launch4j.minVersion);
if (server.config.launch4j.setMaxVersion)
jre.setMaxVersion(server.config.launch4j.maxVersion);
jre.setRuntimeBits(Jre.RUNTIME_BITS_64_AND_32);
jre.setJdkPreference(Jre.JDK_PREFERENCE_PREFER_JRE);
config.setJre(jre);
// Prepare version info (product)
VersionInfo info = new VersionInfo();
info.setProductName(server.config.launch4j.productName);
info.setProductVersion(formatVars(server.config.launch4j.productVer));
info.setFileDescription(server.config.launch4j.fileDesc);
info.setFileVersion(formatVars(server.config.launch4j.fileVer));
info.setCopyright(server.config.launch4j.copyright);
info.setTrademarks(server.config.launch4j.trademarks);
info.setInternalName(formatVars(server.config.launch4j.internalName));
// Prepare version info (file)
info.setTxtFileVersion(formatVars(server.config.launch4j.txtFileVersion));
info.setTxtProductVersion(formatVars(server.config.launch4j.txtProductVersion));
// Prepare version info (misc)
info.setOriginalFilename(path.getFileName().toString());
info.setLanguage(LanguageID.RUSSIAN);
config.setVersionInfo(info);
// Set JAR wrapping options
config.setDontWrapJar(false);
config.setJar(server.launcherBinary.syncBinaryFile.toFile());
config.setOutfile(path.toFile());
// Return prepared config
ConfigPersister.getInstance().setAntConfig(config, null);
return path;
}
private final static class Launch4JLog extends Log {
private static final Launch4JLog INSTANCE = new Launch4JLog();
private static final Logger logger = LogManager.getLogger();
@Override
public void append(String s) {
logger.info(s);
}
@Override
public void clear() {
// Do nothing
}
}
}

View file

@ -3,9 +3,11 @@
import me.tongfei.progressbar.ProgressBar;
import me.tongfei.progressbar.ProgressBarBuilder;
import me.tongfei.progressbar.ProgressBarStyle;
import pro.gravit.launcher.AsyncDownloader;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.Downloader;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.Downloader;
import pro.gravit.utils.command.CommandException;
import java.io.IOException;
import java.nio.file.Path;
@ -29,15 +31,25 @@ public Command(Map<String, pro.gravit.utils.command.Command> childCommands, Laun
this.server = server;
}
protected ClientProfile.Version parseClientVersion(String arg) throws CommandException {
if(arg.isEmpty()) {
throw new CommandException("ClientVersion can't be empty");
}
return Launcher.gsonManager.gson.fromJson(arg, ClientProfile.Version.class);
}
protected boolean showApplyDialog(String text) throws IOException {
System.out.printf("%s [Y/N]:", text);
String response = server.commandHandler.readLine().toLowerCase(Locale.ROOT);
return response.equals("y");
}
protected Downloader downloadWithProgressBar(String taskName, List<AsyncDownloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception {
protected Downloader downloadWithProgressBar(String taskName, List<Downloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception {
long total = 0;
for (AsyncDownloader.SizedFile file : list) {
for (Downloader.SizedFile file : list) {
if(file.size < 0) {
continue;
}
total += file.size;
}
long totalFiles = list.size();
@ -49,7 +61,7 @@ protected Downloader downloadWithProgressBar(String taskName, List<AsyncDownload
.setStyle(ProgressBarStyle.COLORFUL_UNICODE_BLOCK)
.setUnit("MB", 1024 * 1024)
.build();
bar.setExtraMessage(String.format(" [0/%d]", totalFiles));
bar.setExtraMessage(" [0/%d]".formatted(totalFiles));
Downloader downloader = Downloader.downloadList(list, baseUrl, targetDir, new Downloader.DownloadCallback() {
@Override
public void apply(long fullDiff) {
@ -59,7 +71,7 @@ public void apply(long fullDiff) {
@Override
public void onComplete(Path path) {
bar.setExtraMessage(String.format(" [%d/%d]", currentFiles.incrementAndGet(), totalFiles));
bar.setExtraMessage(" [%d/%d]".formatted(currentFiles.incrementAndGet(), totalFiles));
}
}, null, 4);
downloader.getFuture().handle((v, e) -> {

View file

@ -10,7 +10,7 @@
import pro.gravit.launchserver.command.Command;
public class DebugCommand extends Command {
private transient Logger logger = LogManager.getLogger();
private final transient Logger logger = LogManager.getLogger();
public DebugCommand(LaunchServer server) {
super(server);
@ -32,6 +32,7 @@ public void invoke(String... args) throws Exception {
boolean value = Boolean.parseBoolean(args[0]);
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
config.getWatchManager().setIntervalSeconds(-1);
LoggerConfig loggerConfig = config.getLoggerConfig("pro.gravit");
loggerConfig.setLevel(value ? Level.TRACE : Level.DEBUG);
ctx.updateLoggers();

View file

@ -1,25 +0,0 @@
package pro.gravit.launchserver.command.basic;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
public final class RestartCommand extends Command {
public RestartCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return "Restart LaunchServer";
}
@Override
public void invoke(String... args) {
server.fullyRestart();
}
}

View file

@ -5,14 +5,17 @@
import pro.gravit.launchserver.command.hash.*;
import pro.gravit.launchserver.command.modules.LoadModuleCommand;
import pro.gravit.launchserver.command.modules.ModulesCommand;
import pro.gravit.launchserver.command.profiles.ProfilesCommand;
import pro.gravit.launchserver.command.service.*;
import pro.gravit.launchserver.command.sync.*;
import pro.gravit.launchserver.command.tools.SignDirCommand;
import pro.gravit.launchserver.command.tools.SignJarCommand;
import pro.gravit.utils.command.BaseCommandCategory;
import pro.gravit.utils.command.basic.ClearCommand;
import pro.gravit.utils.command.basic.GCCommand;
import pro.gravit.utils.command.basic.HelpCommand;
public abstract class CommandHandler extends pro.gravit.utils.command.CommandHandler {
@SuppressWarnings("deprecation")
public static void registerCommands(pro.gravit.utils.command.CommandHandler handler, LaunchServer server) {
BaseCommandCategory basic = new BaseCommandCategory();
// Register basic commands
@ -20,7 +23,6 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
basic.registerCommand("version", new VersionCommand(server));
basic.registerCommand("build", new BuildCommand(server));
basic.registerCommand("stop", new StopCommand(server));
basic.registerCommand("restart", new RestartCommand(server));
basic.registerCommand("debug", new DebugCommand(server));
basic.registerCommand("clear", new ClearCommand(handler));
basic.registerCommand("gc", new GCCommand());
@ -35,12 +37,8 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
updates.registerCommand("unindexAsset", new UnindexAssetCommand(server));
updates.registerCommand("downloadAsset", new DownloadAssetCommand(server));
updates.registerCommand("downloadClient", new DownloadClientCommand(server));
updates.registerCommand("syncBinaries", new SyncBinariesCommand(server));
updates.registerCommand("syncUpdates", new SyncUpdatesCommand(server));
updates.registerCommand("syncProfiles", new SyncProfilesCommand(server));
updates.registerCommand("syncUP", new SyncUPCommand(server));
updates.registerCommand("saveProfiles", new SaveProfilesCommand(server));
updates.registerCommand("makeProfile", new MakeProfileCommand(server));
updates.registerCommand("sync", new SyncCommand(server));
updates.registerCommand("profile", new ProfilesCommand(server));
Category updatesCategory = new Category(updates, "updates", "Update and Sync Management");
handler.registerCategory(updatesCategory);
@ -51,11 +49,16 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
service.registerCommand("notify", new NotifyCommand(server));
service.registerCommand("component", new ComponentCommand(server));
service.registerCommand("clients", new ClientsCommand(server));
service.registerCommand("signJar", new SignJarCommand(server));
service.registerCommand("signDir", new SignDirCommand(server));
service.registerCommand("securitycheck", new SecurityCheckCommand(server));
service.registerCommand("token", new TokenCommand(server));
Category serviceCategory = new Category(service, "service", "Managing LaunchServer Components");
handler.registerCategory(serviceCategory);
//Register tools commands
BaseCommandCategory tools = new BaseCommandCategory();
tools.registerCommand("signJar", new SignJarCommand(server));
tools.registerCommand("signDir", new SignDirCommand(server));
Category toolsCategory = new Category(tools, "tools", "Other tools");
handler.registerCategory(toolsCategory);
}
}

View file

@ -3,12 +3,11 @@
import com.google.gson.JsonObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.AsyncDownloader;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.Downloader;
import pro.gravit.launchserver.HttpRequester;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.Downloader;
import pro.gravit.utils.helper.IOHelper;
import java.io.Writer;
@ -40,11 +39,11 @@ public String getUsageDescription() {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 2);
verifyArgs(args, 1);
//Version version = Version.byName(args[0]);
String versionName = args[0];
String dirName = IOHelper.verifyFileName(args.length > 1 ? args[1] : "assets");
String type = args.length > 2 ? args[2] : "mojang";
String dirName = IOHelper.verifyFileName(args[1]);
Path assetDir = server.updatesDir.resolve(dirName);
// Create asset dir
@ -85,7 +84,7 @@ public void invoke(String... args) throws Exception {
logger.info("Copy {} into {}", indexPath, targetPath);
Files.copy(indexPath, targetPath, StandardCopyOption.REPLACE_EXISTING);
}
List<AsyncDownloader.SizedFile> toDownload = new ArrayList<>(128);
List<Downloader.SizedFile> toDownload = new ArrayList<>(128);
for (var e : objects.entrySet()) {
var value = e.getValue().getAsJsonObject();
var hash = value.get("hash").getAsString();
@ -101,7 +100,7 @@ public void invoke(String... args) throws Exception {
continue;
}
}
toDownload.add(new AsyncDownloader.SizedFile(hash, path, size));
toDownload.add(new Downloader.SizedFile(hash, path, size));
}
logger.info("Download {} files", toDownload.size());
Downloader downloader = downloadWithProgressBar(dirName, toDownload, RESOURCES_DOWNLOAD_URL, assetDir);

View file

@ -3,17 +3,17 @@
import com.google.gson.JsonElement;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launcher.base.profiles.ClientProfileVersions;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.launchserver.helper.MakeProfileHelper;
import pro.gravit.utils.command.CommandException;
import pro.gravit.utils.helper.IOHelper;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.UUID;
@ -41,18 +41,14 @@ public void invoke(String... args) throws IOException, CommandException {
verifyArgs(args, 2);
//Version version = Version.byName(args[0]);
String versionName = args[0];
String dirName = IOHelper.verifyFileName(args[1]);
Path clientDir = server.updatesDir.resolve(args[1]);
String dirName = IOHelper.verifyFileName(args[1] != null ? args[1] : args[0]);
Path clientDir = server.updatesDir.resolve(dirName);
boolean isMirrorClientDownload = false;
if (args.length > 2) {
isMirrorClientDownload = args[2].equals("mirror");
}
// Create client dir
logger.info("Creating client dir: '{}'", dirName);
Files.createDirectory(clientDir);
// Download required client
logger.info("Downloading client, it may take some time");
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir);
@ -60,43 +56,47 @@ public void invoke(String... args) throws IOException, CommandException {
// Create profile file
logger.info("Creaing profile file: '{}'", dirName);
ClientProfile client = null;
ClientProfile clientProfile = null;
if (isMirrorClientDownload) {
try {
JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName);
clientProfile = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class);
var builder = new ClientProfileBuilder(clientProfile);
builder.setTitle(dirName);
builder.setDir(dirName);
builder.setUuid(UUID.randomUUID());
clientProfile = builder.createClientProfile();
if (clientProfile.getServers() != null) {
ClientProfile.ServerProfile serverProfile = clientProfile.getDefaultServerProfile();
if (serverProfile != null) {
serverProfile.name = dirName;
}
}
} catch (Exception e) {
logger.error("Filed download clientProfile from mirror: '{}' Generation through MakeProfileHelper", versionName);
isMirrorClientDownload = false;
}
}
if (!isMirrorClientDownload) {
try {
String internalVersion = versionName;
if (internalVersion.contains("-")) {
internalVersion = internalVersion.substring(0, versionName.indexOf('-'));
}
ClientProfile.Version version = ClientProfile.Version.byName(internalVersion);
if (version.compareTo(ClientProfile.Version.MC164) <= 0) {
logger.warn("Minecraft 1.6.4 and below not supported. Use at your own risk");
ClientProfile.Version version = ClientProfile.Version.of(internalVersion);
if (version.compareTo(ClientProfileVersions.MINECRAFT_1_7_10) <= 0) {
logger.warn("Minecraft 1.7.9 and below not supported. Use at your own risk");
}
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version, Files.exists(server.updatesDir.resolve("assets")));
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version);
for (MakeProfileHelper.MakeProfileOption option : options) {
logger.debug("Detected option {}", option.getClass().getSimpleName());
}
client = MakeProfileHelper.makeProfile(version, dirName, options);
clientProfile = MakeProfileHelper.makeProfile(version, dirName, options);
} catch (Throwable e) {
isMirrorClientDownload = true;
}
}
if (isMirrorClientDownload) {
JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName);
client = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class);
client.setTitle(dirName);
client.setDir(dirName);
client.setUUID(UUID.randomUUID());
if (client.getServers() != null) {
ClientProfile.ServerProfile serverProfile = client.getDefaultServerProfile();
if (serverProfile != null) {
serverProfile.name = dirName;
}
}
}
try (BufferedWriter writer = IOHelper.newWriter(IOHelper.resolveIncremental(server.profilesDir,
dirName, "json"))) {
Launcher.gsonManager.configGson.toJson(client, writer);
}
server.config.profileProvider.addProfile(clientProfile);
// Finished
server.syncProfilesDir();

View file

@ -3,7 +3,7 @@
import com.google.gson.JsonObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.command.CommandException;

View file

@ -1,71 +0,0 @@
package pro.gravit.launchserver.command.hash;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
public class SaveProfilesCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public SaveProfilesCommand(LaunchServer server) {
super(server);
}
public static void saveProfile(ClientProfile profile, Path path) throws IOException {
if (profile.getUUID() == null) profile.setUUID(UUID.randomUUID());
if (profile.getServers().size() == 0) {
ClientProfile.ServerProfile serverProfile = new ClientProfile.ServerProfile();
serverProfile.isDefault = true;
serverProfile.name = profile.getTitle();
serverProfile.serverAddress = profile.getServerAddress();
serverProfile.serverPort = profile.getServerPort();
profile.getServers().add(serverProfile);
}
try (Writer w = IOHelper.newWriter(path)) {
Launcher.gsonManager.configGson.toJson(profile, w);
}
}
@Override
public String getArgsDescription() {
return "[profile names...]";
}
@Override
public String getUsageDescription() {
return "load and save profile";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
if (args.length > 0) {
for (String profileName : args) {
Path profilePath = server.profilesDir.resolve(profileName.concat(".json"));
if (!Files.exists(profilePath)) {
logger.error("Profile {} not found", profilePath.toString());
return;
}
ClientProfile profile;
try (Reader reader = IOHelper.newReader(profilePath)) {
profile = Launcher.gsonManager.configGson.fromJson(reader, ClientProfile.class);
}
saveProfile(profile, profilePath);
logger.info("Profile {} save successful", profilePath.toString());
}
server.syncProfilesDir();
}
}
}

View file

@ -2,9 +2,9 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.LauncherTrustManager;
import pro.gravit.launcher.modules.LauncherModule;
import pro.gravit.launcher.modules.LauncherModuleInfo;
import pro.gravit.launcher.core.LauncherTrustManager;
import pro.gravit.launcher.base.modules.LauncherModule;
import pro.gravit.launcher.base.modules.LauncherModuleInfo;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.launchserver.launchermodules.LauncherModuleLoader;

View file

@ -0,0 +1,69 @@
package pro.gravit.launchserver.command.profiles;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
import java.util.stream.Stream;
public class CloneProfileCommand extends Command {
private final transient Logger logger = LogManager.getLogger(CloneProfileCommand.class);
public CloneProfileCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[profile title/uuid] [new profile title]";
}
@Override
public String getUsageDescription() {
return "clone profile and profile dir";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 2);
ClientProfile profile;
try {
UUID uuid = UUID.fromString(args[0]);
profile = server.config.profileProvider.getProfile(uuid);
} catch (IllegalArgumentException ex) {
profile = server.config.profileProvider.getProfile(args[0]);
}
var builder = new ClientProfileBuilder(profile);
builder.setTitle(args[1]);
builder.setUuid(UUID.randomUUID());
if(profile.getServers().size() == 1) {
profile.getServers().getFirst().name = args[1];
}
logger.info("Copy {} to {}", profile.getDir(), args[1]);
var src = server.updatesDir.resolve(profile.getDir());
var dest = server.updatesDir.resolve(args[1]);
try (Stream<Path> stream = Files.walk(src)) {
stream.forEach(source -> {
try {
IOHelper.copy(source, dest.resolve(src.relativize(source)));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
builder.setDir(args[1]);
profile = builder.createClientProfile();
server.config.profileProvider.addProfile(profile);
logger.info("Profile {} cloned from {}", args[1], args[0]);
server.syncProfilesDir();
server.syncUpdatesDir(List.of(args[1]));
}
}

View file

@ -0,0 +1,50 @@
package pro.gravit.launchserver.command.profiles;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import java.util.UUID;
public class DeleteProfileCommand extends Command {
private final transient Logger logger = LogManager.getLogger(ListProfilesCommand.class);
public DeleteProfileCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[uuid/title]";
}
@Override
public String getUsageDescription() {
return "permanently delete profile";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
ClientProfile profile;
try {
UUID uuid = UUID.fromString(args[0]);
profile = server.config.profileProvider.getProfile(uuid);
} catch (IllegalArgumentException ex) {
profile = server.config.profileProvider.getProfile(args[0]);
}
if(profile == null) {
logger.error("Profile {} not found", args[0]);
return;
}
logger.warn("THIS ACTION DELETE PROFILE AND ALL FILES IN {}", profile.getDir());
if(!showApplyDialog("Continue?")) {
return;
}
logger.info("Delete {} ({})", profile.getTitle(), profile.getUUID());
server.config.profileProvider.deleteProfile(profile);
logger.info("Delete {}", profile.getDir());
server.config.updatesProvider.delete(profile.getDir());
}
}

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