2020-03-22 03:44:18 +03:00
package pro.gravit.launcher.client ;
2021-06-05 02:30:56 +03:00
import pro.gravit.launcher.* ;
2020-03-22 03:44:18 +03:00
import pro.gravit.launcher.api.AuthService ;
import pro.gravit.launcher.api.ClientService ;
2020-04-03 11:12:31 +03:00
import pro.gravit.launcher.client.events.client.* ;
2020-03-22 03:44:18 +03:00
import pro.gravit.launcher.guard.LauncherGuardManager ;
import pro.gravit.launcher.hasher.FileNameMatcher ;
import pro.gravit.launcher.hasher.HashedDir ;
2021-01-28 20:13:21 +03:00
import pro.gravit.launcher.hasher.HashedEntry ;
2020-03-22 03:44:18 +03:00
import pro.gravit.launcher.managers.ClientGsonManager ;
2021-04-29 19:56:48 +03:00
import pro.gravit.launcher.managers.ConsoleManager ;
2020-03-22 03:44:18 +03:00
import pro.gravit.launcher.modules.events.PreConfigPhase ;
import pro.gravit.launcher.patches.FMLPatcher ;
import pro.gravit.launcher.profiles.ClientProfile ;
2020-09-12 09:41:58 +03:00
import pro.gravit.launcher.profiles.optional.actions.OptionalAction ;
import pro.gravit.launcher.profiles.optional.actions.OptionalActionClassPath ;
2020-09-26 20:52:43 +03:00
import pro.gravit.launcher.profiles.optional.actions.OptionalActionClientArgs ;
2021-06-18 07:34:32 +03:00
import pro.gravit.launcher.profiles.optional.triggers.OptionalTrigger ;
2020-03-22 03:44:18 +03:00
import pro.gravit.launcher.request.Request ;
import pro.gravit.launcher.request.RequestException ;
2020-09-12 09:41:58 +03:00
import pro.gravit.launcher.request.auth.AuthRequest ;
2021-04-13 15:30:06 +03:00
import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest ;
2020-03-22 03:44:18 +03:00
import pro.gravit.launcher.serialize.HInput ;
import pro.gravit.launcher.utils.DirWatcher ;
import pro.gravit.utils.helper.* ;
import javax.swing.* ;
2021-01-28 20:13:21 +03:00
import java.io.File ;
2020-03-22 03:44:18 +03:00
import java.io.IOException ;
import java.lang.invoke.MethodHandle ;
import java.lang.invoke.MethodHandles ;
import java.lang.invoke.MethodType ;
2021-01-28 20:13:21 +03:00
import java.net.* ;
2020-03-22 03:44:18 +03:00
import java.nio.file.FileVisitResult ;
import java.nio.file.Path ;
import java.nio.file.Paths ;
import java.nio.file.SimpleFileVisitor ;
import java.nio.file.attribute.BasicFileAttributes ;
import java.util.* ;
import java.util.stream.Collectors ;
import java.util.stream.Stream ;
public class ClientLauncherEntryPoint {
2021-01-28 20:13:21 +03:00
private static ClassLoader classLoader ;
2020-04-05 10:27:04 +03:00
2020-03-22 04:30:29 +03:00
private static ClientLauncherProcess . ClientParams readParams ( SocketAddress address ) throws IOException {
2020-04-05 10:27:04 +03:00
try ( Socket socket = IOHelper . newSocket ( ) ) {
2020-03-22 03:44:18 +03:00
socket . connect ( address ) ;
2020-04-05 10:27:04 +03:00
try ( HInput input = new HInput ( socket . getInputStream ( ) ) ) {
2020-03-22 03:44:18 +03:00
byte [ ] serialized = input . readByteArray ( 0 ) ;
ClientLauncherProcess . ClientParams params = Launcher . gsonManager . gson . fromJson ( new String ( serialized , IOHelper . UNICODE_CHARSET ) , ClientLauncherProcess . ClientParams . class ) ;
params . clientHDir = new HashedDir ( input ) ;
params . assetHDir = new HashedDir ( input ) ;
2020-08-02 01:16:18 +03:00
boolean isNeedReadJavaDir = input . readBoolean ( ) ;
2020-09-25 18:48:33 +03:00
if ( isNeedReadJavaDir )
2020-08-02 01:16:18 +03:00
params . javaHDir = new HashedDir ( input ) ;
2020-03-22 03:44:18 +03:00
return params ;
}
}
}
2020-04-05 10:27:04 +03:00
2020-03-22 04:30:29 +03:00
public static void main ( String [ ] args ) throws Throwable {
2020-03-22 03:44:18 +03:00
LauncherEngine . IS_CLIENT . set ( true ) ;
LauncherEngine engine = LauncherEngine . clientInstance ( ) ;
2020-04-03 11:12:31 +03:00
JVMHelper . verifySystemProperties ( ClientLauncherEntryPoint . class , true ) ;
EnvHelper . checkDangerousParams ( ) ;
JVMHelper . checkStackTrace ( ClientLauncherEntryPoint . class ) ;
LogHelper . printVersion ( " Client Launcher " ) ;
2020-03-22 03:44:18 +03:00
LauncherEngine . checkClass ( LauncherEngine . class ) ;
LauncherEngine . checkClass ( LauncherAgent . class ) ;
2020-03-22 04:40:14 +03:00
LauncherEngine . checkClass ( ClientLauncherEntryPoint . class ) ;
2020-03-22 03:44:18 +03:00
LauncherEngine . modulesManager = new ClientModuleManager ( ) ;
2020-05-01 19:56:08 +03:00
LauncherEngine . modulesManager . loadModule ( new ClientLauncherCoreModule ( ) ) ;
2020-03-22 03:44:18 +03:00
LauncherConfig . initModules ( LauncherEngine . modulesManager ) ; //INIT
LauncherEngine . modulesManager . initModules ( null ) ;
initGson ( LauncherEngine . modulesManager ) ;
2021-04-29 19:56:48 +03:00
ConsoleManager . initConsole ( ) ;
2020-03-22 03:44:18 +03:00
LauncherEngine . modulesManager . invokeEvent ( new PreConfigPhase ( ) ) ;
engine . readKeys ( ) ;
LauncherGuardManager . initGuard ( true ) ;
LogHelper . debug ( " Reading ClientLauncher params " ) ;
ClientLauncherProcess . ClientParams params = readParams ( new InetSocketAddress ( " 127.0.0.1 " , Launcher . getConfig ( ) . clientPort ) ) ;
2021-05-07 15:25:04 +03:00
if ( params . profile . getClassLoaderConfig ( ) ! = ClientProfile . ClassLoaderConfig . AGENT ) {
2021-01-28 20:13:21 +03:00
LauncherEngine . verifyNoAgent ( ) ;
}
2020-03-22 03:44:18 +03:00
ClientProfile profile = params . profile ;
Launcher . profile = profile ;
AuthService . profile = profile ;
LauncherEngine . clientParams = params ;
2021-05-25 12:17:29 +03:00
if ( params . oauth ! = null ) {
2021-05-23 17:11:27 +03:00
LogHelper . info ( " Using OAuth " ) ;
2021-05-25 12:17:29 +03:00
if ( params . oauthExpiredTime ! = 0 ) {
2021-05-23 14:13:30 +03:00
Request . setOAuth ( params . authId , params . oauth , params . oauthExpiredTime ) ;
} else {
Request . setOAuth ( params . authId , params . oauth ) ;
}
2021-05-25 12:17:29 +03:00
if ( params . extendedTokens ! = null ) {
2021-05-23 14:13:30 +03:00
Request . addAllExtendedToken ( params . extendedTokens ) ;
}
2021-05-25 12:17:29 +03:00
} else if ( params . session ! = null ) {
2021-05-23 17:11:27 +03:00
LogHelper . info ( " Using Sessions " ) ;
Request . setSession ( params . session ) ;
2021-05-23 14:13:30 +03:00
}
2021-01-28 20:13:21 +03:00
checkJVMBitsAndVersion ( params . profile . getMinJavaVersion ( ) , params . profile . getRecommendJavaVersion ( ) , params . profile . getMaxJavaVersion ( ) , params . profile . isWarnMissJavaVersion ( ) ) ;
2020-04-03 11:12:31 +03:00
LauncherEngine . modulesManager . invokeEvent ( new ClientProcessInitPhase ( engine , params ) ) ;
2020-03-22 03:44:18 +03:00
Path clientDir = Paths . get ( params . clientDir ) ;
Path assetDir = Paths . get ( params . assetDir ) ;
// Verify ClientLauncher sign and classpath
LogHelper . debug ( " Verifying ClientLauncher sign and classpath " ) ;
List < URL > classpath = new LinkedList < > ( ) ;
resolveClassPathStream ( clientDir , params . profile . getClassPath ( ) ) . map ( IOHelper : : toURL ) . collect ( Collectors . toCollection ( ( ) - > classpath ) ) ;
2020-09-25 18:48:33 +03:00
for ( OptionalAction a : params . actions ) {
if ( a instanceof OptionalActionClassPath )
resolveClassPathStream ( clientDir , ( ( OptionalActionClassPath ) a ) . args ) . map ( IOHelper : : toURL ) . collect ( Collectors . toCollection ( ( ) - > classpath ) ) ;
2020-09-12 09:41:58 +03:00
}
2020-03-22 03:44:18 +03:00
// Start client with WatchService monitoring
boolean digest = ! profile . isUpdateFastCheck ( ) ;
LogHelper . debug ( " Restore sessions " ) ;
2021-05-23 14:13:30 +03:00
Request . restore ( ) ;
2021-06-09 06:01:07 +03:00
Request . service . registerEventHandler ( new BasicLauncherEventHandler ( ) ) ;
2020-03-22 03:44:18 +03:00
Request . service . reconnectCallback = ( ) - >
{
LogHelper . debug ( " WebSocket connect closed. Try reconnect " ) ;
try {
2021-05-23 14:13:30 +03:00
Request . reconnect ( ) ;
2020-03-22 03:44:18 +03:00
} catch ( Exception e ) {
LogHelper . error ( e ) ;
2021-05-23 14:13:30 +03:00
throw new RequestException ( " Connection failed " , e ) ;
2020-03-22 03:44:18 +03:00
}
} ;
2021-05-07 15:25:04 +03:00
if ( params . profile . getClassLoaderConfig ( ) = = ClientProfile . ClassLoaderConfig . LAUNCHER ) {
2021-01-28 20:13:21 +03:00
ClientClassLoader classLoader = new ClientClassLoader ( classpath . toArray ( new URL [ 0 ] ) , ClassLoader . getSystemClassLoader ( ) ) ;
ClientLauncherEntryPoint . classLoader = classLoader ;
Thread . currentThread ( ) . setContextClassLoader ( classLoader ) ;
classLoader . nativePath = clientDir . resolve ( " natives " ) . toString ( ) ;
LauncherEngine . modulesManager . invokeEvent ( new ClientProcessClassLoaderEvent ( engine , classLoader , profile ) ) ;
AuthService . username = params . playerProfile . username ;
AuthService . uuid = params . playerProfile . uuid ;
ClientService . classLoader = classLoader ;
ClientService . nativePath = classLoader . nativePath ;
classLoader . addURL ( IOHelper . getCodeSource ( ClientLauncherEntryPoint . class ) . toUri ( ) . toURL ( ) ) ;
ClientService . baseURLs = classLoader . getURLs ( ) ;
2021-05-07 15:25:04 +03:00
} else if ( params . profile . getClassLoaderConfig ( ) = = ClientProfile . ClassLoaderConfig . AGENT ) {
2021-01-28 20:13:21 +03:00
ClientLauncherEntryPoint . classLoader = ClassLoader . getSystemClassLoader ( ) ;
classpath . add ( IOHelper . getCodeSource ( ClientLauncherEntryPoint . class ) . toUri ( ) . toURL ( ) ) ;
2021-03-20 11:53:22 +03:00
for ( URL url : classpath ) {
2021-01-28 20:13:21 +03:00
LauncherAgent . addJVMClassPath ( Paths . get ( url . toURI ( ) ) ) ;
}
ClientService . instrumentation = LauncherAgent . inst ;
ClientService . nativePath = clientDir . resolve ( " natives " ) . toString ( ) ;
LauncherEngine . modulesManager . invokeEvent ( new ClientProcessClassLoaderEvent ( engine , classLoader , profile ) ) ;
AuthService . username = params . playerProfile . username ;
AuthService . uuid = params . playerProfile . uuid ;
ClientService . classLoader = classLoader ;
ClientService . baseURLs = classpath . toArray ( new URL [ 0 ] ) ;
}
2021-05-25 12:17:29 +03:00
if ( params . profile . getRuntimeInClientConfig ( ) ! = ClientProfile . RuntimeInClientConfig . NONE ) {
2021-04-29 19:56:48 +03:00
CommonHelper . newThread ( " Client Launcher Thread " , true , ( ) - > {
try {
engine . start ( args ) ;
} catch ( Throwable throwable ) {
LogHelper . error ( throwable ) ;
}
} ) . start ( ) ;
}
2020-04-03 11:12:31 +03:00
LauncherEngine . modulesManager . invokeEvent ( new ClientProcessReadyEvent ( engine , params ) ) ;
2020-03-22 03:44:18 +03:00
LogHelper . debug ( " Starting JVM and client WatchService " ) ;
FileNameMatcher assetMatcher = profile . getAssetUpdateMatcher ( ) ;
FileNameMatcher clientMatcher = profile . getClientUpdateMatcher ( ) ;
2020-08-02 01:16:18 +03:00
Path javaDir = Paths . get ( System . getProperty ( " java.home " ) ) ;
2020-03-22 03:44:18 +03:00
try ( DirWatcher assetWatcher = new DirWatcher ( assetDir , params . assetHDir , assetMatcher , digest ) ;
2020-08-02 01:16:18 +03:00
DirWatcher clientWatcher = new DirWatcher ( clientDir , params . clientHDir , clientMatcher , digest ) ;
DirWatcher javaWatcher = params . javaHDir = = null ? null : new DirWatcher ( javaDir , params . javaHDir , null , digest ) ) {
2020-03-22 03:44:18 +03:00
// Verify current state of all dirs
//verifyHDir(IOHelper.JVM_DIR, jvmHDir.object, null, digest);
//for (OptionalFile s : Launcher.profile.getOptional()) {
// if (params.updateOptional.contains(s)) s.mark = true;
// else hdir.removeR(s.file);
//}
// Start WatchService, and only then client
CommonHelper . newThread ( " Asset Directory Watcher " , true , assetWatcher ) . start ( ) ;
CommonHelper . newThread ( " Client Directory Watcher " , true , clientWatcher ) . start ( ) ;
2020-09-25 18:48:33 +03:00
if ( javaWatcher ! = null )
2020-08-02 01:16:18 +03:00
CommonHelper . newThread ( " Java Directory Watcher " , true , clientWatcher ) . start ( ) ;
2020-03-22 03:44:18 +03:00
verifyHDir ( assetDir , params . assetHDir , assetMatcher , digest ) ;
verifyHDir ( clientDir , params . clientHDir , clientMatcher , digest ) ;
2020-09-25 18:48:33 +03:00
if ( javaWatcher ! = null )
2020-08-02 01:16:18 +03:00
verifyHDir ( javaDir , params . javaHDir , null , digest ) ;
2020-10-27 21:55:08 +03:00
LauncherEngine . modulesManager . invokeEvent ( new ClientProcessLaunchEvent ( engine , params ) ) ;
2020-03-22 03:44:18 +03:00
launch ( profile , params ) ;
}
}
2020-04-05 10:27:04 +03:00
2020-03-22 03:44:18 +03:00
private static void initGson ( ClientModuleManager moduleManager ) {
2020-09-12 09:41:58 +03:00
AuthRequest . registerProviders ( ) ;
2021-04-13 15:30:06 +03:00
GetAvailabilityAuthRequest . registerProviders ( ) ;
2020-09-12 09:41:58 +03:00
OptionalAction . registerProviders ( ) ;
2021-06-18 07:34:32 +03:00
OptionalTrigger . registerProviders ( ) ;
2020-03-22 03:44:18 +03:00
Launcher . gsonManager = new ClientGsonManager ( moduleManager ) ;
Launcher . gsonManager . initGson ( ) ;
}
2020-04-05 10:27:04 +03:00
2020-03-22 03:44:18 +03:00
public static void verifyHDir ( Path dir , HashedDir hdir , FileNameMatcher matcher , boolean digest ) throws IOException {
//if (matcher != null)
// matcher = matcher.verifyOnly();
// Hash directory and compare (ignore update-only matcher entries, it will break offline-mode)
HashedDir currentHDir = new HashedDir ( dir , matcher , true , digest ) ;
HashedDir . Diff diff = hdir . diff ( currentHDir , matcher ) ;
if ( ! diff . isSame ( ) ) {
2021-03-20 11:53:22 +03:00
if ( LogHelper . isDebugEnabled ( ) ) {
2021-01-28 20:13:21 +03:00
diff . extra . walk ( File . separator , ( e , k , v ) - > {
2021-03-20 11:53:22 +03:00
if ( v . getType ( ) . equals ( HashedEntry . Type . FILE ) ) {
LogHelper . error ( " Extra file %s " , e ) ;
} else LogHelper . error ( " Extra %s " , e ) ;
2021-01-28 20:13:21 +03:00
return HashedDir . WalkAction . CONTINUE ;
} ) ;
2021-03-20 11:53:22 +03:00
diff . mismatch . walk ( File . separator , ( e , k , v ) - > {
if ( v . getType ( ) . equals ( HashedEntry . Type . FILE ) ) {
LogHelper . error ( " Mismatch file %s " , e ) ;
} else LogHelper . error ( " Mismatch %s " , e ) ;
2021-01-28 20:13:21 +03:00
return HashedDir . WalkAction . CONTINUE ;
} ) ;
}
2020-03-22 03:44:18 +03:00
throw new SecurityException ( String . format ( " Forbidden modification: '%s' " , IOHelper . getFileName ( dir ) ) ) ;
}
}
2020-04-05 10:27:04 +03:00
2021-01-28 20:13:21 +03:00
public static boolean checkJVMBitsAndVersion ( int minVersion , int recommendVersion , int maxVersion , boolean showMessage ) {
boolean ok = true ;
2020-03-22 03:44:18 +03:00
if ( JVMHelper . JVM_BITS ! = JVMHelper . OS_BITS ) {
String error = String . format ( " У В а с установлена Java %d, но Ваша система определена как %d. Установите Java правильной разрядности" , JVMHelper . JVM_BITS , JVMHelper . OS_BITS ) ;
LogHelper . error ( error ) ;
2021-01-28 20:13:21 +03:00
if ( showMessage )
2020-03-22 03:44:18 +03:00
JOptionPane . showMessageDialog ( null , error ) ;
2021-01-28 20:13:21 +03:00
ok = false ;
2020-03-22 03:44:18 +03:00
}
String jvmVersion = JVMHelper . RUNTIME_MXBEAN . getVmVersion ( ) ;
LogHelper . info ( jvmVersion ) ;
2021-01-28 20:13:21 +03:00
int version = JVMHelper . getVersion ( ) ;
if ( version < minVersion | | version > maxVersion ) {
String error = String . format ( " У В а с установлена Java %s. Для правильной работы необходима Java %d" , JVMHelper . RUNTIME_MXBEAN . getVmVersion ( ) , recommendVersion ) ;
2020-03-22 03:44:18 +03:00
LogHelper . error ( error ) ;
2021-01-28 20:13:21 +03:00
if ( showMessage )
2020-03-22 03:44:18 +03:00
JOptionPane . showMessageDialog ( null , error ) ;
2021-01-28 20:13:21 +03:00
ok = false ;
2020-03-22 03:44:18 +03:00
}
2021-01-28 20:13:21 +03:00
return ok ;
2020-03-22 03:44:18 +03:00
}
2020-04-05 10:27:04 +03:00
2020-03-22 03:44:18 +03:00
private static LinkedList < Path > resolveClassPathList ( Path clientDir , String . . . classPath ) throws IOException {
return resolveClassPathStream ( clientDir , classPath ) . collect ( Collectors . toCollection ( LinkedList : : new ) ) ;
}
private static Stream < Path > resolveClassPathStream ( Path clientDir , String . . . classPath ) throws IOException {
Stream . Builder < Path > builder = Stream . builder ( ) ;
for ( String classPathEntry : classPath ) {
Path path = clientDir . resolve ( IOHelper . toPath ( classPathEntry . replace ( IOHelper . CROSS_SEPARATOR , IOHelper . PLATFORM_SEPARATOR ) ) ) ;
if ( IOHelper . isDir ( path ) ) { // Recursive walking and adding
IOHelper . walk ( path , new ClassPathFileVisitor ( builder ) , false ) ;
continue ;
}
builder . accept ( path ) ;
}
return builder . build ( ) ;
}
private static void launch ( ClientProfile profile , ClientLauncherProcess . ClientParams params ) throws Throwable {
// Add client args
Collection < String > args = new LinkedList < > ( ) ;
if ( profile . getVersion ( ) . compareTo ( ClientProfile . Version . MC164 ) > = 0 )
params . addClientArgs ( args ) ;
else {
params . addClientLegacyArgs ( args ) ;
System . setProperty ( " minecraft.applet.TargetDirectory " , params . clientDir ) ;
}
Collections . addAll ( args , profile . getClientArgs ( ) ) ;
2021-03-20 11:53:22 +03:00
for ( OptionalAction action : params . actions ) {
if ( action instanceof OptionalActionClientArgs ) {
2020-09-26 20:52:43 +03:00
args . addAll ( ( ( OptionalActionClientArgs ) action ) . args ) ;
}
}
2020-03-22 03:44:18 +03:00
List < String > copy = new ArrayList < > ( args ) ;
for ( int i = 0 , l = copy . size ( ) ; i < l ; i + + ) {
String s = copy . get ( i ) ;
if ( i + 1 < l & & ( " --accessToken " . equals ( s ) | | " --session " . equals ( s ) ) ) {
copy . set ( i + 1 , " censored " ) ;
}
}
LogHelper . debug ( " Args: " + copy ) ;
// Resolve main class and method
Class < ? > mainClass = classLoader . loadClass ( profile . getMainClass ( ) ) ;
2021-03-20 11:53:22 +03:00
if ( LogHelper . isDevEnabled ( ) & & classLoader instanceof URLClassLoader ) {
for ( URL u : ( ( URLClassLoader ) classLoader ) . getURLs ( ) ) {
2021-01-28 20:13:21 +03:00
LogHelper . dev ( " ClassLoader URL: %s " , u . toString ( ) ) ;
}
2020-03-22 03:44:18 +03:00
}
FMLPatcher . apply ( ) ;
2020-04-03 11:12:31 +03:00
LauncherEngine . modulesManager . invokeEvent ( new ClientProcessPreInvokeMainClassEvent ( params , profile , args ) ) ;
2021-01-28 20:13:21 +03:00
{
List < String > compatClasses = profile . getCompatClasses ( ) ;
2021-03-20 11:53:22 +03:00
for ( String e : compatClasses ) {
2021-01-28 20:13:21 +03:00
Class < ? > clazz = classLoader . loadClass ( e ) ;
MethodHandle runMethod = MethodHandles . publicLookup ( ) . findStatic ( clazz , " run " , MethodType . methodType ( void . class ) ) ;
runMethod . invoke ( ) ;
}
}
2020-03-22 03:44:18 +03:00
MethodHandle mainMethod = MethodHandles . publicLookup ( ) . findStatic ( mainClass , " main " , MethodType . methodType ( void . class , String [ ] . class ) ) . asFixedArity ( ) ;
Launcher . LAUNCHED . set ( true ) ;
JVMHelper . fullGC ( ) ;
// Invoke main method
try {
mainMethod . invokeWithArguments ( ( Object ) args . toArray ( new String [ 0 ] ) ) ;
LogHelper . debug ( " Main exit successful " ) ;
} catch ( Throwable e ) {
LogHelper . error ( e ) ;
throw e ;
} finally {
LauncherEngine . exitLauncher ( 0 ) ;
}
}
2020-04-05 10:27:04 +03:00
private static final class ClassPathFileVisitor extends SimpleFileVisitor < Path > {
private final Stream . Builder < Path > result ;
private ClassPathFileVisitor ( Stream . Builder < Path > result ) {
this . result = result ;
}
@Override
public FileVisitResult visitFile ( Path file , BasicFileAttributes attrs ) throws IOException {
if ( IOHelper . hasExtension ( file , " jar " ) | | IOHelper . hasExtension ( file , " zip " ) )
result . accept ( file ) ;
return super . visitFile ( file , attrs ) ;
}
}
2020-03-22 03:44:18 +03:00
}