Compare commits

..

47 commits

Author SHA1 Message Date
Gravita
f1559183a1
[FIX} New API bug fixes 2024-11-08 15:33:53 +07:00
Gravita
c90a13af71
Merge with dev 2024-11-08 07:37:17 +07:00
Gravita
912caa6b8a
[FIX] Add more logging to timestamp failed error 2024-11-05 08:52:59 +07:00
Gravita
926094076c
[FIX] Fix NPE with nativePath PR #719 2024-11-05 07:08:31 +07:00
Gravita
a1af61a599
[ANY] Update modules 2024-10-22 17:14:36 +07:00
Gravita
81b16cb54e
[ANY] Update gradle 2024-10-15 18:53:03 +07:00
Gravita
a486f21fa2
[ANY] Update dependencies 2024-10-15 18:48:20 +07:00
Gravita
86ea247f07
Merge tag 'v5.6.8' into dev
v5.6.8 stable
2024-10-13 19:19:00 +07:00
Gravita
d26b179006
Merge branch 'release/5.6.8' 2024-10-13 19:18:50 +07:00
Gravita
c4d1251429
[ANY] 5.6.8 stable 2024-10-13 19:05:35 +07:00
Gravita
f16f5fbc6d
[FIX] NPE: User not found and permissions enabled 2024-10-06 22:59:49 +07:00
Gravita
abe904d73c
[FEATURE] ExtendedCheckServer support in SQLAuthCoreProvider 2024-10-02 20:50:36 +07:00
Gravita
eaf685897f
Merge tag 'v5.6.7' into dev
5.6.7 stable release
2024-09-28 20:31:33 +07:00
Gravita
c8934d887a
Merge branch 'release/5.6.7' 2024-09-28 20:31:23 +07:00
Gravita
070a5d9b69
[ANY] 5.6.7 release 2024-09-28 20:31:10 +07:00
Gravita
cc2bce4300
[FIX] initializeAtStart thread safety 2024-09-28 02:29:19 +07:00
Gravita
c7f4d8ac49
[FEATURE] Support initializeAtStart 2024-09-28 02:27:42 +07:00
Gravita
7dcb08fdaf
[FEATURE] Support HWID in SQLCoreProvider 2024-09-28 01:47:55 +07:00
Gravita
9d870849a1
[ANY] Update dependencies 2024-09-18 16:36:15 +07:00
microwin7
dda3ebc7b4 [FIX] RejectedExecutionException after cancelled Download task 2024-08-23 22:02:13 +03:00
Gravita
537623afaf
Merge tag 'v5.6.6' into dev
5.6.6 stable
2024-08-18 23:57:14 +07:00
Gravita
0cff6e247a
Merge branch 'release/5.6.6' 2024-08-18 23:57:08 +07:00
Gravita
b85075c559
[ANY] 5.6.6 Stable 2024-08-18 23:57:00 +07:00
Gravita
3e654f4d79
[FEATURE] Add support opens, exports, reads to SYSTEM_CLASS_LOADER. Remove AGENT 2024-08-18 23:10:58 +07:00
Gravita
46f1f7b69e
[FIX] Symlink in launcher-pack 2024-08-18 19:18:53 +07:00
microwin7
981f2ac3dd [ANY] Update modules and update version oshi 2024-08-18 01:00:08 +03:00
microwin7
8509cbb6b5 [ANY] Update modules 2024-08-14 00:13:45 +03:00
microwin7
1119625d12 [ANY] Update modules 2024-08-13 05:44:32 +03:00
microwin7
9a69426547 [ANY] Update modules 2024-08-12 07:03:17 +03:00
microwin7
31cbfe2919 [ANY][FEATURE] Update modules. Removed mainClass for Cleanroom
In MakeProfileHelper
2024-08-12 06:01:08 +03:00
microwin7
27ebadcd19 [ANY] Update modules 2024-08-12 02:00:49 +03:00
Antoni
b3349044b5
[ANY] Update patch Version and Type release 2024-08-07 01:27:18 +03:00
Antoni
c43edeb982
[FIX] UpdatesProvider register 2024-08-06 03:25:02 +03:00
Gravita
1ff58099bd
[FIX] UpdatesProvider register 2024-08-06 06:19:12 +07:00
Gravita
9fba637f83
[FIX] Launcher update token 2024-08-05 05:36:53 +07:00
Gravita
ca70ee78d1
[FIX] Launcher update 2024-08-05 05:34:45 +07:00
Gravita
5e116a81e5
[ANY] Update modules 2024-08-05 03:25:05 +07:00
Gravita
b0f799d194
[FIX] UpdatesProvider init 2024-08-05 03:24:42 +07:00
Gravita
63f9f8e21d
[FEATURE] UpdatesProvider Part 2 2024-07-31 00:36:04 +07:00
Gravita
b8ccbc5e48
[FEATURE] UpdatesProvider Part 1 2024-07-31 00:15:08 +07:00
Gravita
a687c5afd8
[ANY] Update gradle 2024-07-30 23:20:35 +07:00
Gravita
3969d81db7
[ANY] Update modules 2024-07-30 23:06:54 +07:00
Gravita
a30d0624a1
[ANY] Update modules 2024-07-30 21:05:37 +07:00
Gravita
d5abe0d411
[ANY] Update modules 2024-07-30 17:34:20 +07:00
Gravita
1e7a856a99
[FIX] Profile duplicate 2024-07-30 15:43:22 +07:00
Gravita
e6f5b585a7
[ANY] Update modules 2024-07-30 15:33:46 +07:00
Gravita
2ed4abf9b0
[FIX] Usage profilesProvider 2024-07-26 01:27:01 +07:00
45 changed files with 867 additions and 257 deletions

