Merge branch 'release/5.3.0'

This commit is contained in:
Gravita 2022-09-08 14:09:51 +07:00
commit 07be86f695
67 changed files with 1056 additions and 473 deletions

View file

@ -7,9 +7,13 @@
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.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;
@ -19,12 +23,9 @@
import pro.gravit.utils.helper.CommonHelper;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.*;
public class HttpAuthCoreProvider extends AuthCoreProvider {
public class HttpAuthCoreProvider extends AuthCoreProvider implements AuthSupportHardware {
private transient final Logger logger = LogManager.getLogger();
private transient HttpRequester requester;
public String bearerToken;
@ -32,12 +33,23 @@ public class HttpAuthCoreProvider extends AuthCoreProvider {
public String getUserByLoginUrl;
public String getUserByUUIDUrl;
public String getUserByTokenUrl;
public String getAuthDetails;
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;
@Override
public User getUserByUsername(String username) {
try {
@ -73,11 +85,11 @@ public User getUserByUUID(UUID uuid) {
@Override
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
if(getAuthDetails == null) {
if(getAuthDetailsUrl == null) {
return super.getDetails(client);
}
try {
var result = requester.send(requester.get(getAuthDetails, bearerToken), GetAuthDetailsResponse.class).getOrThrow();
var result = requester.send(requester.get(getAuthDetailsUrl, bearerToken), GetAuthDetailsResponse.class).getOrThrow();
return result.details;
} catch (IOException e) {
logger.error(e);
@ -113,7 +125,7 @@ public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthRespon
}
try {
return requester.send(requester.post(refreshTokenUrl, new RefreshTokenRequest(refreshToken, context),
null), AuthManager.AuthReport.class).getOrThrow();
null), HttpAuthReport.class).getOrThrow().toAuthReport();
} catch (IOException e) {
logger.error(e);
return null;
@ -133,6 +145,128 @@ public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext c
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);
}
}
@Override
public Iterable<User> getUsersByHardwareInfo(UserHardware hardware) {
if(getUsersByHardwareInfoUrl == null) {
return null;
}
try {
return requester.send(requester
.post(getUsersByHardwareInfoUrl, new HardwareRequest((HttpUserHardware) hardware), bearerToken), List.class).getOrThrow();
} 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);
}
}
public record HttpAuthReport(String minecraftAccessToken, String oauthAccessToken,
String oauthRefreshToken, long oauthExpire,
HttpUserSession session) {
@ -246,7 +380,27 @@ public RefreshTokenRequest(String refreshToken, AuthResponse.AuthContext context
}
}
public static class HttpUser implements User, UserSupportTextures, UserSupportProperties {
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 class HttpUser implements User, UserSupportTextures, UserSupportProperties, UserSupportHardware {
private String username;
private UUID uuid;
private String serverId;
@ -258,19 +412,22 @@ public static class HttpUser implements User, UserSupportTextures, UserSupportPr
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) {
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) {
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;
@ -278,9 +435,10 @@ public HttpUser(String username, UUID uuid, String serverId, String 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) {
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;
@ -289,9 +447,10 @@ public HttpUser(String username, UUID uuid, String serverId, String accessToken,
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) {
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;
@ -299,6 +458,7 @@ public HttpUser(String username, UUID uuid, String serverId, String accessToken,
this.permissions = permissions;
this.assets = assets;
this.properties = properties;
this.hwidId = hwidId;
}
@Override
@ -374,8 +534,17 @@ public String toString() {
", 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;
}
}
public static class HttpUserSession implements UserSession {
@ -416,4 +585,65 @@ public String toString() {
'}';
}
}
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 +
'}';
}
}
}

View file

@ -0,0 +1,11 @@
package pro.gravit.launchserver.auth.core.interfaces.session;
import java.security.PrivateKey;
import java.security.PublicKey;
public interface UserSessionSupportKeys {
ClientProfileKeys getClientProfileKeys();
record ClientProfileKeys(PublicKey publicKey, PrivateKey privateKey, byte[] signature /* V2 */, long expiresAt, long refreshedAfter) {
}
}

View file

@ -122,7 +122,7 @@ public String createHardwareToken(String username, UserHardware hardware) {
return Jwts.builder()
.setIssuer("LaunchServer")
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 8))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * server.config.netty.security.hardwareTokenExpire))
.claim("hardware", hardware.getId())
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
.compact();
@ -132,7 +132,7 @@ public String createPublicKeyToken(String username, byte[] publicKey) {
return Jwts.builder()
.setIssuer("LaunchServer")
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 8))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * server.config.netty.security.publicKeyTokenExpire))
.claim("publicKey", Base64.getEncoder().encodeToString(publicKey))
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
.compact();

View file

@ -124,9 +124,8 @@ protected void initProps() {
server.runtime.clientCheckSecret.concat(".").concat(launcherSalt));
properties.put("runtimeconfig.secureCheckHash", Base64.getEncoder().encodeToString(launcherSecureHash));
properties.put("runtimeconfig.secureCheckSalt", launcherSalt);
//LogHelper.debug("[checkSecure] %s: %s", launcherSalt, Arrays.toString(launcherSecureHash));
if (server.runtime.oemUnlockKey == null) server.runtime.oemUnlockKey = SecurityHelper.randomStringToken();
properties.put("runtimeconfig.oemUnlockKey", server.runtime.oemUnlockKey);
if (server.runtime.unlockSecret == null) server.runtime.unlockSecret = SecurityHelper.randomStringToken();
properties.put("runtimeconfig.unlockSecret", server.runtime.unlockSecret);
}

View file

@ -14,7 +14,7 @@
import java.nio.file.Path;
public class Launch4JTask implements LauncherBuildTask, BuildExeMainTask {
public static final String DOWNLOAD_URL = "http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html"; // Oracle
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;

View file

@ -1,25 +1,40 @@
package pro.gravit.launchserver.command.hash;
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.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 proguard.OutputWriter;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class DownloadAssetCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
private static final String MINECRAFT_VERSIONS_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
private static final String RESOURCES_DOWNLOAD_URL = "https://resources.download.minecraft.net/";
public DownloadAssetCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[version] [dir]";
return "[version] [dir] (mojang/mirror)";
}
@Override
@ -32,20 +47,94 @@ public void invoke(String... args) throws Exception {
verifyArgs(args, 2);
//Version version = Version.byName(args[0]);
String versionName = args[0];
String type = args.length > 2 ? args[2] : "mojang";
String dirName = IOHelper.verifyFileName(args[1]);
Path assetDir = server.updatesDir.resolve(dirName);
// Create asset dir
logger.info("Creating asset dir: '{}'", dirName);
Files.createDirectory(assetDir);
if(Files.notExists(assetDir)) {
logger.info("Creating asset dir: '{}'", dirName);
Files.createDirectory(assetDir);
}
// Download required asset
logger.info("Downloading asset, it may take some time");
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getAssetsURL(version.name), assetDir);
server.mirrorManager.downloadZip(assetDir, "assets/%s.zip", versionName);
if(type.equals("mojang")) {
HttpRequester requester = new HttpRequester();
logger.info("Fetch versions from {}", MINECRAFT_VERSIONS_URL);
var versions = requester.send(requester.get(MINECRAFT_VERSIONS_URL, null), MinecraftVersions.class).getOrThrow();
String profileUrl = null;
for(var e : versions.versions) {
if(e.id.equals(versionName)) {
profileUrl = e.url;
break;
}
}
if(profileUrl == null) {
logger.error("Version {} not found", versionName);
return;
}
logger.info("Fetch profile {} from {}", versionName, profileUrl);
var profileInfo = requester.send(requester.get(profileUrl, null), MiniVersion.class).getOrThrow();
String assetsIndexUrl = profileInfo.assetIndex.url;
String assetIndex = profileInfo.assetIndex.id;
Path indexPath = assetDir.resolve("indexes").resolve(assetIndex+".json");
logger.info("Fetch asset index {} from {}", assetIndex, assetsIndexUrl);
JsonObject assets = requester.send(requester.get(assetsIndexUrl, null), JsonObject.class).getOrThrow();
JsonObject objects = assets.get("objects").getAsJsonObject();
try(Writer writer = IOHelper.newWriter(indexPath)) {
logger.info("Save {}", indexPath);
Launcher.gsonManager.configGson.toJson(assets, writer);
}
if(!assetIndex.equals(versionName)) {
Path targetPath = assetDir.resolve("indexes").resolve(versionName+".json");
logger.info("Copy {} into {}", indexPath, targetPath);
Files.copy(indexPath, targetPath, StandardCopyOption.REPLACE_EXISTING);
}
List<AsyncDownloader.SizedFile> toDownload = new ArrayList<>(128);
for(var e : objects.entrySet()) {
var value = e.getValue().getAsJsonObject();
var hash = value.get("hash").getAsString();
hash = hash.substring(0, 2) + "/" + hash;
var size = value.get("size").getAsLong();
var path = "objects/" + hash;
var target = assetDir.resolve(path);
if(Files.exists(target)) {
long fileSize = Files.size(target);
if(fileSize != size) {
logger.warn("File {} corrupted. Size {}, expected {}", target, size, fileSize);
} else {
continue;
}
}
toDownload.add(new AsyncDownloader.SizedFile(hash, path, size));
}
logger.info("Download {} files", toDownload.size());
Downloader downloader = downloadWithProgressBar(dirName, toDownload, RESOURCES_DOWNLOAD_URL, assetDir);
downloader.getFuture().get();
} else {
// Download required asset
logger.info("Downloading asset, it may take some time");
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getAssetsURL(version.name), assetDir);
server.mirrorManager.downloadZip(assetDir, "assets/%s.zip", versionName);
}
// Finished
server.syncUpdatesDir(Collections.singleton(dirName));
logger.info("Asset successfully downloaded: '{}'", dirName);
}
public record MiniVersionInfo(String id, String url) {
}
public record MinecraftVersions(List<MiniVersionInfo> versions) {
}
public record MinecraftAssetIndexInfo(String id, String url) {
}
public record MiniVersion(MinecraftAssetIndexInfo assetIndex) {
}
}

View file

@ -71,7 +71,7 @@ public void invoke(String... args) throws IOException, CommandException {
if (version.compareTo(ClientProfile.Version.MC164) <= 0) {
logger.warn("Minecraft 1.6.4 and below not supported. Use at your own risk");
}
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version);
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version, Files.exists(server.updatesDir.resolve("assets")));
for (MakeProfileHelper.MakeProfileOption option : options) {
logger.debug("Detected option {}", option.getClass().getSimpleName());
}

View file