View file

@ -70,6 +70,7 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
/**
* The path to the folder with updates/webroot
*/
@Deprecated
public final Path updatesDir;
// Constant paths
@ -359,9 +360,7 @@ public void run() {
syncProfilesDir();
// Sync updates dir
if (!IOHelper.isDir(updatesDir))
Files.createDirectory(updatesDir);
updatesManager.readUpdatesDir();
config.updatesProvider.syncInitially();
modulesManager.invokeEvent(new LaunchServerProfilesSyncEvent(this));

View file

@ -16,6 +16,7 @@
import pro.gravit.launchserver.auth.profiles.ProfileProvider;
import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.texture.TextureProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.components.Component;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
@ -180,6 +181,7 @@ public static void registerAll() {
OptionalTrigger.registerProviders();
MixProvider.registerProviders();
ProfileProvider.registerProviders();
UpdatesProvider.registerProviders();
}
private static void printExperimentalBranch() {

View file

@ -9,15 +9,22 @@
import java.util.function.Consumer;
public class HikariSQLSourceConfig implements SQLSourceConfig {
private transient HikariDataSource dataSource;
private transient volatile HikariDataSource dataSource;
private String dsClass;
private Properties dsProps;
private String driverClass;
private String jdbcUrl;
private String username;
private String password;
private boolean initializeAtStart;
public void init() {
if(initializeAtStart) {
initializeConnection();
}
}
private void initializeConnection() {
if (dataSource != null) {
return;
}
@ -34,6 +41,11 @@ public void init() {
@Override
public Connection getConnection() throws SQLException {
if(dataSource == null && !initializeAtStart) {
synchronized (this) {
initializeConnection();
}
}
return dataSource.getConnection();
}

View file

@ -295,7 +295,9 @@ private SQLUser queryUser(String sql, String value) throws SQLException {
user = constructUser(set);
}
}
user.permissions = requestPermissions(user.uuid.toString());
if(user != null) {
user.permissions = requestPermissions(user.uuid.toString());
}
return user;
}

View file

@ -44,6 +44,7 @@ public SQLSourceConfig getSQLConfig() {
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
super.init(server, pair);
logger.warn("Method 'mysql' deprecated and may be removed in future release. Please use new 'sql' method: https://gravitlauncher.com/auth");
String userInfoCols = makeUserCols();
String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey";
if (sqlFindHardwareByPublicKey == null)

View file

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

View file

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

View file

@ -45,8 +45,7 @@ public void addProfile(ClientProfile profile) throws IOException {
}
}
if(target == null) {
target = IOHelper.resolveIncremental(profilesDirPath,
profile.getTitle(), "json");
target = profilesDirPath.resolve(profile.getTitle()+".json");
oldProfile = profilesMap.get(target);
if(oldProfile != null && !oldProfile.getUUID().equals(profile.getUUID())) {
throw new FileAlreadyExistsException(target.toString());

View file

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

View file

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

View file

@ -14,11 +14,11 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class BinaryPipeline {
public abstract class BinaryPipeline {
public final List<LauncherBuildTask> tasks = new ArrayList<>();
public final Path buildDir;
public final String nameFormat;
private transient final Logger logger = LogManager.getLogger();
protected transient final Logger logger = LogManager.getLogger();
public BinaryPipeline(Path buildDir, String nameFormat) {
this.buildDir = buildDir;
@ -80,27 +80,6 @@ public Optional<LauncherBuildTask> getTaskBefore(Predicate<LauncherBuildTask> pr
return Optional.empty();
}
public void build(Path target, boolean deleteTempFiles) throws IOException {
logger.info("Building launcher binary file");
Path thisPath = null;
long time_start = System.currentTimeMillis();
long time_this = time_start;
for (LauncherBuildTask task : tasks) {
logger.info("Task {}", task.getName());
Path oldPath = thisPath;
thisPath = task.process(oldPath);
long time_task_end = System.currentTimeMillis();
long time_task = time_task_end - time_this;
time_this = time_task_end;
logger.info("Task {} processed from {} millis", task.getName(), time_task);
}
long time_end = System.currentTimeMillis();
if (deleteTempFiles) IOHelper.move(thisPath, target);
else IOHelper.copy(thisPath, target);
IOHelper.deleteDir(buildDir, false);
logger.info("Build successful from {} millis", time_end - time_start);
}
public String nextName(String taskName) {
return nameFormat.formatted(taskName);
}

View file

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

View file

@ -7,10 +7,7 @@
import pro.gravit.utils.helper.UnpackHelper;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.stream.Collectors;
@ -40,7 +37,7 @@ public Path process(Path inputFile) throws IOException {
if(Files.isDirectory(server.launcherLibrariesCompile)) {
IOHelper.walk(server.launcherLibrariesCompile, new ListFileVisitor(server.launcherBinary.addonLibs), false);
}
try(Stream<Path> stream = Files.walk(server.launcherPack).filter((e) -> {
try(Stream<Path> stream = Files.walk(server.launcherPack, FileVisitOption.FOLLOW_LINKS).filter((e) -> {
try {
return !Files.isDirectory(e) && !Files.isHidden(e);
} catch (IOException ex) {

View file

@ -40,14 +40,13 @@ public void invoke(String... args) throws Exception {
logger.error("Profile {} not found", args[0]);
return;
}
var clientDir = server.updatesDir.resolve(profile.getDir()).toAbsolutePath();
logger.warn("THIS ACTION DELETE PROFILE AND ALL FILES IN {}", clientDir);
logger.warn("THIS ACTION DELETE PROFILE AND ALL FILES IN {}", profile.getDir());
if(!showApplyDialog("Continue?")) {
return;
}
logger.info("Delete {} ({})", profile.getTitle(), profile.getUUID());
server.config.profileProvider.deleteProfile(profile);
logger.info("Delete {}", clientDir);
IOHelper.deleteDir(clientDir, true);
logger.info("Delete {}", profile.getDir());
server.config.updatesProvider.delete(profile.getDir());
}
}

View file

@ -14,6 +14,8 @@
import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.protect.StdProtectHandler;
import pro.gravit.launchserver.auth.texture.RequestTextureProvider;
import pro.gravit.launchserver.auth.updates.LocalUpdatesProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.components.AuthLimiterComponent;
import pro.gravit.launchserver.components.Component;
import pro.gravit.launchserver.components.ProGuardComponent;
@ -32,13 +34,13 @@ public final class LaunchServerConfig {
public String[] mirrors;
public String binaryName;
public boolean copyBinaries = true;
public boolean cacheUpdates = true;
public LauncherConfig.LauncherEnvironment env;
public Map<String, AuthProviderPair> auth;
// Handlers & Providers
public ProtectHandler protectHandler;
public Map<String, Component> components;
public ProfileProvider profileProvider = new LocalProfileProvider();
public UpdatesProvider updatesProvider = new LocalUpdatesProvider();
public NettyConfig netty;
public LauncherConf launcher;
public JarSignerConf sign;
@ -174,6 +176,10 @@ public void init(LaunchServer.ReloadType type) {
server.registerObject("profileProvider", profileProvider);
profileProvider.init(server);
}
if(updatesProvider != null) {
server.registerObject("updatesProvider", updatesProvider);
updatesProvider.init(server);
}
if (components != null) {
components.forEach((k, v) -> server.registerObject("component.".concat(k), v));
}
@ -218,6 +224,10 @@ public void close(LaunchServer.ReloadType type) {
server.unregisterObject("profileProvider", profileProvider);
profileProvider.close();
}
if(updatesProvider != null) {
server.unregisterObject("updatesProvider", updatesProvider);
updatesProvider.close();
}
}
public static class JarSignerConf {

View file

@ -197,9 +197,6 @@ public static String getMainClassByVersion(ClientProfile.Version version, MakePr
if(version.compareTo(ClientProfileVersions.MINECRAFT_1_7_10) == 0) {
return "com.gtnewhorizons.retrofuturabootstrap.Main";
}
if(version.compareTo(ClientProfileVersions.MINECRAFT_1_12_2) == 0) {
return "top.outlands.foundation.boot.Foundation"; // Cleanroom
}
if (findOption(options, MakeProfileOptionLaunchWrapper.class).isPresent()) {
return "net.minecraft.launchwrapper.Launch";
}

View file

@ -17,6 +17,7 @@
import pro.gravit.launchserver.auth.profiles.ProfileProvider;
import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.texture.TextureProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.components.Component;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
import pro.gravit.launchserver.socket.WebSocketService;
@ -48,6 +49,7 @@ public void registerAdapters(GsonBuilder builder) {
builder.registerTypeAdapter(OptionalTrigger.class, new UniversalJsonAdapter<>(OptionalTrigger.providers));
builder.registerTypeAdapter(MixProvider.class, new UniversalJsonAdapter<>(MixProvider.providers));
builder.registerTypeAdapter(ProfileProvider.class, new UniversalJsonAdapter<>(ProfileProvider.providers));
builder.registerTypeAdapter(UpdatesProvider.class, new UniversalJsonAdapter<>(UpdatesProvider.providers));
modulesManager.invokeEvent(new PreGsonPhase(builder));
//ClientWebSocketService.appendTypeAdapters(builder);
}

View file

@ -18,112 +18,38 @@
public class UpdatesManager {
private final LaunchServer server;
private final Logger logger = LogManager.getLogger();
private final Path cacheFile;
private volatile Map<String, HashedDir> updatesDirMap;
public UpdatesManager(LaunchServer server) {
this.server = server;
this.cacheFile = server.dir.resolve(".updates-cache");
}
private void writeCache(Path file) throws IOException {
try (HOutput output = new HOutput(IOHelper.newOutput(file))) {
output.writeLength(updatesDirMap.size(), 0);
for (Map.Entry<String, HashedDir> entry : updatesDirMap.entrySet()) {
output.writeString(entry.getKey(), 0);
entry.getValue().write(output);
}
}
logger.debug("Saved {} updates to cache", updatesDirMap.size());
}
private void readCache(Path file) throws IOException {
Map<String, HashedDir> updatesDirMap = new HashMap<>(16);
try (HInput input = new HInput(IOHelper.newInput(file))) {
int size = input.readLength(0);
for (int i = 0; i < size; ++i) {
String name = input.readString(0);
HashedDir dir = new HashedDir(input);
updatesDirMap.put(name, dir);
}
}
logger.debug("Found {} updates from cache", updatesDirMap.size());
this.updatesDirMap = Collections.unmodifiableMap(updatesDirMap);
}
@Deprecated
public void readUpdatesFromCache() throws IOException {
readCache(cacheFile);
}
@Deprecated
public void readUpdatesDir() throws IOException {
if (server.config.cacheUpdates) {
if (Files.exists(cacheFile)) {
try {
readCache(cacheFile);
return;
} catch (Throwable e) {
logger.error("Read updates cache failed", e);
}
}
}
syncUpdatesDir(null);
}
@Deprecated
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
logger.info("Syncing updates dir");
Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(server.updatesDir)) {
for (final Path updateDir : dirStream) {
if (Files.isHidden(updateDir))
continue; // Skip hidden
// Resolve name and verify is dir
String name = IOHelper.getFileName(updateDir);
if (!IOHelper.isDir(updateDir)) {
if (!IOHelper.isFile(updateDir) && Stream.of(".jar", ".exe", ".hash").noneMatch(e -> updateDir.toString().endsWith(e)))
logger.warn("Not update dir: '{}'", name);
continue;
}
// Add from previous map (it's guaranteed to be non-null)
if (dirs != null && !dirs.contains(name)) {
HashedDir hdir = updatesDirMap.get(name);
if (hdir != null) {
newUpdatesDirMap.put(name, hdir);
continue;
}
}
// Sync and sign update dir
logger.info("Syncing '{}' update dir", name);
HashedDir updateHDir = new HashedDir(updateDir, null, true, true);
newUpdatesDirMap.put(name, updateHDir);
}
}
updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap);
if (server.config.cacheUpdates) {
try {
writeCache(cacheFile);
} catch (Throwable e) {
logger.error("Write updates cache failed", e);
}
}
server.modulesManager.invokeEvent(new LaunchServerUpdatesSyncEvent(server));
server.config.updatesProvider.sync(dirs);
}
@Deprecated
public HashSet<String> getUpdatesList() {
HashSet<String> set = new HashSet<>();
for (Map.Entry<String, HashedDir> entry : updatesDirMap.entrySet())
set.add(entry.getKey());
return set;
return new HashSet<>();
}
@Deprecated
public HashedDir getUpdate(String name) {
return updatesDirMap.get(name);
return server.config.updatesProvider.getUpdatesDir(name);
}
@Deprecated
public void addUpdate(String name, HashedDir dir) {
updatesDirMap.put(name, dir);
throw new UnsupportedOperationException();
}
}

View file

@ -13,6 +13,7 @@
import java.util.Set;
public class ProfilesResponse extends SimpleResponse {
@Deprecated
public static List<ClientProfile> getListVisibleProfiles(LaunchServer server, Client client) {
List<ClientProfile> profileList;
Set<ClientProfile> serverProfiles = server.getProfiles();
@ -40,6 +41,6 @@ public void execute(ChannelHandlerContext ctx, Client client) {
sendError("Access denied");
return;
}
sendResult(new ProfilesRequestEvent(getListVisibleProfiles(server, client)));
sendResult(new ProfilesRequestEvent(server.config.profileProvider.getProfiles(client)));
}
}

View file

@ -52,7 +52,7 @@ public void execute(ChannelHandlerContext ctx, Client client) {
client.checkSign = true;
sendResult(new LauncherRequestEvent(false, server.config.netty.launcherURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
} else {
sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherURL, null, 0));
}
} else if (launcher_type == 2) //EXE
{
@ -62,7 +62,7 @@ public void execute(ChannelHandlerContext ctx, Client client) {
client.checkSign = true;
sendResult(new LauncherRequestEvent(false, server.config.netty.launcherEXEURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
} else {
sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherEXEURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherEXEURL, null, 0));
}
} else sendError("Request launcher type error");
}

View file

@ -1,7 +1,6 @@
apply plugin: 'com.github.johnrengelman.shadow'
String mainClassName = "pro.gravit.launcher.start.ClientLauncherWrapper"
String mainAgentName = "pro.gravit.launcher.runtime.LauncherAgent"
repositories {
maven {
@ -20,7 +19,6 @@
jar {
archiveClassifier.set('clean')
manifest.attributes("Main-Class": mainClassName,
"Premain-Class": mainAgentName,
"Multi-Release": "true",
"Automatic-Module-Name": "GravitLauncher")
}

View file

@ -130,7 +130,6 @@ public static void main(String... args) throws Throwable {
LogHelper.printLicense("Launcher");
LauncherEngine.checkClass(LauncherEngineWrapper.class);
LauncherEngine.checkClass(LauncherEngine.class);
LauncherEngine.checkClass(LauncherAgent.class);
LauncherEngine.checkClass(ClientLauncherEntryPoint.class);
LauncherEngine.modulesManager = new RuntimeModuleManager();
LauncherEngine.modulesManager.loadModule(new RuntimeLauncherCoreModule());

View file

@ -1,6 +1,7 @@
package pro.gravit.launcher.runtime.backend;
import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.backend.UserSettings;
import java.util.HashMap;
@ -19,5 +20,24 @@ public static class AuthorizationData {
public String refreshToken;
@LauncherNetworkAPI
public long expireIn;
public AuthFeatureAPI.AuthToken toToken() {
return new AuthFeatureAPI.AuthToken() {
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public String getRefreshToken() {
return refreshToken;
}
@Override
public long getExpire() {
return expireIn;
}
};
}
}
}

View file

@ -37,7 +37,8 @@ CompletableFuture<LauncherBackendAPI.ReadyProfile> downloadProfile(ClientProfile
AtomicReference<DownloadedDir> clientRef = new AtomicReference<>();
AtomicReference<DownloadedDir> assetRef = new AtomicReference<>();
AtomicReference<DownloadedDir> javaRef = new AtomicReference<>();
return downloadDir(profile.getDir(), profile.getClientUpdateMatcher(), settings.view, callback).thenCompose((clientDir -> {
return LauncherAPIHolder.profile().changeCurrentProfile(profile)
.thenCompose(vv -> downloadDir(profile.getDir(), profile.getClientUpdateMatcher(), settings.view, callback)).thenCompose((clientDir -> {
clientRef.set(clientDir);
return downloadAsset(profile.getAssetDir(), profile.getAssetUpdateMatcher(), profile.getAssetIndex(), callback);
})).thenCompose(assetDir -> {
@ -53,7 +54,7 @@ CompletableFuture<LauncherBackendAPI.ReadyProfile> downloadProfile(ClientProfile
CompletableFuture<DownloadedDir> downloadAsset(String dirName, FileNameMatcher matcher, String assetIndexString, LauncherBackendAPI.DownloadCallback callback) {
Path targetDir = DirBridge.dirUpdates.resolve(dirName);
Path assetIndexPath = targetDir.resolve("indexes").resolve(assetIndexString);
Path assetIndexPath = targetDir.resolve("indexes").resolve(assetIndexString+".json");
return LauncherAPIHolder.profile().fetchUpdateInfo(dirName).thenComposeAsync((response) -> {
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_ASSET_VERIFY);
return verifyAssetIndex(assetIndexString, response, assetIndexPath, targetDir);
@ -116,7 +117,7 @@ CompletableFuture<DownloadedDir> downloadDir(Path targetDir, ProfileFeatureAPI.U
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_HASHING);
HashedDir realFiles = new HashedDir(targetDir, matcher, false, true);
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DIFF);
return realFiles.diff(updateInfo.getHashedDir(), matcher);
return updateInfo.getHashedDir().diff(realFiles, matcher);
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -165,7 +166,12 @@ private List<Downloader.SizedFile> collectFilesAndCreateDirectories(Path dir, Ha
List<Downloader.SizedFile> files = new ArrayList<>();
mismatch.walk(File.separator, (path, name, entry) -> {
if(entry.getType() == HashedEntry.Type.DIR) {
Files.createDirectory(dir.resolve(path));
var dirPath = dir.resolve(path);
if(!Files.exists(dirPath)) {
Files.createDirectory(dirPath);
} else if (!Files.isDirectory(dirPath)) {
throw new IOException(String.format("%s is not a directory", path));
}
return HashedDir.WalkAction.CONTINUE;
}
String pathFixed = path.replace(File.separatorChar, '/');

View file

@ -16,6 +16,7 @@
import pro.gravit.launcher.core.backend.exceptions.LauncherBackendException;
import pro.gravit.launcher.core.backend.extensions.Extension;
import pro.gravit.launcher.runtime.NewLauncherSettings;
import pro.gravit.launcher.runtime.client.DirBridge;
import pro.gravit.launcher.runtime.client.ServerPinger;
import pro.gravit.launcher.runtime.debug.DebugMain;
import pro.gravit.launcher.runtime.managers.SettingsManager;
@ -73,6 +74,7 @@ private void doInit() throws Exception {
allSettings = settingsManager.getConfig();
backendSettings = (BackendSettings) getUserSettings("backend", (k) -> new BackendSettings());
permissions = new ClientPermissions();
DirBridge.dirUpdates = DirBridge.defaultUpdatesDir;
}
@Override
@ -102,6 +104,14 @@ public CompletableFuture<LauncherInitData> init() {
}, executorService);
}
public AuthFeatureAPI.AuthToken getAuthToken() {
return backendSettings.auth.toToken();
}
public AuthMethod getAuthMethod() {
return authMethod;
}
@Override
public void selectAuthMethod(AuthMethod method) {
this.authMethod = method;
@ -175,9 +185,11 @@ public ClientProfileSettings makeClientProfileSettings(ProfileFeatureAPI.ClientP
var settings = backendSettings.settings.get(profile.getUUID());
if(settings == null) {
settings = new ProfileSettingsImpl((ClientProfile) profile);
settings.backend = this;
settings.updateEnabledMods();
} else {
settings = settings.copy();
settings.initAfterGson((ClientProfile) profile, this);
//settings.initAfterGson((ClientProfile) profile, this);
}
return settings;
}
@ -211,6 +223,7 @@ public CompletableFuture<List<Java>> getAvailableJava() {
return e;
});
}
return availableJavasFuture;
}
return CompletableFuture.completedFuture(availableJavas);
}

View file

@ -12,6 +12,7 @@
import pro.gravit.utils.helper.JavaHelper;
import java.util.*;
import java.util.concurrent.ExecutionException;
public class ProfileSettingsImpl implements LauncherBackendAPI.ClientProfileSettings {
transient ClientProfile profile;
@ -120,6 +121,30 @@ public JavaHelper.JavaVersion getSelectedJava() {
return selectedJava;
}
@Override
public JavaHelper.JavaVersion getRecommendedJava() {
JavaHelper.JavaVersion result = null;
try {
for(var java : backend.getAvailableJava().get()) {
if(isRecommended(java)) {
return (JavaHelper.JavaVersion) java;
}
if(isCompatible(java)) {
if(result == null) {
result = (JavaHelper.JavaVersion) java;
continue;
}
if(result.getMajorVersion() < java.getMajorVersion()) {
result = (JavaHelper.JavaVersion) java;
}
}
}
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
return result;
}
@Override
public void setSelectedJava(LauncherBackendAPI.Java java) {
selectedJava = (JavaHelper.JavaVersion) java;
@ -138,6 +163,7 @@ public boolean isCompatible(LauncherBackendAPI.Java java) {
@Override
public ProfileSettingsImpl copy() {
ProfileSettingsImpl cloned = new ProfileSettingsImpl();
cloned.backend = backend;
cloned.profile = profile;
cloned.ram = new HashMap<>(ram);
cloned.flags = new HashSet<>(flags);
@ -155,7 +181,9 @@ public void updateEnabledMods() {
for(var e : view.enabled) {
enabled.add(e.name);
}
saveJavaPath = selectedJava.getPath().toAbsolutePath().toString();
if(selectedJava != null) {
saveJavaPath = selectedJava.getPath().toAbsolutePath().toString();
}
}
public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) {

View file

@ -1,9 +1,11 @@
package pro.gravit.launcher.runtime.backend;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launcher.base.profiles.PlayerProfile;
import pro.gravit.launcher.core.api.LauncherAPIHolder;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
import pro.gravit.launcher.runtime.client.ClientLauncherProcess;
@ -61,9 +63,14 @@ public void run(LauncherBackendAPI.RunCallback callback) {
builder.setUpdateExclusions(new ArrayList<>());
profile = builder.createClientProfile();
}
process = new ClientLauncherProcess(clientDir.path(), assetDir.path(), settings.getSelectedJava(), clientDir.path().resolve("resourcepacks"),
var java = settings.getSelectedJava();
if(java == null) {
java = settings.getRecommendedJava();
}
process = new ClientLauncherProcess(clientDir.path(), assetDir.path(), java, clientDir.path().resolve("resourcepacks"),
profile, new PlayerProfile(backend.getSelfUser()), settings.view, backend.getSelfUser().getAccessToken(),
clientDir.dir(), assetDir.dir(), javaDir == null ? null : javaDir.dir());
clientDir.dir(), assetDir.dir(), javaDir == null ? null : javaDir.dir(),
new AuthRequestEvent.OAuthRequestEvent(backend.getAuthToken()), backend.getAuthMethod().getName());
process.params.ram = (int) (settings.getReservedMemoryBytes(LauncherBackendAPI.ClientProfileSettings.MemoryClass.TOTAL) >> 20);
if (process.params.ram > 0) {
process.jvmArgs.add("-Xms" + process.params.ram + 'M');

View file

@ -2,6 +2,7 @@
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.LauncherConfig;
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
import pro.gravit.launcher.client.ClientLauncherEntryPoint;
import pro.gravit.launcher.client.ClientParams;
import pro.gravit.launcher.runtime.LauncherEngine;
@ -70,6 +71,12 @@ public ClientLauncherProcess(Path clientDir, Path assetDir,
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(clientDir, assetDir, javaVersion, resourcePackDir, profile, playerProfile, view, accessToken, clientHDir, assetHDir, jvmHDir, null, null);
}
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, AuthRequestEvent.OAuthRequestEvent oAuthRequestEvent, String authId) {
this.javaVersion = javaVersion;
this.workDir = clientDir.toAbsolutePath();
this.executeFile = IOHelper.resolveJavaBin(this.javaVersion.jvmDir);
@ -78,6 +85,8 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersi
this.params.resourcePackDir = resourcePackDir.toAbsolutePath().toString();
this.params.assetDir = assetDir.toAbsolutePath().toString();
this.params.timestamp = System.currentTimeMillis();
this.params.oauth = oAuthRequestEvent;
this.params.authId = authId;
Path nativesPath;
if(profile.hasFlag(ClientProfile.CompatibilityFlags.LEGACY_NATIVES_DIR)) {
nativesPath = workDir.resolve("natives");
@ -120,10 +129,8 @@ private void applyClientProfile() {
if (params.ram > 0) {
this.jvmArgs.add("-Xmx" + params.ram + 'M');
}
this.params.oauth = Request.getOAuth();
if (this.params.oauth == null) {
throw new UnsupportedOperationException("Legacy session not supported");
} else {
this.params.oauth = Request.getOAuth();
this.params.authId = Request.getAuthId();
this.params.oauthExpiredTime = Request.getTokenExpiredTime();
this.params.extendedTokens = Request.getExtendedTokens();
@ -142,9 +149,7 @@ public void start(boolean pipeOutput) throws IOException, InterruptedException {
}
//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) {
if (params.profile.getClassLoaderConfig() == ClientProfile.ClassLoaderConfig.SYSTEM_ARGS) {
Set<Path> ignorePath = new HashSet<>();
var moduleConf = params.profile.getModuleConf();
if(moduleConf != null) {
@ -159,6 +164,24 @@ public void start(boolean pipeOutput) throws IOException, InterruptedException {
processArgs.add("--add-modules");
processArgs.add(String.join(",", moduleConf.modules));
}
if(moduleConf.exports != null && !moduleConf.exports.isEmpty()) {
for(var e : moduleConf.exports.entrySet()) {
processArgs.add("--add-exports");
processArgs.add(String.format("%s=%s", e.getKey(), e.getValue()));
}
}
if(moduleConf.opens != null && !moduleConf.opens.isEmpty()) {
for(var e : moduleConf.opens.entrySet()) {
processArgs.add("--add-opens");
processArgs.add(String.format("%s=%s", e.getKey(), e.getValue()));
}
}
if(moduleConf.reads != null && !moduleConf.reads.isEmpty()) {
for(var e : moduleConf.reads.entrySet()) {
processArgs.add("--add-reads");
processArgs.add(String.format("%s=%s", e.getKey(), e.getValue()));
}
}
}
systemClassPath.addAll(ClientLauncherEntryPoint.resolveClassPath(ignorePath, workDir, params.actions, params.profile)
.map(Path::toString)

View file

@ -141,7 +141,6 @@ public void cancel() {
}
}
tasks.clear();
executor.shutdownNow();
}
public boolean isCanceled() {

View file

@ -87,6 +87,12 @@ public OAuthRequestEvent(String accessToken, String refreshToken, long expire) {
this.expire = expire;
}
public OAuthRequestEvent(AuthFeatureAPI.AuthToken token) {
this.accessToken = token.getAccessToken();
this.refreshToken = token.getRefreshToken();
this.expire = token.getExpire();
}
@Override
public String getAccessToken() {
return accessToken;

View file

@ -414,7 +414,7 @@ public List<CompatibilityFlags> getFlags() {
}
public enum ClassLoaderConfig {
AGENT, LAUNCHER, MODULE, SYSTEM_ARGS
LAUNCHER, MODULE, SYSTEM_ARGS
}
public enum CompatibilityFlags {

View file

@ -1,6 +1,7 @@
package pro.gravit.launcher.base.request;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.request.auth.*;
import pro.gravit.launcher.base.request.auth.password.*;
import pro.gravit.launcher.base.request.update.ProfilesRequest;
@ -89,6 +90,8 @@ private AuthRequest.AuthPasswordInterface convertAuthPassword(AuthMethodPassword
return new AuthCodePassword(oauth.redirectUrl());
} else if(password instanceof AuthRequest.AuthPasswordInterface custom) {
return custom;
} else if(password == null) {
return null;
}
else {
throw new UnsupportedOperationException();
@ -164,6 +167,11 @@ public CompletableFuture<List<ProfileFeatureAPI.ClientProfile>> getProfiles() {
return request.request(new ProfilesRequest()).thenApply(response -> (List) response.profiles);
}
@Override
public CompletableFuture<Void> changeCurrentProfile(ClientProfile profile) {
return request.request(new SetProfileRequest((pro.gravit.launcher.base.profiles.ClientProfile) profile)).thenApply(response -> null);
}
@Override
public CompletableFuture<UpdateInfo> fetchUpdateInfo(String dirName) {
return request.request(new UpdateRequest(dirName)).thenApply(response -> new UpdateInfoData(response.hdir, response.url));

View file

@ -1,7 +1,6 @@
apply plugin: 'org.openjfx.javafxplugin'
String mainClassName = "pro.gravit.launcher.ClientLauncherWrapper"
String mainAgentName = "pro.gravit.launcher.LauncherAgent"
String mainClassName = "pro.gravit.launcher.start.ClientLauncherWrapper"
repositories {
maven {
@ -14,7 +13,6 @@
jar {
archiveClassifier.set('clean')
manifest.attributes("Main-Class": mainClassName,
"Premain-Class": mainAgentName,
"Multi-Release": "true")
}

View file

@ -86,11 +86,9 @@ private static void realMain(String[] args) throws Throwable {
modulesManager.invokeEvent(new PreConfigPhase());
LogHelper.debug("Reading ClientLauncher params");
ClientParams params = readParams(new InetSocketAddress("127.0.0.1", Launcher.getConfig().clientPort));
if (params.profile.getClassLoaderConfig() != ClientProfile.ClassLoaderConfig.AGENT) {
ClientLauncherMethods.verifyNoAgent();
}
ClientLauncherMethods.verifyNoAgent();
if(params.timestamp > System.currentTimeMillis() || params.timestamp + 30*1000 < System.currentTimeMillis() ) {
LogHelper.error("Timestamp failed. Exit");
LogHelper.error("Timestamp failed: current %d | params %d | diff %d", System.currentTimeMillis(), params.timestamp, System.currentTimeMillis() - params.timestamp);
ClientLauncherMethods.exitLauncher(-662);
return;
}
@ -160,7 +158,7 @@ private static void realMain(String[] args) throws Throwable {
System.load(Paths.get(params.nativesDir).resolve(ClientService.findLibrary(e)).toAbsolutePath().toString());
}
}
if (classLoaderConfig == ClientProfile.ClassLoaderConfig.LAUNCHER) {
if (classLoaderConfig == ClientProfile.ClassLoaderConfig.LAUNCHER || classLoaderConfig == ClientProfile.ClassLoaderConfig.MODULE) {
if(JVMHelper.JVM_VERSION <= 11) {
launch = new LegacyLaunch();
} else {
@ -170,20 +168,12 @@ private static void realMain(String[] args) throws Throwable {
System.setProperty("java.class.path", classpath.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator)));
modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(launch, classLoaderControl, profile));
ClientService.baseURLs = classLoaderControl.getURLs();
} else if (classLoaderConfig == ClientProfile.ClassLoaderConfig.AGENT) {
launch = new BasicLaunch(LauncherAgent.inst);
classpathURLs.add(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toUri().toURL());
classLoaderControl = launch.init(classpath, params.nativesDir, options);
for (URL url : classpathURLs) {
LauncherAgent.addJVMClassPath(Paths.get(url.toURI()));
}
ClientService.instrumentation = LauncherAgent.inst;
modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(launch, null, profile));
ClientService.baseURLs = classpathURLs.toArray(new URL[0]);
} else if (classLoaderConfig == ClientProfile.ClassLoaderConfig.SYSTEM_ARGS) {
launch = new BasicLaunch();
classLoaderControl = launch.init(classpath, params.nativesDir, options);
ClientService.baseURLs = classpathURLs.toArray(new URL[0]);
} else {
throw new UnsupportedOperationException(String.format("Unknown classLoaderConfig %s", classLoaderConfig));
}
if(profile.hasFlag(ClientProfile.CompatibilityFlags.CLASS_CONTROL_API)) {
ClientService.classLoaderControl = classLoaderControl;

View file

@ -1,59 +0,0 @@
package pro.gravit.launcher.client;
import pro.gravit.launcher.client.utils.NativeJVMHalt;
import pro.gravit.utils.helper.LogHelper;
import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.nio.file.Path;
import java.util.jar.JarFile;
public final class LauncherAgent {
public static Instrumentation inst;
private static boolean isAgentStarted = false;
public static void addJVMClassPath(String path) throws IOException {
LogHelper.debug("Launcher Agent addJVMClassPath");
inst.appendToSystemClassLoaderSearch(new JarFile(new File(path)));
}
public static void addJVMClassPath(Path path) throws IOException {
LogHelper.debug("Launcher Agent addJVMClassPath");
inst.appendToSystemClassLoaderSearch(new JarFile(path.toFile()));
}
public static void premain(String agentArgument, Instrumentation instrumentation) {
System.out.println("Launcher Agent");
checkAgentStacktrace();
inst = instrumentation;
NativeJVMHalt.initFunc();
isAgentStarted = true;
}
public static void checkAgentStacktrace() {
RuntimeException ex = new SecurityException("Error check agent stacktrace");
boolean isFoundNative = false;
boolean foundPreMain = false;
for (StackTraceElement e : ex.getStackTrace()) {
if (e.isNativeMethod()) {
if (!isFoundNative) isFoundNative = true;
else throw ex;
}
if (e.getMethodName().equals("premain")) {
if (!foundPreMain) foundPreMain = true;
else throw ex;
}
}
if (!isFoundNative || !foundPreMain) throw ex;
}
public static boolean isStarted() {
return isAgentStarted;
}
public boolean isAgentStarted() {
return isAgentStarted;
}
}

View file

@ -9,6 +9,7 @@
public interface ProfileFeatureAPI extends FeatureAPI {
CompletableFuture<List<ClientProfile>> getProfiles();
CompletableFuture<Void> changeCurrentProfile(ClientProfile profile);
CompletableFuture<UpdateInfo> fetchUpdateInfo(String dirName);
interface UpdateInfo {

View file

@ -63,6 +63,7 @@ interface ClientProfileSettings {
void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback);
void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback);
Java getSelectedJava();
Java getRecommendedJava();
void setSelectedJava(Java java);
boolean isRecommended(Java java);
boolean isCompatible(Java java);

View file

@ -123,6 +123,9 @@ protected Class<?> findClass(String name) throws ClassNotFoundException {
@Override
public String findLibrary(String name) {
if(nativePath == null) {
return null;
}
return nativePath.concat(IOHelper.PLATFORM_SEPARATOR).concat(JVMHelper.NATIVE_PREFIX).concat(name).concat(JVMHelper.NATIVE_EXTENSION);
}

View file

@ -262,6 +262,9 @@ protected Class<?> findClass(String moduleName, String name) {
@Override
public String findLibrary(String name) {
if(nativePath == null) {
return null;
}
return nativePath.concat(IOHelper.PLATFORM_SEPARATOR).concat(JVMHelper.NATIVE_PREFIX).concat(name).concat(JVMHelper.NATIVE_EXTENSION);
}

Binary file not shown.

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

7
gradlew vendored
View file

@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

2
gradlew.bat vendored
View file

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################

@ -1 +1 @@
Subproject commit c5087d40c9dd5b065fccedeac21afb967a25e112
Subproject commit 0d8cef927b1fda3097dc88c3adcffc4d0e33dd69

View file

@ -1,18 +1,18 @@
project.ext {
verAsm = '9.7'
verNetty = '4.1.111.Final'
verOshiCore = '6.6.1'
verJunit = '5.10.2'
verAsm = '9.7.1'
verNetty = '4.1.114.Final'
verOshiCore = '6.6.5'
verJunit = '5.11.2'
verJansi = '2.4.1'
verJline = '3.26.1'
verJwt = '0.12.5'
verJline = '3.26.3'
verJwt = '0.12.6'
verGson = '2.11.0'
verBcpkix = '1.78.1'
verSlf4j = '2.0.13'
verLog4j = '2.23.1'
verSlf4j = '2.0.16'
verLog4j = '2.24.1'
verMySQLConn = '9.0.0'
verMariaDBConn = '3.4.0'
verPostgreSQLConn = '42.7.3'
verH2Conn = '2.2.224'
verProguard = '7.5.0'
verMariaDBConn = '3.4.1'
verPostgreSQLConn = '42.7.4'
verH2Conn = '2.3.232'
verProguard = '7.6.0'
}