@ -10,6 +10,7 @@
import pro.gravit.utils.helper.IOHelper;
import java.io.Writer;
import java.nio.file.Files;
public class MakeProfileCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
@ -32,7 +33,7 @@ public String getUsageDescription() {
public void invoke(String... args) throws Exception {
verifyArgs(args, 3);
ClientProfile.Version version = ClientProfile.Version.byName(args[1]);
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(server.updatesDir.resolve(args[2]), version);
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(server.updatesDir.resolve(args[2]), version, Files.exists(server.updatesDir.resolve("assets")));
for (MakeProfileHelper.MakeProfileOption option : options) {
logger.info("Detected option {}", option);
}

View file

@ -134,7 +134,7 @@ public Path process(Path inputFile) throws IOException {
if (component.enabled) {
Configuration proguard_cfg = new Configuration();
if (!checkJMods(IOHelper.JVM_DIR.resolve("jmods"))) {
logger.error("Java path: {} is not JDK! Please install JDK", IOHelper.JVM_DIR);
throw new RuntimeException(String.format("Java path: %s is not JDK! Please install JDK", IOHelper.JVM_DIR));
}
Path jfxPath = tryFindOpenJFXPath(IOHelper.JVM_DIR);
if (checkFXJMods(IOHelper.JVM_DIR.resolve("jmods"))) {
@ -143,8 +143,7 @@ public Path process(Path inputFile) throws IOException {
} else if (jfxPath != null && checkFXJMods(jfxPath)) {
logger.debug("JMods resolved in {}", jfxPath.toString());
} else {
logger.error("JavaFX jmods not found. May be install OpenJFX?");
jfxPath = null;
throw new RuntimeException("JavaFX jmods not found. May be install OpenJFX?");
}
ConfigurationParser parser = new ConfigurationParser(proguardConf.buildConfig(inputFile, outputJar, jfxPath == null ? new Path[0] : new Path[]{jfxPath}),
proguardConf.proguard.toFile(), System.getProperties());

View file

@ -45,9 +45,9 @@ public final class LaunchServerConfig {
public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
LaunchServerConfig newConfig = new LaunchServerConfig();
newConfig.mirrors = new String[]{"https://mirror.gravit.pro/5.2.x/", "https://gravit-launcher-mirror.storage.googleapis.com/"};
newConfig.mirrors = new String[]{"https://mirror.gravit.pro/5.3.x/", "https://gravit-launcher-mirror.storage.googleapis.com/"};
newConfig.launch4j = new LaunchServerConfig.ExeConf();
newConfig.launch4j.enabled = true;
newConfig.launch4j.enabled = false;
newConfig.launch4j.copyright = "© GravitLauncher Team";
newConfig.launch4j.fileDesc = "GravitLauncher ".concat(Version.getVersion().getVersionString());
newConfig.launch4j.fileVer = Version.getVersion().getVersionString().concat(".").concat(String.valueOf(Version.getVersion().patch));
@ -168,9 +168,9 @@ public void verify() {
boolean updateMirror = Boolean.getBoolean("launchserver.config.disableUpdateMirror");
if(!updateMirror) {
for(int i=0;i < mirrors.length;++i) {
if("https://mirror.gravit.pro/".equals(mirrors[i])) {
logger.warn("Replace mirror 'https://mirror.gravit.pro/' to 'https://mirror.gravit.pro/5.2.x/'. If you really need to use original url, use '-Dlaunchserver.config.disableUpdateMirror=true'");
mirrors[i] = "https://mirror.gravit.pro/5.2.x/";
if("https://mirror.gravit.pro/5.2.x/".equals(mirrors[i])) {
logger.warn("Replace mirror 'https://mirror.gravit.pro/5.2.x/' to 'https://mirror.gravit.pro/5.3.x/'. If you really need to use original url, use '-Dlaunchserver.config.disableUpdateMirror=true'");
mirrors[i] = "https://mirror.gravit.pro/5.3.x/";
}
}
}
@ -278,8 +278,6 @@ public static class LauncherConf {
public static class NettyConfig {
public boolean fileServerEnabled;
@Deprecated
public boolean sendExceptionEnabled;
public boolean ipForwarding;
public boolean disableWebApiInterface;
public boolean showHiddenFiles;
@ -289,6 +287,8 @@ public static class NettyConfig {
public String address;
public Map<String, LaunchServerConfig.NettyUpdatesBind> bindings = new HashMap<>();
public NettyPerformanceConfig performance;
public NettySecurityConfig security = new NettySecurityConfig();
public NettyBindAddress[] binds;
public LogLevel logLevel = LogLevel.DEBUG;
}
@ -298,7 +298,6 @@ public static class NettyPerformanceConfig {
public int bossThread;
public int workerThread;
public int schedulerThread;
public long sessionLifetimeMs = 24 * 60 * 60 * 1000;
public int maxWebSocketRequestBytes = 1024 * 1024;
}
@ -311,4 +310,11 @@ public NettyBindAddress(String address, int port) {
this.port = port;
}
}
public static class NettySecurityConfig {
public long hardwareTokenExpire = 60 * 60 * 8;
public long publicKeyTokenExpire = 60 * 60 * 8;
public long launcherTokenExpire = 60 * 60 * 8;
}
}

View file

@ -8,7 +8,7 @@ public class LaunchServerRuntimeConfig {
private transient final Logger logger = LogManager.getLogger();
public String passwordEncryptKey;
public String runtimeEncryptKey;
public String oemUnlockKey;
public String unlockSecret;
public String registerApiKey;
public String clientCheckSecret;

View file

@ -18,7 +18,11 @@ public static ClientProfile makeProfile(ClientProfile.Version version, String ti
ClientProfileBuilder builder = new ClientProfileBuilder();
builder.setVersion(version.name);
builder.setDir(title);
builder.setAssetDir("asset" + version.name);
if(findOption(options, MakeProfileOptionGlobalAssets.class).isPresent()) {
builder.setAssetDir("assets");
} else {
builder.setAssetDir("asset" + version.name);
}
builder.setAssetIndex(version.name);
builder.setInfo("Информация о сервере");
builder.setTitle(title);
@ -42,11 +46,16 @@ public static ClientProfile makeProfile(ClientProfile.Version version, String ti
Set<OptionalFile> optionals = new HashSet<>();
jvmArgs.add("-XX:+DisableAttachMechanism");
// Official Mojang launcher java arguments
jvmArgs.add("-XX:+UseG1GC");
jvmArgs.add("-XX:+UnlockExperimentalVMOptions");
jvmArgs.add("-XX:G1NewSizePercent=20");
jvmArgs.add("-XX:MaxGCPauseMillis=50");
jvmArgs.add("-XX:G1HeapRegionSize=32M");
if(version.compareTo(ClientProfile.Version.MC112) <= 0) {
jvmArgs.add("-XX:+UseConcMarkSweepGC");
jvmArgs.add("-XX:+CMSIncrementalMode");
} else if(version.compareTo(ClientProfile.Version.MC118) <= 0) { // 1.13 - 1.16.5
jvmArgs.add("-XX:+UseG1GC");
jvmArgs.add("-XX:+UnlockExperimentalVMOptions");
} else { // 1.18+
jvmArgs.add("-XX:+UseShenandoahGC");
jvmArgs.add("-XX:+UnlockExperimentalVMOptions");
}
// -----------
Optional<MakeProfileOptionForge> forge = findOption(options, MakeProfileOptionForge.class);
Optional<MakeProfileOptionFabric> fabric = findOption(options, MakeProfileOptionFabric.class);
@ -192,7 +201,7 @@ private static String getLog4jVersion(Path dir) throws IOException {
return null;
}
public static MakeProfileOption[] getMakeProfileOptionsFromDir(Path dir, ClientProfile.Version version) throws IOException {
public static MakeProfileOption[] getMakeProfileOptionsFromDir(Path dir, ClientProfile.Version version, boolean globalAssets) throws IOException {
List<MakeProfileOption> options = new ArrayList<>(2);
if (Files.exists(dir.resolve("forge.jar"))) {
options.add(new MakeProfileOptionForge());
@ -227,6 +236,9 @@ public static MakeProfileOption[] getMakeProfileOptionsFromDir(Path dir, ClientP
if (Files.exists(dir.resolve("libraries/forge/launchwrapper-1.12-launcherfixed.jar.jar")) || Files.exists(dir.resolve("libraries/net/minecraft/launchwrapper"))) {
options.add(new MakeProfileOptionLaunchWrapper());
}
if(globalAssets) {
options.add(new MakeProfileOptionGlobalAssets());
}
return options.toArray(new MakeProfileOption[0]);
}
@ -302,6 +314,10 @@ public static class MakeProfileOptionLaunchWrapper implements MakeProfileOption
}
public static class MakeProfileOptionGlobalAssets implements MakeProfileOption {
}
public static class MakeProfileOptionFabric implements MakeProfileOption {
public String jimfsPath;
public String guavaPath;

View file

@ -16,6 +16,7 @@
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.auth.core.interfaces.session.UserSessionSupportKeys;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportProperties;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportTextures;
import pro.gravit.launchserver.auth.texture.TextureProvider;
@ -26,7 +27,13 @@
import pro.gravit.utils.helper.SecurityHelper;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
public class AuthManager {
@ -180,6 +187,10 @@ public void internalAuth(Client client, AuthResponse.ConnectTypes authType, Auth
client.uuid = uuid;
}
public UserSessionSupportKeys.ClientProfileKeys createClientProfileKeys(UUID playerUUID) {
throw new UnsupportedOperationException("Minecraft 1.19.1 signature"); // TODO
}
public CheckServerReport checkServer(Client client, String username, String serverID) throws IOException {
if (client.auth == null) return null;
User user = client.auth.core.checkServer(client, username, serverID);

View file

@ -20,6 +20,7 @@
import pro.gravit.launchserver.socket.response.WebSocketServerResponse;
import pro.gravit.launchserver.socket.response.auth.*;
import pro.gravit.launchserver.socket.response.management.FeaturesResponse;
import pro.gravit.launchserver.socket.response.management.GetPublicKeyResponse;
import pro.gravit.launchserver.socket.response.management.ServerStatusResponse;
import pro.gravit.launchserver.socket.response.profile.BatchProfileByUsername;
import pro.gravit.launchserver.socket.response.profile.ProfileByUUIDResponse;
@ -88,6 +89,8 @@ public static void registerResponses() {
providers.register("refreshToken", RefreshTokenResponse.class);
providers.register("restore", RestoreResponse.class);
providers.register("additionalData", AdditionalDataResponse.class);
providers.register("clientProfileKey", FetchClientProfileKeyResponse.class);
providers.register("getPublicKey", GetPublicKeyResponse.class);
}
public void forEachActiveChannels(BiConsumer<Channel, WebSocketFrameHandler> callback) {

View file

@ -0,0 +1,32 @@
package pro.gravit.launchserver.socket.response.auth;
import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.events.request.FetchClientProfileKeyRequestEvent;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportKeys;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse;
public class FetchClientProfileKeyResponse extends SimpleResponse {
@Override
public String getType() {
return "clientProfileKey";
}
@Override
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
if (!client.isAuth || client.type != AuthResponse.ConnectTypes.CLIENT) {
sendError("Permissions denied");
return;
}
UserSession session = client.sessionObject;
UserSessionSupportKeys.ClientProfileKeys keys;
if(session instanceof UserSessionSupportKeys support) {
keys = support.getClientProfileKeys();
} else {
keys = server.authManager.createClientProfileKeys(client.uuid);
}
sendResult(new FetchClientProfileKeyRequestEvent(keys.publicKey(), keys.privateKey(), keys.signature(), keys.expiresAt(), keys.refreshedAfter()));
}
}

View file

@ -0,0 +1,18 @@
package pro.gravit.launchserver.socket.response.management;
import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.events.request.GetPublicKeyRequestEvent;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse;
public class GetPublicKeyResponse extends SimpleResponse {
@Override
public String getType() {
return "getPublicKey";
}
@Override
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
sendResult(new GetPublicKeyRequestEvent(server.keyAgreementManager.rsaPublicKey, server.keyAgreementManager.ecdsaPublicKey));
}
}

View file

@ -71,7 +71,7 @@ public String createLauncherExtendedToken() {
return Jwts.builder()
.setIssuer("LaunchServer")
.claim("checkSign", true)
.setExpiration(Date.from(LocalDateTime.now().plusHours(8).toInstant(ZoneOffset.UTC)))
.setExpiration(Date.from(LocalDateTime.now().plusSeconds(server.config.netty.security.launcherTokenExpire).toInstant(ZoneOffset.UTC)))
.signWith(server.keyAgreementManager.ecdsaPrivateKey, SignatureAlgorithm.ES256)
.compact();
}

View file

@ -42,7 +42,6 @@ task javadocJar(type: Jar) {
shadowJar {
duplicatesStrategy = 'EXCLUDE'
archiveClassifier.set(null)
relocate 'org.objectweb.asm', 'pro.gravit.repackage.org.objectweb.asm'
relocate 'io.netty', 'pro.gravit.repackage.io.netty'
configurations = [project.configurations.pack]
exclude 'module-info.class'
@ -52,7 +51,6 @@ task javadocJar(type: Jar) {
pack project(':LauncherAPI')
bundle group: 'com.github.oshi', name: 'oshi-core', version: rootProject['verOshiCore']
pack group: 'io.netty', name: 'netty-codec-http', version: rootProject['verNetty']
pack group: 'org.ow2.asm', name: 'asm-tree', version: rootProject['verAsm']
}
task genRuntimeJS(type: Zip) {

View file

@ -1,6 +1,5 @@
package pro.gravit.launcher;
import pro.gravit.launcher.patches.FMLPatcher;
import pro.gravit.launcher.utils.NativeJVMHalt;
import pro.gravit.utils.helper.LogHelper;
@ -30,7 +29,6 @@ public static void premain(String agentArgument, Instrumentation instrumentation
checkAgentStacktrace();
inst = instrumentation;
NativeJVMHalt.initFunc();
FMLPatcher.apply();
isAgentStarted = true;
}

View file

@ -5,10 +5,10 @@
import pro.gravit.launcher.client.events.ClientExitPhase;
import pro.gravit.launcher.client.events.ClientPreGuiPhase;
import pro.gravit.launcher.console.GetPublicKeyCommand;
import pro.gravit.launcher.console.ModulesCommand;
import pro.gravit.launcher.console.SignDataCommand;
import pro.gravit.launcher.events.request.*;
import pro.gravit.launcher.guard.LauncherGuardInterface;
import pro.gravit.launcher.guard.LauncherGuardManager;
import pro.gravit.launcher.guard.LauncherGuard;
import pro.gravit.launcher.guard.LauncherNoGuard;
import pro.gravit.launcher.guard.LauncherWrapperGuard;
import pro.gravit.launcher.gui.NoRuntimeProvider;
@ -28,7 +28,6 @@
import pro.gravit.launcher.request.secure.GetSecureLevelInfoRequest;
import pro.gravit.launcher.request.secure.SecurityReportRequest;
import pro.gravit.launcher.request.update.LauncherRequest;
import pro.gravit.launcher.request.websockets.ClientWebSocketService;
import pro.gravit.launcher.request.websockets.OfflineRequestService;
import pro.gravit.launcher.request.websockets.StdWebSocketService;
import pro.gravit.launcher.utils.NativeJVMHalt;
@ -50,7 +49,7 @@
public class LauncherEngine {
public static ClientLauncherProcess.ClientParams clientParams;
public static LauncherGuardInterface guard;
public static LauncherGuard guard;
public static ClientModuleManager modulesManager;
public final boolean clientInstance;
// Instance
@ -85,8 +84,14 @@ public static void checkClass(Class<?> clazz) throws SecurityException {
}
}
public static void exitLauncher(int code) {
modulesManager.invokeEvent(new ClientExitPhase(code));
public static void beforeExit(int code) {
try {
modulesManager.invokeEvent(new ClientExitPhase(code));
} catch (Throwable ignored) {
}
}
public static void forceExit(int code) {
try {
System.exit(code);
} catch (Throwable e) //Forge Security Manager?
@ -95,6 +100,11 @@ public static void exitLauncher(int code) {
}
}
public static void exitLauncher(int code) {
beforeExit(code);
forceExit(code);
}
public static void main(String... args) throws Throwable {
JVMHelper.checkStackTrace(LauncherEngine.class);
JVMHelper.verifySystemProperties(Launcher.class, true);
@ -143,7 +153,7 @@ public static void verifyNoAgent() {
throw new SecurityException("JavaAgent found");
}
public static LauncherGuardInterface tryGetStdGuard() {
public static LauncherGuard tryGetStdGuard() {
switch (Launcher.getConfig().guardType) {
case "no":
return new LauncherNoGuard();
@ -264,7 +274,6 @@ public void start(String... args) throws Throwable {
registerCommands();
LauncherEngine.modulesManager.invokeEvent(new ClientEngineInitPhase(this));
runtimeProvider.preLoad();
LauncherGuardManager.initGuard(clientInstance);
LogHelper.debug("Dir: %s", DirBridge.dir);
runtimeProvider.run(args);
}
@ -272,5 +281,6 @@ public void start(String... args) throws Throwable {
private void registerCommands() {
ConsoleManager.handler.registerCommand("getpublickey", new GetPublicKeyCommand(this));
ConsoleManager.handler.registerCommand("signdata", new SignDataCommand(this));
ConsoleManager.handler.registerCommand("modules", new ModulesCommand());
}
}

View file

@ -1,5 +1,7 @@
package pro.gravit.launcher.api;
import pro.gravit.launcher.utils.ApiBridgeService;
import java.lang.instrument.Instrumentation;
import java.net.URL;
@ -12,4 +14,8 @@ public class ClientService {
public static ClassLoader getClassLoader() {
return classLoader;
}
public static String findLibrary(String name) {
return ApiBridgeService.findLibrary(classLoader, name);
}
}

View file

@ -3,10 +3,10 @@
import pro.gravit.launcher.*;
import pro.gravit.launcher.api.AuthService;
import pro.gravit.launcher.api.ClientService;
import pro.gravit.launcher.api.KeyService;
import pro.gravit.launcher.client.events.client.*;
import pro.gravit.launcher.events.request.ProfileByUUIDRequestEvent;
import pro.gravit.launcher.events.request.ProfileByUsernameRequestEvent;
import pro.gravit.launcher.guard.LauncherGuardManager;
import pro.gravit.launcher.hasher.FileNameMatcher;
import pro.gravit.launcher.hasher.HashedDir;
import pro.gravit.launcher.hasher.HashedEntry;
@ -15,7 +15,6 @@
import pro.gravit.launcher.modules.LauncherModulesManager;
import pro.gravit.launcher.modules.events.OfflineModeEvent;
import pro.gravit.launcher.modules.events.PreConfigPhase;
import pro.gravit.launcher.patches.FMLPatcher;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launcher.profiles.optional.actions.OptionalAction;
import pro.gravit.launcher.profiles.optional.actions.OptionalActionClassPath;
@ -26,7 +25,6 @@
import pro.gravit.launcher.request.RequestService;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest;
import pro.gravit.launcher.request.uuid.BatchProfileByUsernameRequest;
import pro.gravit.launcher.request.uuid.ProfileByUUIDRequest;
import pro.gravit.launcher.request.uuid.ProfileByUsernameRequest;
import pro.gravit.launcher.request.websockets.OfflineRequestService;
@ -87,7 +85,6 @@ public static void main(String[] args) throws Throwable {
ConsoleManager.initConsole();
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));
if (params.profile.getClassLoaderConfig() != ClientProfile.ClassLoaderConfig.AGENT) {
@ -118,10 +115,11 @@ public static void main(String[] args) throws Throwable {
// Verify ClientLauncher sign and classpath
LogHelper.debug("Verifying ClientLauncher sign and classpath");
List<Path> classpath = resolveClassPath(clientDir, params.actions, params.profile).collect(Collectors.toList());
List<Path> classpath = resolveClassPath(clientDir, params.actions, params.profile)
.filter(x -> !profile.getModulePath().contains(clientDir.relativize(x).toString()))
.collect(Collectors.toList());
List<URL> classpathURLs = classpath.stream().map(IOHelper::toURL).collect(Collectors.toList());
// Start client with WatchService monitoring
boolean digest = !profile.isUpdateFastCheck();
RequestService service;
if(params.offlineMode) {
service = initOffline(LauncherEngine.modulesManager, params);
@ -143,13 +141,14 @@ public static void main(String[] args) throws Throwable {
}
};
}
LogHelper.debug("Natives dir %s", params.nativesDir);
ClientProfile.ClassLoaderConfig classLoaderConfig = profile.getClassLoaderConfig();
if (classLoaderConfig == ClientProfile.ClassLoaderConfig.LAUNCHER) {
ClientClassLoader classLoader = new ClientClassLoader(classpathURLs.toArray(new URL[0]), ClassLoader.getSystemClassLoader());
System.setProperty("java.class.path", classpath.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator)));
ClientLauncherEntryPoint.classLoader = classLoader;
Thread.currentThread().setContextClassLoader(classLoader);
classLoader.nativePath = clientDir.resolve("natives").toString();
classLoader.nativePath = params.nativesDir;
LauncherEngine.modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(engine, classLoader, profile));
ClientService.classLoader = classLoader;
ClientService.nativePath = classLoader.nativePath;
@ -162,7 +161,7 @@ public static void main(String[] args) throws Throwable {
LauncherAgent.addJVMClassPath(Paths.get(url.toURI()));
}
ClientService.instrumentation = LauncherAgent.inst;
ClientService.nativePath = clientDir.resolve("natives").toString();
ClientService.nativePath = params.nativesDir;
LauncherEngine.modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(engine, classLoader, profile));
ClientService.classLoader = classLoader;
ClientService.baseURLs = classpathURLs.toArray(new URL[0]);
@ -170,9 +169,11 @@ public static void main(String[] args) throws Throwable {
ClientLauncherEntryPoint.classLoader = ClassLoader.getSystemClassLoader();
ClientService.classLoader = ClassLoader.getSystemClassLoader();
ClientService.baseURLs = classpathURLs.toArray(new URL[0]);
ClientService.nativePath = params.nativesDir;
}
AuthService.username = params.playerProfile.username;
AuthService.uuid = params.playerProfile.uuid;
KeyService.serverRsaPublicKey = Launcher.getConfig().rsaPublicKey;
if (params.profile.getRuntimeInClientConfig() != ClientProfile.RuntimeInClientConfig.NONE) {
CommonHelper.newThread("Client Launcher Thread", true, () -> {
try {
@ -187,9 +188,9 @@ public static void main(String[] args) throws Throwable {
FileNameMatcher assetMatcher = profile.getAssetUpdateMatcher();
FileNameMatcher clientMatcher = profile.getClientUpdateMatcher();
Path javaDir = Paths.get(System.getProperty("java.home"));
try (DirWatcher assetWatcher = new DirWatcher(assetDir, params.assetHDir, assetMatcher, digest);
DirWatcher clientWatcher = new DirWatcher(clientDir, params.clientHDir, clientMatcher, digest);
DirWatcher javaWatcher = params.javaHDir == null ? null : new DirWatcher(javaDir, params.javaHDir, null, digest)) {
try (DirWatcher assetWatcher = new DirWatcher(assetDir, params.assetHDir, assetMatcher, true);
DirWatcher clientWatcher = new DirWatcher(clientDir, params.clientHDir, clientMatcher, true);
DirWatcher javaWatcher = params.javaHDir == null ? null : new DirWatcher(javaDir, params.javaHDir, null, true)) {
// Verify current state of all dirs
//verifyHDir(IOHelper.JVM_DIR, jvmHDir.object, null, digest);
//for (OptionalFile s : Launcher.profile.getOptional()) {
@ -201,10 +202,10 @@ public static void main(String[] args) throws Throwable {
CommonHelper.newThread("Client Directory Watcher", true, clientWatcher).start();
if (javaWatcher != null)
CommonHelper.newThread("Java Directory Watcher", true, javaWatcher).start();
verifyHDir(assetDir, params.assetHDir, assetMatcher, digest);
verifyHDir(clientDir, params.clientHDir, clientMatcher, digest);
verifyHDir(assetDir, params.assetHDir, assetMatcher, false, false);
verifyHDir(clientDir, params.clientHDir, clientMatcher, false, true);
if (javaWatcher != null)
verifyHDir(javaDir, params.javaHDir, null, digest);
verifyHDir(javaDir, params.javaHDir, null, false, true);
LauncherEngine.modulesManager.invokeEvent(new ClientProcessLaunchEvent(engine, params));
launch(profile, params);
}
@ -243,36 +244,34 @@ public static void applyClientOfflineProcessors(OfflineRequestService service, C
});
}
public static void verifyHDir(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException {
public static void verifyHDir(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest, boolean checkExtra) 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()) {
if (LogHelper.isDebugEnabled()) {
diff.extra.walk(File.separator, (e, k, v) -> {
if (v.getType().equals(HashedEntry.Type.FILE)) {
LogHelper.error("Extra file %s", e);
} else LogHelper.error("Extra %s", e);
return HashedDir.WalkAction.CONTINUE;
});
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);
return HashedDir.WalkAction.CONTINUE;
});
}
if (!diff.mismatch.isEmpty() || (checkExtra && !diff.extra.isEmpty())) {
diff.extra.walk(File.separator, (e, k, v) -> {
if (v.getType().equals(HashedEntry.Type.FILE)) {
LogHelper.error("Extra file %s", e);
} else LogHelper.error("Extra %s", e);
return HashedDir.WalkAction.CONTINUE;
});
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);
return HashedDir.WalkAction.CONTINUE;
});
throw new SecurityException(String.format("Forbidden modification: '%s'", IOHelper.getFileName(dir)));
}
}
public static boolean checkJVMBitsAndVersion(int minVersion, int recommendVersion, int maxVersion, boolean showMessage) {
boolean ok = true;
if (JVMHelper.JVM_BITS != JVMHelper.OS_BITS) {
String error = String.format("У Вас установлена Java %d, но Ваша система определена как %d. Установите Java правильной разрядности", JVMHelper.JVM_BITS, JVMHelper.OS_BITS);
if (JVMHelper.JVM_BITS == 64 && JVMHelper.ARCH_TYPE == JVMHelper.ARCH.X86) {
String error = "У Вас установлена Java x64, но Ваша система определена как x32. Установите Java правильной разрядности";
LogHelper.error(error);
if (showMessage)
JOptionPane.showMessageDialog(null, error);
@ -282,7 +281,7 @@ public static boolean checkJVMBitsAndVersion(int minVersion, int recommendVersio
LogHelper.info(jvmVersion);
int version = JVMHelper.getVersion();
if (version < minVersion || version > maxVersion) {
String error = String.format("У Вас установлена Java %s. Для правильной работы необходима Java %d", JVMHelper.RUNTIME_MXBEAN.getVmVersion(), recommendVersion);
String error = String.format("У Вас установлена Java %d, но этот клиент требует Java %d", JVMHelper.getVersion(), recommendVersion);
LogHelper.error(error);
if (showMessage)
JOptionPane.showMessageDialog(null, error);
@ -347,7 +346,6 @@ private static void launch(ClientProfile profile, ClientLauncherProcess.ClientPa
LogHelper.dev("ClassLoader URL: %s", u.toString());
}
}
FMLPatcher.apply();
LauncherEngine.modulesManager.invokeEvent(new ClientProcessPreInvokeMainClassEvent(params, profile, args));
// Invoke main method
try {

View file

@ -1,6 +1,7 @@
package pro.gravit.launcher.client;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherConfig;
import pro.gravit.launcher.LauncherEngine;
import pro.gravit.launcher.LauncherNetworkAPI;
import pro.gravit.launcher.client.events.client.ClientProcessBuilderCreateEvent;
@ -25,6 +26,7 @@
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
@ -42,42 +44,45 @@ public class ClientLauncherProcess {
private final transient Boolean[] waitWriteParams = new Boolean[]{false};
public Path executeFile;
public Path workDir;
public Path javaDir;
public int bits;
public JavaHelper.JavaVersion javaVersion;
public boolean useLegacyJavaClassPathProperty;
public boolean isStarted;
public JavaHelper.JavaVersion javaVersion;
private transient Process process;
public ClientLauncherProcess(Path executeFile, Path workDir, Path javaDir, String mainClass) {
public ClientLauncherProcess(Path executeFile, Path workDir, JavaHelper.JavaVersion javaVersion, String mainClass) {
this.executeFile = executeFile;
this.workDir = workDir;
this.javaDir = javaDir;
this.javaVersion = javaVersion;
this.mainClass = mainClass;
}
public ClientLauncherProcess(Path clientDir, Path assetDir, Path javaDir,
public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersion javaVersion,
ClientProfile profile, PlayerProfile playerProfile, String accessToken,
HashedDir clientHDir, HashedDir assetHDir, HashedDir jvmHDir) {
this(clientDir, assetDir, javaDir, clientDir.resolve("resourcepacks"), profile, playerProfile, null, accessToken, clientHDir, assetHDir, jvmHDir);
this(clientDir, assetDir, javaVersion, clientDir.resolve("resourcepacks"), profile, playerProfile, null, accessToken, clientHDir, assetHDir, jvmHDir);
}
public ClientLauncherProcess(Path clientDir, Path assetDir,
ClientProfile profile, PlayerProfile playerProfile, String accessToken,
HashedDir clientHDir, HashedDir assetHDir, HashedDir jvmHDir) {
this(clientDir, assetDir, Paths.get(System.getProperty("java.home")), clientDir.resolve("resourcepacks"), profile, playerProfile, null, accessToken, clientHDir, assetHDir, jvmHDir);
this(clientDir, assetDir, JavaHelper.JavaVersion.getCurrentJavaVersion(), clientDir.resolve("resourcepacks"), profile, playerProfile, null, accessToken, clientHDir, assetHDir, jvmHDir);
}
public ClientLauncherProcess(Path clientDir, Path assetDir, Path javaDir, Path resourcePackDir,
public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersion javaVersion, Path resourcePackDir,
ClientProfile profile, PlayerProfile playerProfile, OptionalView view, String accessToken,
HashedDir clientHDir, HashedDir assetHDir, HashedDir jvmHDir) {
this.javaVersion = javaVersion;
this.workDir = clientDir.toAbsolutePath();
this.javaDir = javaDir;
this.executeFile = IOHelper.resolveJavaBin(this.javaDir);
this.executeFile = IOHelper.resolveJavaBin(this.javaVersion.jvmDir);
this.mainClass = ClientLauncherEntryPoint.class.getName();
this.params.clientDir = this.workDir.toString();
this.params.resourcePackDir = resourcePackDir.toAbsolutePath().toString();
this.params.assetDir = assetDir.toAbsolutePath().toString();
Path nativesPath = workDir.resolve("natives").resolve(JVMHelper.OS_TYPE.name).resolve(javaVersion.arch.name);
if(!Files.isDirectory(nativesPath)) {
nativesPath = workDir.resolve("natives");
}
this.params.nativesDir = nativesPath.toString();
this.params.profile = profile;
this.params.playerProfile = playerProfile;
this.params.accessToken = accessToken;
@ -87,16 +92,6 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, Path javaDir, Path r
if (view != null) {
this.params.actions = view.getEnabledActions();
}
try {
javaVersion = JavaHelper.JavaVersion.getByPath(javaDir);
} catch (IOException e) {
LogHelper.error(e);
javaVersion = null;
}
if (javaVersion == null) {
javaVersion = JavaHelper.JavaVersion.getCurrentJavaVersion();
}
this.bits = JVMHelper.JVM_BITS;
applyClientProfile();
}
@ -115,7 +110,7 @@ private void applyClientProfile() {
this.jvmArgs.addAll(((OptionalActionJvmArgs) a).args);
}
}
this.systemEnv.put("JAVA_HOME", javaDir.toString());
this.systemEnv.put("JAVA_HOME", javaVersion.jvmDir.toString());
Collections.addAll(this.systemClassPath, this.params.profile.getAlternativeClassPath());
if (params.ram > 0) {
this.jvmArgs.add("-Xmx" + params.ram + 'M');
@ -154,10 +149,19 @@ public void start(boolean pipeOutput) throws IOException, InterruptedException {
applyJava9Params(processArgs);
}
//ADD CLASSPATH
processArgs.add(JVMHelper.jvmProperty("java.library.path", this.params.nativesDir));
if (params.profile.getClassLoaderConfig() == ClientProfile.ClassLoaderConfig.AGENT) {
processArgs.add("-javaagent:".concat(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toAbsolutePath().toString()));
} else if (params.profile.getClassLoaderConfig() == ClientProfile.ClassLoaderConfig.SYSTEM_ARGS) {
systemClassPath.addAll(ClientLauncherEntryPoint.resolveClassPath(workDir, params.actions, params.profile).map(Path::toString).collect(Collectors.toList()));
systemClassPath.addAll(ClientLauncherEntryPoint.resolveClassPath(workDir, params.actions, params.profile)
.filter(x -> !params.profile.getModulePath().contains(workDir.relativize(x).toString()))
.map(Path::toString)
.collect(Collectors.toList()));
}
if(Launcher.getConfig().environment != LauncherConfig.LauncherEnvironment.PROD) {
processArgs.add(JVMHelper.jvmProperty(LogHelper.DEV_PROPERTY, String.valueOf(LogHelper.isDevEnabled())));
processArgs.add(JVMHelper.jvmProperty(LogHelper.DEBUG_PROPERTY, String.valueOf(LogHelper.isDebugEnabled())));
processArgs.add(JVMHelper.jvmProperty(LogHelper.STACKTRACE_PROPERTY, String.valueOf(LogHelper.isStacktraceEnabled())));
}
if (useLegacyJavaClassPathProperty) {
processArgs.add("-Djava.class.path=".concat(String.join(getPathSeparator(), systemClassPath)));
@ -176,7 +180,7 @@ public void start(boolean pipeOutput) throws IOException, InterruptedException {
LogHelper.debug("Commandline: %s", Arrays.toString(processArgs.toArray()));
ProcessBuilder processBuilder = new ProcessBuilder(processArgs);
EnvHelper.addEnv(processBuilder);
processBuilder.environment().put("JAVA_HOME", javaDir.toAbsolutePath().toString());
processBuilder.environment().put("JAVA_HOME", javaVersion.jvmDir.toAbsolutePath().toString());
processBuilder.environment().putAll(systemEnv);
processBuilder.directory(workDir.toFile());
processBuilder.inheritIO();
@ -256,6 +260,8 @@ public static class ClientParams {
public String resourcePackDir;
public String nativesDir;
// Client params
public PlayerProfile playerProfile;

View file

@ -7,6 +7,8 @@
import pro.gravit.launcher.modules.impl.SimpleModuleManager;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
public final class ClientModuleManager extends SimpleModuleManager {
public ClientModuleManager() {
@ -28,6 +30,10 @@ public LauncherModule loadModule(LauncherModule module) {
return super.loadModule(module);
}
public List<LauncherModule> getModules() {
return Collections.unmodifiableList(modules);
}
@Override
public final boolean verifyClassCheckResult(LauncherTrustManager.CheckClassResult result) {
return result.type == LauncherTrustManager.CheckClassResultType.SUCCESS;

View file

@ -0,0 +1,49 @@
package pro.gravit.launcher.console;
import pro.gravit.launcher.LauncherEngine;
import pro.gravit.launcher.LauncherTrustManager;
import pro.gravit.launcher.managers.ConsoleManager;
import pro.gravit.launcher.modules.LauncherModule;
import pro.gravit.launcher.modules.LauncherModuleInfo;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.helper.LogHelper;
import java.security.cert.X509Certificate;
import java.util.Arrays;
public class ModulesCommand extends Command {
@Override
public String getArgsDescription() {
return "[]";
}
@Override
public String getUsageDescription() {
return "show modules";
}
@Override
public void invoke(String... args) throws Exception {
for(LauncherModule module : LauncherEngine.modulesManager.getModules()) {
LauncherModuleInfo info = module.getModuleInfo();
LauncherTrustManager.CheckClassResult checkStatus = module.getCheckResult();
if(!ConsoleManager.isConsoleUnlock) {
LogHelper.info("[MODULE] %s v: %s", info.name, info.version.getVersionString());
} else {
LogHelper.info("[MODULE] %s v: %s p: %d deps: %s sig: %s", info.name, info.version.getVersionString(), info.priority, Arrays.toString(info.dependencies), checkStatus == null ? "null" : checkStatus.type);
printCheckStatusInfo(checkStatus);
}
}
}
private void printCheckStatusInfo(LauncherTrustManager.CheckClassResult checkStatus) {
if (checkStatus != null && checkStatus.endCertificate != null) {
X509Certificate cert = checkStatus.endCertificate;
LogHelper.info("[MODULE CERT] Module signer: %s", cert.getSubjectX500Principal().getName());
}
if (checkStatus != null && checkStatus.rootCertificate != null) {
X509Certificate cert = checkStatus.rootCertificate;
LogHelper.info("[MODULE CERT] Module signer CA: %s", cert.getSubjectX500Principal().getName());
}
}
}

View file

@ -26,7 +26,7 @@ public class DebugMain {
public static final AtomicBoolean IS_DEBUG = new AtomicBoolean(false);
public static String webSocketURL = System.getProperty("launcherdebug.websocket", "ws://localhost:9274/api");
public static String projectName = System.getProperty("launcherdebug.projectname", "Minecraft");
public static String unlockKey = System.getProperty("launcherdebug.unlockkey", "0000");
public static String unlockSecret = System.getProperty("launcherdebug.unlocksecret", "");
public static boolean offlineMode = Boolean.getBoolean("launcherdebug.offlinemode");
public static String[] moduleClasses = System.getProperty("launcherdebug.modules", "").split(",");
public static String[] moduleFiles = System.getProperty("launcherdebug.modulefiles", "").split(",");
@ -39,7 +39,7 @@ public static void main(String[] args) throws Throwable {
LogHelper.info("Launcher start in DEBUG mode (Only for developers)");
LogHelper.debug("Initialization LauncherConfig");
LauncherConfig config = new LauncherConfig(webSocketURL, new HashMap<>(), projectName, environment, new DebugLauncherTrustManager(DebugLauncherTrustManager.TrustDebugMode.TRUST_ALL));
config.oemUnlockKey = unlockKey;
config.unlockSecret = unlockSecret;
Launcher.setConfig(config);
Launcher.applyLauncherEnv(environment);
LauncherEngine.modulesManager = new ClientModuleManager();

View file

@ -2,7 +2,7 @@
import pro.gravit.launcher.client.ClientLauncherProcess;
public interface LauncherGuardInterface {
public interface LauncherGuard {
String getName();
void applyGuardParams(ClientLauncherProcess process);

View file

@ -1,8 +0,0 @@
package pro.gravit.launcher.guard;
public class LauncherGuardManager {
public static LauncherGuardInterface guard;
public static void initGuard(boolean clientInstance) {
}
}

View file

@ -2,7 +2,7 @@
import pro.gravit.launcher.client.ClientLauncherProcess;
public class LauncherNoGuard implements LauncherGuardInterface {
public class LauncherNoGuard implements LauncherGuard {
@Override
public String getName() {
return "noGuard";

View file

@ -8,7 +8,7 @@
import java.io.IOException;
public class LauncherWrapperGuard implements LauncherGuardInterface {
public class LauncherWrapperGuard implements LauncherGuard {
public LauncherWrapperGuard() {
try {

View file

@ -49,7 +49,7 @@ public static void registerCommands() {
}
public static boolean checkUnlockKey(String key) {
return key.equals(Launcher.getConfig().oemUnlockKey);
return key.equals(Launcher.getConfig().unlockSecret);
}
public static boolean unlock() {

View file

@ -1,107 +0,0 @@
package pro.gravit.launcher.patches;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import pro.gravit.utils.helper.SecurityHelper;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.ByteBuffer;
import java.util.Locale;
import java.util.Random;
import java.util.Vector;
public class FMLPatcher extends ClassLoader implements Opcodes {
public static final MethodType EXITMH = MethodType.methodType(void.class, int.class);
public static final String[] PACKAGES = new String[]{"cpw.mods.fml.", "net.minecraftforge.fml.", "cpw.mods."};
public static final Vector<MethodHandle> MHS = new Vector<>();
public static volatile FMLPatcher INSTANCE = null;
public FMLPatcher(final ClassLoader cl) {
super(cl);
}
public static void apply() {
INSTANCE = new FMLPatcher(null); // Never cause ClassFormatError (fuck forge 1.14!!!)
for (String s : PACKAGES) {
String rMethod = randomStr(16);
try {
MHS.add(MethodHandles.publicLookup().findStatic(INSTANCE.def(s + randomStr(16), rMethod), rMethod,
EXITMH));
} catch (NoSuchMethodException | IllegalAccessException e) {
// Simple ignore - other Forge
}
}
}
public static void exit(final int code) {
for (MethodHandle mh : MHS)
try {
mh.invoke(code);
} catch (Throwable ignored) {
}
}
private static byte[] gen(final String name, final String exName) { // "cpw/mods/fml/SafeExitJVMLegacy", "exit"
final ClassWriter classWriter = new ClassWriter(0);
MethodVisitor methodVisitor;
classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, name, null, "java/lang/Object", null);
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitCode();
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, exName, "(I)V", null, null);
methodVisitor.visitCode();
final Label label0 = new Label();
final Label label1 = new Label();
final Label label2 = new Label();
methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/Throwable");
methodVisitor.visitLabel(label0);
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;",
false);
methodVisitor.visitVarInsn(ILOAD, 0);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Runtime", "halt", "(I)V", false);
methodVisitor.visitLabel(label1);
final Label label3 = new Label();
methodVisitor.visitJumpInsn(GOTO, label3);
methodVisitor.visitLabel(label2);
methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
methodVisitor.visitVarInsn(ASTORE, 1);
methodVisitor.visitVarInsn(ILOAD, 0);
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "exit", "(I)V", false);
methodVisitor.visitLabel(label3);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
return classWriter.toByteArray();
}
public static String randomStr(final int lenght) {
String alphabet = "abcdefghijklmnopqrstuvwxyz";
alphabet += alphabet.toUpperCase(Locale.US);
final StringBuilder sb = new StringBuilder(lenght);
final Random random = SecurityHelper.newRandom();
for (int i = 0; i < lenght; i++)
sb.append(alphabet.charAt(random.nextInt(26)));
return sb.toString();
}
public Class<?> def(final String name, final String exName) {
return super.defineClass(name, ByteBuffer.wrap(gen(name.replace('.', '/'), exName)), null);
}
}

View file

@ -2,6 +2,7 @@
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherTrustManager;
import pro.gravit.launcher.client.ClientClassLoader;
import java.security.cert.X509Certificate;
@ -15,4 +16,12 @@ public static void checkCertificatesSuccess(X509Certificate[] certs) throws Exce
LauncherTrustManager trustManager = Launcher.getConfig().trustManager;
trustManager.checkCertificatesSuccess(certs, trustManager::stdCertificateChecker);
}
public static String findLibrary(ClassLoader classLoader, String library) {
if(classLoader instanceof ClientClassLoader) {
ClientClassLoader clientClassLoader = (ClientClassLoader) classLoader;
return clientClassLoader.findLibrary(library);
}
return null;
}
}

View file

@ -1,5 +1,6 @@
package pro.gravit.launcher.utils;
import pro.gravit.launcher.LauncherEngine;
import pro.gravit.launcher.hasher.FileNameMatcher;
import pro.gravit.launcher.hasher.HashedDir;
import pro.gravit.launcher.hasher.HashedEntry;
@ -46,7 +47,7 @@ public DirWatcher(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean dig
private static void handleError(Throwable e) {
LogHelper.error(e);
NativeJVMHalt.haltA(-123);
LauncherEngine.exitLauncher(-123);
}
private static Deque<String> toPath(Iterable<Path> path) {

View file

@ -0,0 +1,82 @@
package pro.gravit.launcher.utils;
import pro.gravit.launcher.AsyncDownloader;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherEngine;
import pro.gravit.launcher.LauncherInject;
import pro.gravit.launcher.events.request.LauncherRequestEvent;
import pro.gravit.launcher.request.update.LauncherRequest;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.SecurityHelper;
import javax.net.ssl.HttpsURLConnection;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class LauncherUpdater {
@LauncherInject("launcher.certificatePinning")
private static boolean isCertificatePinning;
public static void nothing() {
}
private static Path getLauncherPath() {
Path pathToCore = IOHelper.getCodeSource(IOHelper.class);
Path pathToApi = IOHelper.getCodeSource(LauncherRequest.class);
Path pathToSelf = IOHelper.getCodeSource(LauncherUpdater.class);
if(pathToCore.equals(pathToApi) && pathToCore.equals(pathToSelf)) {
return pathToCore;
} else {
throw new SecurityException("Found split-jar launcher");
}
}
public static Path prepareUpdate(URL url) throws Exception {
Path pathToLauncher = getLauncherPath();
Path tempFile = Files.createTempFile("launcher-update-", ".jar");
URLConnection connection = url.openConnection();
if (isCertificatePinning) {
HttpsURLConnection connection1 = (HttpsURLConnection) connection;
try {
connection1.setSSLSocketFactory(AsyncDownloader.makeSSLSocketFactory());
} catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | KeyManagementException e) {
throw new IOException(e);
}
}
try (InputStream in = connection.getInputStream()) {
IOHelper.transfer(in, tempFile);
}
if (Arrays.equals(SecurityHelper.digest(SecurityHelper.DigestAlgorithm.MD5, tempFile),
SecurityHelper.digest(SecurityHelper.DigestAlgorithm.MD5, pathToLauncher)))
throw new IOException("Invalid update (launcher needs update, but link has old launcher), check LaunchServer config...");
return tempFile;
}
public static void restart() {
List<String> args = new ArrayList<>(8);
args.add(IOHelper.resolveJavaBin(null).toString());
args.add("-jar");
args.add(IOHelper.getCodeSource(LauncherUpdater.class).toString());
ProcessBuilder builder = new ProcessBuilder(args.toArray(new String[0]));
builder.inheritIO();
try {
builder.start();
} catch (IOException e) {
LogHelper.error(e);
}
LauncherEngine.forceExit(0);
}
}

View file

@ -1,11 +1,11 @@
package pro.gravit.launcher.utils;
import pro.gravit.launcher.patches.FMLPatcher;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper;
import javax.swing.*;
import java.awt.event.WindowEvent;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
public final class NativeJVMHalt {
public final int haltCode;
@ -15,51 +15,24 @@ public NativeJVMHalt(int haltCode) {
System.out.println("JVM exit code " + haltCode);
}
public static void initFunc() {
}
public static void haltA(int code) {
Throwable[] th = new Throwable[3];
NativeJVMHalt halt = new NativeJVMHalt(code);
try {
JVMHelper.RUNTIME.exit(code);
} catch (Throwable exitExc) {
th[0] = exitExc;
try {
new WindowShutdown();
} catch (Throwable windowExc) {
th[1] = windowExc;
LogHelper.dev("Try invoke Shutdown.exit");
Class<?> clazz = Class.forName("java.lang.Shutdown", true, ClassLoader.getSystemClassLoader());
Method exitMethod = clazz.getDeclaredMethod("exit", int.class);
exitMethod.setAccessible(true);
exitMethod.invoke(null, code);
} catch (Throwable e) {
th[1] = e;
if(LogHelper.isDevEnabled()) {
LogHelper.error(e);
}
}
try {
FMLPatcher.exit(code);
} catch (Throwable fmlExc) {
th[2] = fmlExc;
}
for (Throwable t : th) {
if (t != null) LogHelper.error(t);
}
boolean a = halt.aaabBooleanC_D();
System.out.println(a);
halt.aaabbb38C_D();
}
public static boolean initFunc() {
return true;
}
public native void aaabbb38C_D();
@SuppressWarnings("null")
private boolean aaabBooleanC_D() {
return (boolean) (Boolean) null;
}
public static class WindowShutdown extends JFrame {
private static final long serialVersionUID = 6321323663070818367L;
public WindowShutdown() {
super();
super.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
super.processWindowEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
}
}
}

View file

@ -46,8 +46,8 @@ public final class LauncherConfig extends StreamObject {
public final String address;
@LauncherInject("runtimeconfig.secretKeyClient")
public String secretKeyClient;
@LauncherInject("runtimeconfig.oemUnlockKey")
public String oemUnlockKey;
@LauncherInject("runtimeconfig.unlockSecret")
public String unlockSecret;
@LauncherInject("launchercore.env")
public LauncherEnvironment environment;
@ -63,7 +63,6 @@ public LauncherConfig(HInput input) throws IOException, InvalidKeySpecException
projectName = null;
clientPort = -1;
secretKeyClient = null;
oemUnlockKey = null;
try {
trustManager = new LauncherTrustManager(secureConfigCertificates);
} catch (CertificateException e) {

View file

@ -0,0 +1,7 @@
package pro.gravit.launcher.api;
import java.security.interfaces.RSAPublicKey;
public class KeyService {
public static RSAPublicKey serverRsaPublicKey;
}

View file

@ -0,0 +1,35 @@
package pro.gravit.launcher.events.request;
import pro.gravit.launcher.events.RequestEvent;
import java.security.PrivateKey;
import java.security.PublicKey;
public class FetchClientProfileKeyRequestEvent extends RequestEvent {
public byte[] publicKey;
public byte[] privateKey;
public byte[] signature /* V2 */;
public long expiresAt;
public long refreshedAfter;
public FetchClientProfileKeyRequestEvent(byte[] publicKey, byte[] privateKey, byte[] signature, long expiresAt, long refreshedAfter) {
this.publicKey = publicKey;
this.privateKey = privateKey;
this.signature = signature;
this.expiresAt = expiresAt;
this.refreshedAfter = refreshedAfter;
}
public FetchClientProfileKeyRequestEvent(PublicKey publicKey, PrivateKey privateKey, byte[] signature, long expiresAt, long refreshedAfter) {
this.publicKey = publicKey.getEncoded();
this.privateKey = privateKey.getEncoded();
this.signature = signature;
this.expiresAt = expiresAt;
this.refreshedAfter = refreshedAfter;
}
@Override
public String getType() {
return "clientProfileKey";
}
}

View file

@ -0,0 +1,26 @@
package pro.gravit.launcher.events.request;
import pro.gravit.launcher.events.RequestEvent;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
public class GetPublicKeyRequestEvent extends RequestEvent {
public byte[] rsaPublicKey;
public byte[] ecdsaPublicKey;
public GetPublicKeyRequestEvent(byte[] rsaPublicKey, byte[] ecdsaPublicKey) {
this.rsaPublicKey = rsaPublicKey;
this.ecdsaPublicKey = ecdsaPublicKey;
}
public GetPublicKeyRequestEvent(RSAPublicKey rsaPublicKey, ECPublicKey ecdsaPublicKey) {
this.rsaPublicKey = rsaPublicKey.getEncoded();
this.ecdsaPublicKey = ecdsaPublicKey.getEncoded();
}
@Override
public String getType() {
return "getPublicKey";
}
}

View file

@ -80,8 +80,6 @@ public final class ClientProfile implements Comparable<ClientProfile> {
@LauncherNetworkAPI
private ProfileDefaultSettings settings = new ProfileDefaultSettings();
@LauncherNetworkAPI
private boolean updateFastCheck;
@LauncherNetworkAPI
private boolean limited;
// Client launcher
@LauncherNetworkAPI
@ -107,7 +105,7 @@ public ClientProfile() {
runtimeInClientConfig = RuntimeInClientConfig.NONE;
}
public ClientProfile(List<String> update, List<String> updateExclusions, List<String> updateShared, List<String> updateVerify, Set<OptionalFile> updateOptional, List<String> jvmArgs, List<String> classPath, List<String> modulePath, List<String> modules, List<String> altClassPath, List<String> clientArgs, List<String> compatClasses, Map<String, String> properties, List<ServerProfile> servers, SecurityManagerConfig securityManagerConfig, ClassLoaderConfig classLoaderConfig, SignedClientConfig signedClientConfig, RuntimeInClientConfig runtimeInClientConfig, String version, String assetIndex, String dir, String assetDir, int recommendJavaVersion, int minJavaVersion, int maxJavaVersion, boolean warnMissJavaVersion, ProfileDefaultSettings settings, int sortIndex, UUID uuid, String title, String info, boolean updateFastCheck, String mainClass) {
public ClientProfile(List<String> update, List<String> updateExclusions, List<String> updateShared, List<String> updateVerify, Set<OptionalFile> updateOptional, List<String> jvmArgs, List<String> classPath, List<String> modulePath, List<String> modules, List<String> altClassPath, List<String> clientArgs, List<String> compatClasses, Map<String, String> properties, List<ServerProfile> servers, SecurityManagerConfig securityManagerConfig, ClassLoaderConfig classLoaderConfig, SignedClientConfig signedClientConfig, RuntimeInClientConfig runtimeInClientConfig, String version, String assetIndex, String dir, String assetDir, int recommendJavaVersion, int minJavaVersion, int maxJavaVersion, boolean warnMissJavaVersion, ProfileDefaultSettings settings, int sortIndex, UUID uuid, String title, String info, String mainClass) {
this.update = update;
this.updateExclusions = updateExclusions;
this.updateShared = updateShared;
@ -139,7 +137,6 @@ public ClientProfile(List<String> update, List<String> updateExclusions, List<St
this.uuid = uuid;
this.title = title;
this.info = info;
this.updateFastCheck = updateFastCheck;
this.mainClass = mainClass;
}
@ -316,8 +313,9 @@ public void setVersion(Version version) {
this.version = version.name;
}
@Deprecated
public boolean isUpdateFastCheck() {
return updateFastCheck;
return true;
}
@Override
@ -484,7 +482,9 @@ public enum Version {
MC118("1.18", 757),
MC1181("1.18.1", 757),
MC1182("1.18.2", 758),
MC119("1.19", 759);
MC119("1.19", 759),
MC1191("1.19.1", 760),
MC1192("1.19.2", 760);
private static final Map<String, Version> VERSIONS;
static {

View file

@ -36,7 +36,6 @@ public class ClientProfileBuilder {
private UUID uuid;
private String title;
private String info;
private boolean updateFastCheck = true;
private String mainClass;
public ClientProfileBuilder setUpdate(List<String> update) {
@ -194,17 +193,12 @@ public ClientProfileBuilder setInfo(String info) {
return this;
}
public ClientProfileBuilder setUpdateFastCheck(boolean updateFastCheck) {
this.updateFastCheck = updateFastCheck;
return this;
}
public ClientProfileBuilder setMainClass(String mainClass) {
this.mainClass = mainClass;
return this;
}
public ClientProfile createClientProfile() {
return new ClientProfile(update, updateExclusions, updateShared, updateVerify, updateOptional, jvmArgs, classPath, modulePath, modules, altClassPath, clientArgs, compatClasses, properties, servers, securityManagerConfig, classLoaderConfig, signedClientConfig, runtimeInClientConfig, version, assetIndex, dir, assetDir, recommendJavaVersion, minJavaVersion, maxJavaVersion, warnMissJavaVersion, settings, sortIndex, uuid, title, info, updateFastCheck, mainClass);
return new ClientProfile(update, updateExclusions, updateShared, updateVerify, updateOptional, jvmArgs, classPath, modulePath, modules, altClassPath, clientArgs, compatClasses, properties, servers, securityManagerConfig, classLoaderConfig, signedClientConfig, runtimeInClientConfig, version, assetIndex, dir, assetDir, recommendJavaVersion, minJavaVersion, maxJavaVersion, warnMissJavaVersion, settings, sortIndex, uuid, title, info, mainClass);
}
}

View file

@ -0,0 +1,12 @@
package pro.gravit.launcher.profiles.optional.triggers;
import pro.gravit.launcher.profiles.optional.OptionalFile;
import pro.gravit.utils.helper.JVMHelper;
public class ArchTrigger extends OptionalTrigger {
public JVMHelper.ARCH arch;
@Override
protected boolean isTriggered(OptionalFile optional, OptionalTriggerContext context) {
return context.getJavaVersion().arch == arch;
}
}

View file

@ -13,6 +13,7 @@ public static void registerProviders() {
if (!isRegisteredProviders) {
providers.register("java", JavaTrigger.class);
providers.register("os", OSTrigger.class);
providers.register("arch", ArchTrigger.class);
isRegisteredProviders = true;
}
}

View file

@ -0,0 +1,14 @@
package pro.gravit.launcher.request.auth;
import pro.gravit.launcher.events.request.FetchClientProfileKeyRequestEvent;
import pro.gravit.launcher.request.Request;
public class FetchClientProfileKeyRequest extends Request<FetchClientProfileKeyRequestEvent> {
public FetchClientProfileKeyRequest() {
}
@Override
public String getType() {
return "clientProfileKey";
}
}

View file

@ -0,0 +1,14 @@
package pro.gravit.launcher.request.auth;
import pro.gravit.launcher.events.request.GetPublicKeyRequestEvent;
import pro.gravit.launcher.request.Request;
public class GetPublicKeyRequest extends Request<GetPublicKeyRequestEvent> {
public GetPublicKeyRequest() {
}
@Override
public String getType() {
return "getPublicKey";
}
}

View file

@ -23,7 +23,6 @@
public final class LauncherRequest extends Request<LauncherRequestEvent> implements WebSocketRequest {
public static final Path BINARY_PATH = IOHelper.getCodeSource(Launcher.class);
public static final Path C_BINARY_PATH = BINARY_PATH.getParent().resolve(IOHelper.getFileName(BINARY_PATH) + ".tmp");
public static final boolean EXE_BINARY = IOHelper.hasExtension(BINARY_PATH, "exe");
@LauncherNetworkAPI
public final String secureHash;
@ -46,53 +45,9 @@ public LauncherRequest() {
secureSalt = Launcher.getConfig().secureCheckSalt;
}
public static void update(LauncherRequestEvent result) throws IOException {
List<String> args = new ArrayList<>(8);
args.add(IOHelper.resolveJavaBin(null).toString());
if (LogHelper.isDebugEnabled())
args.add(JVMHelper.jvmProperty(LogHelper.DEBUG_PROPERTY, Boolean.toString(LogHelper.isDebugEnabled())));
args.add("-jar");
args.add(BINARY_PATH.toString());
ProcessBuilder builder = new ProcessBuilder(args.toArray(new String[0]));
builder.inheritIO();
// Rewrite and start new instance
if (result.binary != null)
IOHelper.write(BINARY_PATH, result.binary);
else {
/*URLConnection connection = IOHelper.newConnection(new URL(result.url));
connection.setDoOutput(true);
connection.connect();
try (OutputStream stream = connection.getOutputStream()) {
IOHelper.transfer(BINARY_PATH, stream);
}*/
try {
Files.deleteIfExists(C_BINARY_PATH);
URL url = new URL(result.url);
URLConnection connection = url.openConnection();
try (InputStream in = connection.getInputStream()) {
IOHelper.transfer(in, C_BINARY_PATH);
}
try (InputStream in = IOHelper.newInput(C_BINARY_PATH)) {
IOHelper.transfer(in, BINARY_PATH);
}
Files.deleteIfExists(C_BINARY_PATH);
} catch (Throwable e) {
LogHelper.error(e);
}
}
builder.start();
// Kill current instance
JVMHelper.RUNTIME.exit(255);
throw new AssertionError("Why Launcher wasn't restarted?!");
}
@Override
public LauncherRequestEvent requestDo(RequestService service) throws Exception {
LauncherRequestEvent result = super.request(service);
if (result.needUpdate) update(result);
return result;
return super.request(service);
}
@Override

View file

@ -108,6 +108,8 @@ public void registerResults() {
results.register("refreshToken", RefreshTokenRequestEvent.class);
results.register("restore", RestoreRequestEvent.class);
results.register("additionalData", AdditionalDataRequestEvent.class);
results.register("clientProfileKey", FetchClientProfileKeyRequestEvent.class);
results.register("getPublicKey", GetPublicKeyRequestEvent.class);
resultsRegistered = true;
}
}

View file

@ -60,10 +60,10 @@ protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Except
final WebSocketFrame frame = (WebSocketFrame) msg;
if (frame instanceof TextWebSocketFrame) {
final TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
clientJSONPoint.onMessage(textFrame.text());
if (LogHelper.isDevEnabled()) {
LogHelper.dev("Message: %s", textFrame.text());
}
clientJSONPoint.onMessage(textFrame.text());
// uncomment to print request
// logger.info(textFrame.text());
} else if ((frame instanceof PingWebSocketFrame)) {

View file

@ -2,21 +2,34 @@
import pro.gravit.utils.helper.LogHelper;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.*;
public class LauncherTrustManager {
private final X509Certificate[] trustSigners;
private final List<X509Certificate> trustCache = new ArrayList<>();
@LauncherInject("launcher.certificatePinning")
private static boolean isCertificatePinning;
public LauncherTrustManager(X509Certificate[] trustSigners) {
this.trustSigners = trustSigners;
if (requireCustomTrustStore()) {
injectCertificates();
}
}
public LauncherTrustManager(List<byte[]> encodedCertificate) throws CertificateException {
@ -29,6 +42,95 @@ public LauncherTrustManager(List<byte[]> encodedCertificate) throws CertificateE
return null;
}
}).toArray(X509Certificate[]::new);
if (requireCustomTrustStore()) {
injectCertificates();
}
}
private boolean requireCustomTrustStore() {
return trustSigners != null && trustSigners.length != 0 && isCertificatePinning;
}
private void injectCertificates() {
try {
// Получение списка всех существующих и действительных сертификатов из стандартного KeyStore JVM
final Map<String, Certificate> jdkTrustStore = getDefaultKeyStore();
// Создание нового KeyStore с дополнительными сертификатами.
final KeyStore mergedTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
mergedTrustStore.load(null, new char[0]);
// добавление дополнительных сертификатов в новый KeyStore
Arrays.stream(trustSigners).forEach(cert -> setCertificateEntry(mergedTrustStore, "injected-certificate" + UUID.randomUUID(), cert));
// добавление стандартных сертификатов в новый KeyStore
jdkTrustStore.keySet().forEach(key -> setCertificateEntry(mergedTrustStore, key, jdkTrustStore.get(key)));
// Инициализация контекста. В случае неудачи допустимо прерывание процесса, но сертификаты добавлены не будут
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(mergedTrustStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
// Установка контекста по умолчанию
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
LogHelper.info("Successfully injected certificates to truststore");
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | IOException | CertificateException e) {
LogHelper.error("Error while modify existing keystore");
}
}
/**
* Получение набора стандартных сертификатов, вшитых в текущую сессию JVM
*/
private static Map<String, Certificate> getDefaultKeyStore() {
// init existing keystore
final Map<String, Certificate> jdkTrustStore = new HashMap<>();
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
Path ksPath = Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts");
keyStore.load(Files.newInputStream(ksPath), "changeit".toCharArray());
// getting all JDK/JRE certificates
extractAllCertsAndPutInMap(keyStore, jdkTrustStore);
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
LogHelper.warning("Error while loading existing keystore");
}
return jdkTrustStore;
}
/**
* Извлечение существующих сертификатов из стандартного KeyStore текущий сессии JVM. Процесс не должен прерываться в случае неудачи
*/
private static void extractAllCertsAndPutInMap(KeyStore keyStore, Map<String, Certificate> placeToExport) {
try {
Collections.list(keyStore.aliases()).forEach(key -> extractCertAndPutInMap(keyStore, key, placeToExport));
} catch (KeyStoreException e) {
LogHelper.error("Error during extraction certificates from default keystore");
}
}
/**
* Добавление сертификата с именем name в KeyStore. Не должно прерывать общий процесс инъекции сертификатов, в случае неудачи.
*/
private static void setCertificateEntry(KeyStore keyStore, String name, Certificate cert) {
try {
keyStore.setCertificateEntry(name, cert);
} catch (KeyStoreException e) {
LogHelper.warning("Something went wrong while adding certificate " + name);
}
}
/**
* Извлечение существующего сертификата из стандартного KeyStore текущий сессии JVM. Процесс не должен прерываться в случае неудачи
*/
private static void extractCertAndPutInMap(KeyStore keyStoreFromExtract, String key, Map<String, Certificate> placeToExtract) {
try {
if (keyStoreFromExtract.containsAlias(key)) {
placeToExtract.put(key, keyStoreFromExtract.getCertificate(key));
}
} catch (KeyStoreException e) {
LogHelper.warning("Error while extracting certificate " + key);
}
}
public CheckClassResult checkCertificates(X509Certificate[] certs, CertificateChecker checker) {

View file

@ -15,7 +15,7 @@ public class GsonManager {
public void initGson() {
gsonBuilder = CommonHelper.newBuilder();
configGsonBuilder = CommonHelper.newBuilder();
configGsonBuilder.setPrettyPrinting();
configGsonBuilder.setPrettyPrinting().disableHtmlEscaping();
registerAdapters(gsonBuilder);
registerAdapters(configGsonBuilder);
preConfigGson(configGsonBuilder);

View file

@ -5,8 +5,8 @@
public final class Version implements Comparable<Version> {
public static final int MAJOR = 5;
public static final int MINOR = 2;
public static final int PATCH = 13;
public static final int MINOR = 3;
public static final int PATCH = 0;
public static final int BUILD = 1;
public static final Version.Type RELEASE = Type.STABLE;
public final int major;

View file

@ -5,7 +5,7 @@ public final class CommandException extends Exception {
public CommandException(String message) {
super(message);
super(message, null, false, false);
}

View file

@ -16,27 +16,9 @@
public final class CommonHelper {
private static ScriptEngineFactory nashornFactory;
static {
try {
ScriptEngineManager scriptManager = new ScriptEngineManager();
nashornFactory = getEngineFactories(scriptManager);
} catch (Throwable e) {
nashornFactory = null;
}
}
private CommonHelper() {
}
private static ScriptEngineFactory getEngineFactories(ScriptEngineManager manager) {
// Метод похож на костыль но таковым не является, ибо единоразовое получение фактории быстрее, чем её переполучение на ходу.
for (ScriptEngineFactory fact : manager.getEngineFactories())
if (fact.getNames().contains("nashorn") || fact.getNames().contains("Nashorn")) return fact;
return null;
}
public static String low(String s) {
return s.toLowerCase(Locale.US);
}
@ -57,11 +39,9 @@ public static String multiReplace(Pattern[] pattern, String from, String replace
return tmp != null ? tmp : from;
}
@Deprecated
public static ScriptEngine newScriptEngine() {
if (nashornFactory == null) {
throw new UnsupportedOperationException("ScriptEngine not supported");
}
return nashornFactory.getScriptEngine();
throw new UnsupportedOperationException("ScriptEngine not supported");
}
public static Thread newThread(String name, boolean daemon, Runnable runnable) {

View file

@ -1,66 +0,0 @@
package pro.gravit.utils.helper;
import java.util.Objects;
import java.util.function.LongSupplier;
public final class CryptoHelper {
private CryptoHelper() {
}
public static byte[] encode(byte[] txt, String pKey) {
Objects.requireNonNull(txt);
Objects.requireNonNull(pKey);
byte[] key = SecurityHelper.fromHex(pKey);
byte[] res = new byte[txt.length];
for (int i = 0; i < txt.length; i++)
res[i] = (byte) (txt[i] ^ key[i % key.length]);
return res;
}
public static byte[] decode(byte[] pText, String pKey) {
Objects.requireNonNull(pText);
Objects.requireNonNull(pKey);
byte[] res = new byte[pText.length];
byte[] key = SecurityHelper.fromHex(pKey);
for (int i = 0; i < pText.length; i++)
res[i] = (byte) (pText[i] ^ key[i % key.length]);
return res;
}
public static void encodeOrig(byte[] txt, String pKey) {
Objects.requireNonNull(txt);
Objects.requireNonNull(pKey);
byte[] key = SecurityHelper.fromHex(pKey);
for (int i = 0; i < txt.length; i++)
txt[i] = (byte) (txt[i] ^ key[i % key.length]);
}
public static void decodeOrig(byte[] pText, String pKey) {
Objects.requireNonNull(pText);
Objects.requireNonNull(pKey);
byte[] key = SecurityHelper.fromHex(pKey);
for (int i = 0; i < pText.length; i++)
pText[i] = (byte) (pText[i] ^ key[i % key.length]);
}
public static String randomToken(int depth) {
VerifyHelper.verifyInt(depth, VerifyHelper.POSITIVE, "Depth must be positive");
return SecurityHelper.toHex(SecurityHelper.randomBytes(SecurityHelper.TOKEN_LENGTH * depth));
}
public static class StaticRandom implements LongSupplier {
private volatile long rnd;
public StaticRandom(long rnd) {
this.rnd = rnd;
}
@Override
public long getAsLong() {
this.rnd ^= (this.rnd << 21);
this.rnd ^= (this.rnd >>> 35);
this.rnd ^= (this.rnd << 4);
return this.rnd;
}
}
}

View file

@ -22,7 +22,11 @@ public final class JVMHelper {
public static final OS OS_TYPE = OS.byName(OPERATING_SYSTEM_MXBEAN.getName());
// System properties
public static final String OS_VERSION = OPERATING_SYSTEM_MXBEAN.getVersion();
@Deprecated
public static final int OS_BITS = getCorrectOSArch();
public static final ARCH ARCH_TYPE = getArch(System.getProperty("os.arch"));
public static final int JVM_BITS = Integer.parseInt(System.getProperty("sun.arch.data.model"));
public static final SecurityManager SECURITY_MANAGER = System.getSecurityManager();
// Public static fields
@ -42,6 +46,24 @@ public final class JVMHelper {
private JVMHelper() {
}
public enum ARCH {
X86("x86"), X86_64("x86-64"), ARM64("arm64"), ARM32("arm32");
public final String name;
ARCH(String name) {
this.name = name;
}
}
public static ARCH getArch(String arch) {
if(arch.equals("amd64") || arch.equals("x86-64") || arch.equals("x86_64")) return ARCH.X86_64;
if(arch.equals("i386") || arch.equals("i686") || arch.equals("x86")) return ARCH.X86;
if(arch.startsWith("armv8") || arch.startsWith("aarch64")) return ARCH.ARM64;
if(arch.startsWith("arm") || arch.startsWith("aarch32")) return ARCH.ARM32;
throw new InternalError(String.format("Unsupported arch '%s'", arch));
}
public static int getVersion() {
String version = System.getProperty("java.version");
if (version.startsWith("1.")) {
@ -132,6 +154,7 @@ public static void checkStackTrace(Class<?> mainClass) {
}
}
@Deprecated
private static int getCorrectOSArch() {
// As always, mustdie must die
if (OS_TYPE == OS.MUSTDIE)
@ -147,6 +170,7 @@ public static String getEnvPropertyCaseSensitive(String name) {
}
@Deprecated
public static boolean isJVMMatchesSystemArch() {
return JVM_BITS == OS_BITS;
}
@ -178,10 +202,6 @@ public static void verifySystemProperties(Class<?> mainClass, boolean requireSys
// Verify system and java architecture
LogHelper.debug("Verifying JVM architecture");
if (!isJVMMatchesSystemArch()) {
LogHelper.warning("Java and OS architecture mismatch");
LogHelper.warning("It's recommended to download %d-bit JRE", OS_BITS);
}
}
public enum OS {

View file

@ -185,14 +185,14 @@ public static class JavaVersion {
public final Path jvmDir;
public final int version;
public final int build;
public final int bitness;
public final JVMHelper.ARCH arch;
public boolean enabledJavaFX;
public JavaVersion(Path jvmDir, int version) {
this.jvmDir = jvmDir;
this.version = version;
this.build = 0;
this.bitness = JVMHelper.OS_BITS;
this.arch = JVMHelper.ARCH_TYPE;
this.enabledJavaFX = true;
}
@ -200,20 +200,20 @@ public JavaVersion(Path jvmDir, int version, int build, boolean enabledJavaFX) {
this.jvmDir = jvmDir;
this.version = version;
this.build = build;
this.bitness = JVMHelper.OS_BITS;
this.arch = JVMHelper.ARCH_TYPE;
this.enabledJavaFX = enabledJavaFX;
}
public JavaVersion(Path jvmDir, int version, int build, int bitness, boolean enabledJavaFX) {
public JavaVersion(Path jvmDir, int version, int build, JVMHelper.ARCH arch, boolean enabledJavaFX) {
this.jvmDir = jvmDir;
this.version = version;
this.build = build;
this.bitness = bitness;
this.arch = arch;
this.enabledJavaFX = enabledJavaFX;
}
public static JavaVersion getCurrentJavaVersion() {
return new JavaVersion(Paths.get(System.getProperty("java.home")), JVMHelper.getVersion(), JVMHelper.JVM_BUILD, JVMHelper.JVM_BITS, isCurrentJavaSupportJavaFX());
return new JavaVersion(Paths.get(System.getProperty("java.home")), JVMHelper.getVersion(), JVMHelper.JVM_BUILD, JVMHelper.ARCH_TYPE, isCurrentJavaSupportJavaFX());
}
private static boolean isCurrentJavaSupportJavaFX() {
@ -238,21 +238,20 @@ public static JavaVersion getByPath(Path jvmDir) throws IOException {
}
Path releaseFile = jvmDir.resolve("release");
JavaVersionAndBuild versionAndBuild;
int bitness = JVMHelper.OS_BITS;
JVMHelper.ARCH arch = JVMHelper.ARCH_TYPE;
if (IOHelper.isFile(releaseFile)) {
Properties properties = new Properties();
properties.load(IOHelper.newReader(releaseFile));
versionAndBuild = getJavaVersion(properties.getProperty("JAVA_VERSION").replaceAll("\"", ""));
String arch = properties.getProperty("JAVA_VERSION").replaceAll("\"", "");
if (arch.contains("x86_64")) {
bitness = 64;
} else if (arch.contains("x86") || arch.contains("x32")) {
bitness = 32;
try {
arch = JVMHelper.getArch(properties.getProperty("OS_ARCH").replaceAll("\"", ""));
} catch (Throwable ignored) {
arch = null;
}
} else {
versionAndBuild = new JavaVersionAndBuild(isExistExtJavaLibrary(jvmDir, "jfxrt") ? 8 : 9, 0);
}
JavaVersion resultJavaVersion = new JavaVersion(jvmDir, versionAndBuild.version, versionAndBuild.build, bitness, false);
JavaVersion resultJavaVersion = new JavaVersion(jvmDir, versionAndBuild.version, versionAndBuild.build, arch, false);
if (versionAndBuild.version <= 8) {
resultJavaVersion.enabledJavaFX = isExistExtJavaLibrary(jvmDir, "jfxrt");
} else {

View file

@ -387,6 +387,16 @@ public static byte[] sign(byte[] bytes, ECPrivateKey privateKey) {
}
}
public static byte[] sign(byte[] bytes, RSAPrivateKey privateKey) {
Signature signature = newRSASignSignature(privateKey);
try {
signature.update(bytes);
return signature.sign();
} catch (SignatureException e) {
throw new InternalError(e);
}
}
public static String toHex(byte[] bytes) {
if (bytes == null) {

View file

@ -22,7 +22,12 @@ public final class JVMHelper {
public static final OS OS_TYPE = OS.byName(OPERATING_SYSTEM_MXBEAN.getName());
// System properties
public static final String OS_VERSION = OPERATING_SYSTEM_MXBEAN.getVersion();
@Deprecated
public static final int OS_BITS = getCorrectOSArch();
public static final ARCH ARCH_TYPE = getArch(System.getProperty("os.arch"));
public static final int JVM_BITS = Integer.parseInt(System.getProperty("sun.arch.data.model"));
// Public static fields
public static final Runtime RUNTIME = Runtime.getRuntime();
@ -41,6 +46,24 @@ public final class JVMHelper {
private JVMHelper() {
}
public enum ARCH {
X86("x86"), X86_64("x86-64"), ARM64("arm64"), ARM32("arm32");
public final String name;
ARCH(String name) {
this.name = name;
}
}
public static ARCH getArch(String arch) {
if(arch.equals("amd64") || arch.equals("x86-64") || arch.equals("x86_64")) return ARCH.X86_64;
if(arch.equals("i386") || arch.equals("i686") || arch.equals("x86")) return ARCH.X86;
if(arch.startsWith("armv8") || arch.startsWith("aarch64")) return ARCH.ARM64;
if(arch.startsWith("arm") || arch.startsWith("aarch32")) return ARCH.ARM32;
throw new InternalError(String.format("Unsupported arch '%s'", arch));
}
public static int getVersion() {
//System.out.println("[DEBUG] JVMHelper 11 version");
return Runtime.version().feature();
@ -108,6 +131,7 @@ public static void checkStackTrace(Class<?> mainClass) {
}
}
@Deprecated
private static int getCorrectOSArch() {
// As always, mustdie must die
if (OS_TYPE == OS.MUSTDIE)
@ -123,6 +147,7 @@ public static String getEnvPropertyCaseSensitive(String name) {
}
@Deprecated
public static boolean isJVMMatchesSystemArch() {
return JVM_BITS == OS_BITS;
}
@ -154,10 +179,6 @@ public static void verifySystemProperties(Class<?> mainClass, boolean requireSys
// Verify system and java architecture
LogHelper.debug("Verifying JVM architecture");
if (!isJVMMatchesSystemArch()) {
LogHelper.warning("Java and OS architecture mismatch");
LogHelper.warning("It's recommended to download %d-bit JRE", OS_BITS);
}
}
public enum OS {

View file

@ -3,6 +3,7 @@
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherConfig;
import pro.gravit.launcher.api.KeyService;
import pro.gravit.launcher.config.JsonConfigurable;
import pro.gravit.launcher.events.request.AuthRequestEvent;
import pro.gravit.launcher.events.request.ProfilesRequestEvent;
@ -24,6 +25,7 @@
import pro.gravit.utils.PublicURLClassLoader;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.lang.reflect.Type;
import java.nio.file.Path;
@ -134,6 +136,9 @@ public void run(String... args) throws Throwable {
restore();
getProfiles();
}
if(config.encodedServerRsaPublicKey != null) {
KeyService.serverRsaPublicKey = SecurityHelper.toPublicRSAKey(config.encodedServerRsaPublicKey);
}
String classname = (config.mainclass == null || config.mainclass.isEmpty()) ? args[0] : config.mainclass;
if (classname.length() == 0) {
LogHelper.error("MainClass not found. Please set MainClass for ServerWrapper.json or first commandline argument");
@ -231,6 +236,10 @@ public static final class Config {
public Map<String, String> extendedTokens;
public LauncherConfig.LauncherEnvironment env;
public ModuleConf moduleConf = new ModuleConf();
public byte[] encodedServerRsaPublicKey;
public byte[] encodedServerEcPublicKey;
}
public static final class ModuleConf {

View file

@ -1,7 +1,9 @@
package pro.gravit.launcher.server.setup;
import pro.gravit.launcher.events.request.GetPublicKeyRequestEvent;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.auth.GetPublicKeyRequest;
import pro.gravit.launcher.request.websockets.StdWebSocketService;
import pro.gravit.launcher.server.ServerWrapper;
import pro.gravit.utils.PublicURLClassLoader;
@ -79,6 +81,9 @@ public void run() throws Exception {
try {
wrapper.restore();
wrapper.getProfiles();
GetPublicKeyRequestEvent publicKeyRequestEvent = new GetPublicKeyRequest().request();
wrapper.config.encodedServerRsaPublicKey = publicKeyRequestEvent.rsaPublicKey;
wrapper.config.encodedServerEcPublicKey = publicKeyRequestEvent.ecdsaPublicKey;
break;
} catch (Throwable e) {
LogHelper.error(e);

View file

@ -5,7 +5,7 @@
id 'org.openjfx.javafxplugin' version '0.0.10' apply false
}
group = 'pro.gravit.launcher'
version = '5.2.13'
version = '5.3.0'
apply from: 'props.gradle'

Binary file not shown.

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

6
gradlew vendored
View file

@ -205,6 +205,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

14
gradlew.bat vendored
View file

@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

@ -1 +1 @@
Subproject commit d3722bf136b8c8c8da3dea879d5d2015e84b0f8c
Subproject commit eb3145d3e75ff3557fe9f9fc87dd5ea0737ff3a2