Merge branch 'release/5.0.1'

This commit is contained in:
Gravit 2019-05-23 06:14:11 +07:00
commit e55200f5eb
No known key found for this signature in database
GPG key ID: 061981E1E85D3216
39 changed files with 711 additions and 262 deletions

View file

@ -24,7 +24,7 @@ build:
- ./gradlew assemble
artifacts:
paths:
- LaunchServer/build/libs/*.jar
- LaunchServer/build/libs/*
- ServerWrapper/build/libs/*.jar
expire_in: 1 week

View file

@ -263,9 +263,15 @@ public static class ExeConf {
public String txtFileVersion;
public String txtProductVersion;
}
public static class NettyUpdatesBind
{
public String url;
public boolean zip;
}
public class LauncherConf {
public String guardType;
public boolean attachLibraryBeforeProGuard;
}
public class NettyConfig {
@ -276,7 +282,7 @@ public class NettyConfig {
public String downloadURL;
public String launcherEXEURL;
public String address;
public Map<String, String> bindings = new HashMap<>();
public Map<String, NettyUpdatesBind> bindings = new HashMap<>();
public NettyPerformanceConfig performance;
public NettyBindAddress[] binds;
public LogLevel logLevel = LogLevel.DEBUG;

View file

@ -27,6 +27,7 @@ public class MysqlHWIDHandler extends HWIDHandler {
private String hwidFieldHWDiskSerial;
private String hwidFieldProcessorID;
private String hwidFieldBanned;
private String hwidFieldMAC;
private String queryHwids;
private String[] paramsHwids;
@ -37,7 +38,7 @@ public class MysqlHWIDHandler extends HWIDHandler {
private String banMessage;
private boolean compareMode = false;
//Using queryHWID "queryHwids": "SELECT * FROM `users_hwids` WHERE `totalMemory` = ? or `serialNumber` = ? or `HWDiskSerial` = ? or `processorID` = ?"
//Using queryHWID "queryHwids": "SELECT * FROM `users_hwids` WHERE `totalMemory` = ? or `serialNumber` = ? or `HWDiskSerial` = ? or `processorID` = ? or `MACAddr` = ?"
private int compare = 50; //При наборе схожести в 50 очков
private boolean oneCompareMode = false;
@ -51,6 +52,7 @@ public class MysqlHWIDHandler extends HWIDHandler {
`serialNumber` varchar(64) NOT NULL,
`HWDiskSerial` varchar(64) NOT NULL,
`processorID` varchar(64) NOT NULL,
`MACAddr` varchar(64) NOT NULL,
`isBanned` tinyint(1) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@ -90,7 +92,7 @@ public void check0(HWID hwid, String username) throws HWIDException {
public void onUpdateInfo(OshiHWID hwid, String username, Connection c) throws HWIDException {
try (PreparedStatement a = c.prepareStatement(queryHwids)) {
String[] replaceParams = {"totalMemory", String.valueOf(hwid.totalMemory), "serialNumber", hwid.serialNumber, "HWDiskSerial", hwid.HWDiskSerial, "processorID", hwid.processorID};
String[] replaceParams = {"totalMemory", String.valueOf(hwid.totalMemory), "serialNumber", hwid.serialNumber, "HWDiskSerial", hwid.HWDiskSerial, "processorID", hwid.processorID, "MAC", hwid.macAddr};
for (int i = 0; i < paramsHwids.length; i++) {
a.setString(i + 1, CommonHelper.replace(paramsHwids[i], replaceParams));
}
@ -113,12 +115,13 @@ public void onUpdateInfo(OshiHWID hwid, String username, Connection c) throws HW
throw new HWIDException(banMessage);
}
} else {
ps = c.prepareStatement(String.format("INSERT INTO `%s` (`%s`, `%s`, `%s`, `%s`) VALUES (?, ?, ?, ?);",
tableHwids, hwidFieldTotalMemory, hwidFieldSerialNumber, hwidFieldHWDiskSerial, hwidFieldProcessorID));
ps = c.prepareStatement(String.format("INSERT INTO `%s` (`%s`, `%s`, `%s`, `%s`, `%s`) VALUES (?, ?, ?, ?, ?);",
tableHwids, hwidFieldTotalMemory, hwidFieldSerialNumber, hwidFieldHWDiskSerial, hwidFieldProcessorID, hwidFieldMAC));
ps.setString(1, String.valueOf(hwid.totalMemory));
ps.setString(2, hwid.serialNumber);
ps.setString(3, hwid.HWDiskSerial);
ps.setString(4, hwid.processorID);
ps.setString(5, hwid.macAddr);
ps.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
ps.executeUpdate();
@ -135,7 +138,7 @@ public void onUpdateInfo(OshiHWID hwid, String username, Connection c) throws HW
public void onCheckInfo(OshiHWID hwid, String username, Connection c) throws HWIDException {
try (PreparedStatement a = c.prepareStatement(queryHwids)) {
String[] replaceParams = {"totalMemory", String.valueOf(hwid.totalMemory), "serialNumber", hwid.serialNumber, "HWDiskSerial", hwid.HWDiskSerial, "processorID", hwid.processorID};
String[] replaceParams = {"totalMemory", String.valueOf(hwid.totalMemory), "serialNumber", hwid.serialNumber, "HWDiskSerial", hwid.HWDiskSerial, "processorID", hwid.processorID, "MAC", hwid.macAddr};
for (int i = 0; i < paramsHwids.length; i++) {
a.setString(i + 1, CommonHelper.replace(paramsHwids[i], replaceParams));
}
@ -149,7 +152,7 @@ public void onCheckInfo(OshiHWID hwid, String username, Connection c) throws HWI
db_hwid.processorID = set.getString(hwidFieldProcessorID);
db_hwid.HWDiskSerial = set.getString(hwidFieldHWDiskSerial);
db_hwid.totalMemory = Long.valueOf(set.getString(hwidFieldTotalMemory));
db_hwid.macAddr = "";
db_hwid.macAddr = set.getString(hwidFieldMAC);
LogHelper.dev("Compare HWID: %s vs %s", hwid.getSerializeString(), db_hwid.getSerializeString());
int compare_point = hwid.compare(db_hwid);
if (compare_point < compare) continue;
@ -177,7 +180,7 @@ public void setIsBanned(HWID hwid, boolean isBanned) {
OshiHWID oshiHWID = (OshiHWID) hwid;
try (Connection c = mySQLHolder.getConnection()) {
try (PreparedStatement a = c.prepareStatement(queryBan)) {
String[] replaceParamsUpd = {"totalMemory", String.valueOf(oshiHWID.totalMemory), "serialNumber", oshiHWID.serialNumber, "HWDiskSerial", oshiHWID.HWDiskSerial, "processorID", oshiHWID.processorID, "isBanned", isBanned ? "1" : "0"};
String[] replaceParamsUpd = {"totalMemory", String.valueOf(oshiHWID.totalMemory), "serialNumber", oshiHWID.serialNumber, "HWDiskSerial", oshiHWID.HWDiskSerial, "processorID", oshiHWID.processorID, "MAC", oshiHWID.macAddr, "isBanned", isBanned ? "1" : "0"};
for (int i = 0; i < paramsBan.length; i++) {
a.setString(i + 1, CommonHelper.replace(paramsBan[i], replaceParamsUpd));
}
@ -231,6 +234,7 @@ public List<HWID> getHwid(String username) {
oshiHWID.serialNumber = rs.getString(hwidFieldSerialNumber);
oshiHWID.HWDiskSerial = rs.getString(hwidFieldHWDiskSerial);
oshiHWID.processorID = rs.getString(hwidFieldProcessorID);
oshiHWID.macAddr = rs.getString(hwidFieldMAC);
list.add(oshiHWID);
}
}

View file

@ -83,6 +83,12 @@ public void setSecretKey(String key) {
body.append("\";");
}
public void setOemUnlockKey(String key) {
body.append("this.oemUnlockKey = \"");
body.append(key);
body.append("\";");
}
public void setGuardType(String key) {
body.append("this.guardType = \"");
body.append(key);

View file

@ -42,10 +42,11 @@ public JARLauncherBinary(LaunchServer server) throws IOException {
public void init() {
tasks.add(new PrepareBuildTask(server));
tasks.add(new MainBuildTask(server));
if(server.config.launcher.attachLibraryBeforeProGuard) tasks.add(new AttachJarsTask(server));
tasks.add(new ProGuardBuildTask(server));
tasks.add(new AdditionalFixesApplyTask(server));
tasks.add(new RadonBuildTask(server));
tasks.add(new AttachJarsTask(server));
if(!server.config.launcher.attachLibraryBeforeProGuard) tasks.add(new AttachJarsTask(server));
}
@Override

View file

@ -137,6 +137,8 @@ public Path process(Path inputJar) throws IOException {
jaConfigurator.setGuardType(server.config.launcher.guardType);
jaConfigurator.setWarningMissArchJava(server.config.isWarningMissArchJava);
jaConfigurator.setEnv(server.config.env);
if(server.runtime.oemUnlockKey == null) server.runtime.oemUnlockKey = SecurityHelper.randomStringToken();
jaConfigurator.setOemUnlockKey(server.runtime.oemUnlockKey);
server.buildHookManager.registerAllClientModuleClass(jaConfigurator);
reader.getCp().add(new JarFile(inputJar.toFile()));
server.launcherBinary.coreLibs.forEach(e -> {

View file

@ -5,6 +5,7 @@
public class LaunchServerRuntimeConfig {
public String clientToken;
public String oemUnlockKey;
public void verify() {
if (clientToken == null) LogHelper.error("[RuntimeConfig] clientToken must not be null");

View file

@ -39,7 +39,13 @@ public void execute(ChannelHandlerContext ctx, Client client) {
return;
}
String url = LaunchServer.server.config.netty.downloadURL.replace("%dirname%", dirName);
if (server.config.netty.bindings.get(dirName) != null) url = server.config.netty.bindings.get(dirName);
service.sendObject(ctx, new UpdateRequestEvent(dir.object, url));
boolean zip = false;
if (server.config.netty.bindings.get(dirName) != null)
{
LaunchServer.NettyUpdatesBind bind = server.config.netty.bindings.get(dirName);
url = bind.url;
zip = bind.zip;
}
service.sendObject(ctx, new UpdateRequestEvent(dir.object, url, zip));
}
}

View file

@ -5,7 +5,6 @@ var profilesList = [];
var movePoint = null;
var pingers = {};
var loginData;
// Variable which contains all types of auth. Appending data at line 255
var authTypes = {};
function initLauncher() {
@ -195,7 +194,6 @@ function goAuth(event) {
return;
}
// Get auth
var auth = authOptions.getSelectionModel().getSelectedItem();
if (auth === null) {
return; // No auth selected
@ -220,7 +218,7 @@ function goAuth(event) {
}
settings.login = login;
doAuth(/*auth, */login, rsaPassword, authTypes[auth]);
doAuth(login, rsaPassword, authTypes[auth]);
}
/* ======== Console ======== */
@ -259,17 +257,14 @@ function verifyLauncher(e) {
result.list.forEach(function(auth_type, i, arr) {
var serverAuth = new com.jfoenix.controls.JFXComboBox();
serverAuth.getStyleClass().add("authOptions");
// add display name to items and add name with iter to variable authTypes
authOptions.getItems().add(auth_type.displayName);
authTypes[auth_type.displayName] = auth_type.name;
iter++;
});
authOptions.getSelectionModel().select(0);
var sm = authOptions.getSelectionModel().selectedIndexProperty();
// add listener to authOptions select
sm.addListener(new javafx.beans.value.ChangeListener({
changed: function (observableValue, oldSelection, newSelection) {
// get auth name from authTypes
settings.auth = authTypes[authOptions.getSelectionModel().getSelectedItem()];
}
}));
@ -423,8 +418,8 @@ var overlay = {
dimPane.setVisible(true);
dimPane.toFront();
loginPaneLayout.setEffect(new javafx.scene.effect.GaussianBlur(55));
serverPaneLayout.setEffect(new javafx.scene.effect.GaussianBlur(55));
loginPaneLayout.setEffect(new javafx.scene.effect.GaussianBlur(10));
serverPaneLayout.setEffect(new javafx.scene.effect.GaussianBlur(10));
fade(dimPane, 0.0, 0.0, 1.0, function(event) {
dimPane.requestFocus();
dimPane.getChildren().add(newOverlay);

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View file

@ -17,7 +17,6 @@ var processing = {
setError: function(e) {
LogHelper.error(e);
processing.description.textProperty().unbind();
//processing.errorImage.setImage(processing.errorImage);
processing.description.getStyleClass().add("error");
processing.description.setText(e.toString());
},
@ -48,8 +47,6 @@ function offlineAuthRequest(login) {
Request.requestError("Имя пользователя некорректно");
return;
}
// Return offline profile and random access token
return {
pp: PlayerProfile.newOfflineProfile(login),
accessToken: SecurityHelper.randomStringToken()
@ -57,18 +54,15 @@ function offlineAuthRequest(login) {
};
}
/* Export functions */
function makeLauncherRequest(callback) {
var task = settings.offline ? newTask(FunctionalBridge.offlineLauncherRequest) :
newRequestTask(new LauncherRequest());
// Set task properties and start
processing.setTaskProperties(task, callback, function() {
if (settings.offline) {
return;
}
// Repeat request, but in offline mode
settings.offline = true;
overlay.swap(2500, processing.overlay, function() makeLauncherRequest(callback));
}, false);
@ -78,13 +72,11 @@ function makeLauncherRequest(callback) {
function makeProfilesRequest(callback) {
var task = newRequestTask(new ProfilesRequest());
// Set task properties and start
processing.setTaskProperties(task, callback, function() {
if (settings.offline) {
return;
}
// Repeat request, but in offline mode
settings.offline = true;
overlay.swap(2500, processing.overlay, function() makeProfilesRequest(callback));
}, false);
@ -94,7 +86,6 @@ function makeProfilesRequest(callback) {
function makeAuthAvailabilityRequest(callback) {
var task = newRequestTask(new GetAvailabilityAuthRequest());
// Set task properties and start
processing.setTaskProperties(task, callback, function() {
if (settings.offline) {
return;
@ -110,7 +101,6 @@ function makeAuthAvailabilityRequest(callback) {
function makeSetProfileRequest(profile, callback) {
var task = newRequestTask(new SetProfileRequest(profile));
// Set task properties and start
processing.setTaskProperties(task, callback, function() {
if (settings.offline) {
return;

View file

@ -3,7 +3,7 @@
#overlay {
-fx-background-color: transparent;
-fx-background-size: cover;
-fx-background-image: url('../../images/background.jpg');
-fx-background-image: url('../../images/downloader/blured.jpg');
}
#overlay > #utitle {
@ -20,7 +20,7 @@ #overlay > #description.error {
}
.downloadPane {
-fx-background-color: rgba(0, 0, 0, 0.3);
-fx-background-color: rgba(0, 0, 0, 0.2);
}
/* Progress bar */

View file

@ -8,29 +8,27 @@
<!-- DrLeonardo Design -->
<Pane fx:id="overlay" prefHeight="450.0" prefWidth="693.0" xmlns="http://javafx.com/javafx/8.0.201"
xmlns:fx="http://javafx.com/fxml/1">
<children>
<Pane prefHeight="450.0" prefWidth="693.0" styleClass="downloadPane">
<children>
<Label fx:id="utitle" alignment="CENTER" layoutX="100.0" layoutY="125.0" prefHeight="30.0"
prefWidth="495.0" text="Загрузка обновления..." textFill="WHITE">
<font>
<Font name="System Bold" size="20.0"/>
</font>
</Label>
<JFXSpinner fx:id="progress" layoutX="98.0" layoutY="226.0" prefHeight="100.0" prefWidth="100.0"/>
<Label fx:id="description" layoutX="216.0" layoutY="226.0" prefHeight="100.0" prefWidth="380.0"
text="..." textFill="WHITE">
<font>
<Font name="System Bold" size="16.0"/>
</font>
</Label>
</children>
</Pane>
</children>
<stylesheets>
<URL value="@update.css"/>
<URL value="@../../styles.css"/>
</stylesheets>
<Pane fx:id="overlay" prefHeight="450.0" prefWidth="694.0" xmlns="http://javafx.com/javafx/8.0.201" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Pane prefHeight="450.0" prefWidth="694.0" styleClass="downloadPane">
<children>
<Label fx:id="utitle" alignment="CENTER" layoutX="100.0" layoutY="125.0" prefHeight="30.0" prefWidth="495.0" text="Загрузка обновления..." textFill="WHITE">
<font>
<Font name="System Bold" size="20.0" />
</font>
</Label>
<JFXSpinner fx:id="progress" layoutX="98.0" layoutY="226.0" prefHeight="100.0" prefWidth="100.0" />
<Label fx:id="description" layoutX="216.0" layoutY="226.0" prefHeight="100.0" prefWidth="380.0" text="..." textFill="WHITE">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
</children>
</Pane>
<Pane id="mask" layoutX="-1.0" opacity="0.0" prefHeight="450.0" prefWidth="694.0" visible="false" />
</children>
<stylesheets>
<URL value="@update.css" />
<URL value="@../../styles.css" />
</stylesheets>
</Pane>

View file

@ -4,6 +4,9 @@ var update = {
initOverlay: function() {
update.overlay = loadFXML("dialog/overlay/update/update.fxml");
//var updateLayout = update.overlay.lookup("#overlay");
//serverPaneLayout = updateLayout;
update.title = update.overlay.lookup("#utitle");
update.description = update.overlay.lookup("#description");
update.progress = update.overlay.lookup("#progress");

View file

@ -15,62 +15,51 @@
<!-- DrLeonardo Design -->
<Pane fx:id="loginPane" prefWidth="740.0" xmlns="http://javafx.com/javafx/8.0.201" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Pane fx:id="layout" prefWidth="740.0">
<children>
<Pane fx:id="authPane" layoutX="422.0" prefHeight="411.0" prefWidth="286.0" styleClass="loginPane">
<children>
<Pane fx:id="logo" layoutX="72.0" layoutY="62.0" prefWidth="124.0" styleClass="logo">
</Pane>
<JFXTextField id="login" alignment="CENTER" focusColor="#5fd97a" layoutX="34.0" layoutY="196.0"
promptText="Логин" unFocusColor="#dadada"/>
<JFXPasswordField id="password" alignment="CENTER" focusColor="#5fd97a" layoutX="34.0"
layoutY="249.0" promptText="Пароль" unFocusColor="#dadada"/>
<JFXButton id="goAuth" layoutX="34.0" layoutY="370.0" styleClass="auth" text="ВОЙТИ"/>
<JFXCheckBox id="rememberchb" fx:id="savePassword" checkedColor="#5fd97a"
contentDisplay="CENTER" layoutX="63.0" layoutY="297.0" prefWidth="144.0"
text="Сохранить пароль" textFill="#dadada" unCheckedColor="#909090"/>
<JFXComboBox id="authOptions" fx:id="authOptions" focusColor="#5fd97a" layoutX="34.0"
layoutY="341.0" prefHeight="25.0" prefWidth="200.0" promptText="Авторизация 1"
unFocusColor="#70666600">
<styleClass>
<String fx:value="combologin"/>
<String fx:value="combologin-popup"/>
</styleClass>
</JFXComboBox>
<Hyperlink id="link" fx:id="link" layoutX="94.0" layoutY="422.0" prefHeight="19.0"
prefWidth="81.0" textAlignment="CENTER"/>
</children>
</Pane>
<JFXMasonryPane fx:id="news" prefHeight="432.0" prefWidth="423.0" styleClass="news"/>
</children>
</Pane>
<Pane id="mask" opacity="0.0" prefHeight="425.0" prefWidth="694.0" visible="false"/>
<Pane fx:id="bar" layoutX="696.0" prefHeight="425.0" prefWidth="43.0" styleClass="bar">
<children>
<JFXButton id="hide" alignment="CENTER" contentDisplay="CENTER" layoutY="45.0" ripplerFill="#646464"
text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="MINUS" size="30" textAlignment="CENTER"/>
</graphic>
</JFXButton>
<JFXButton id="close" alignment="CENTER" contentDisplay="CENTER" ripplerFill="#fb8c8c" text=""
textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="CLOSE" size="30" textAlignment="CENTER"/>
</graphic>
</JFXButton>
<JFXButton id="discord" alignment="CENTER" contentDisplay="CENTER" layoutY="370.0" ripplerFill="#646464"
text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="#5fd97a" glyphName="MESSAGE_TEXT" size="20"
textAlignment="CENTER"/>
</graphic>
</JFXButton>
</children>
</Pane>
</children>
<stylesheets>
<URL value="@../../styles.css"/>
</stylesheets>
<children>
<Pane fx:id="layout" prefWidth="740.0">
<children>
<Pane fx:id="authPane" layoutX="424.0" prefHeight="411.0" prefWidth="286.0" styleClass="loginPane">
<children>
<Pane fx:id="logo" layoutX="72.0" layoutY="62.0" prefWidth="124.0" styleClass="logo">
</Pane>
<JFXTextField id="login" alignment="CENTER" focusColor="#5fd97a" layoutX="34.0" layoutY="196.0" promptText="Логин" unFocusColor="#dadada" />
<JFXPasswordField id="password" alignment="CENTER" focusColor="#5fd97a" layoutX="34.0" layoutY="249.0" promptText="Пароль" unFocusColor="#dadada" />
<JFXButton id="goAuth" layoutX="34.0" layoutY="370.0" styleClass="auth" text="ВОЙТИ" />
<JFXCheckBox id="rememberchb" fx:id="savePassword" checkedColor="#5fd97a" contentDisplay="CENTER" layoutX="63.0" layoutY="297.0" prefWidth="144.0" text="Сохранить пароль" textFill="#dadada" unCheckedColor="#909090" />
<JFXComboBox id="authOptions" fx:id="authOptions" focusColor="#5fd97a" layoutX="34.0" layoutY="341.0" prefHeight="25.0" prefWidth="200.0" promptText="Способ авторизации" unFocusColor="#70666600">
<styleClass>
<String fx:value="combologin" />
<String fx:value="combologin-popup" />
</styleClass>
</JFXComboBox>
<Hyperlink id="link" fx:id="link" layoutX="94.0" layoutY="422.0" prefHeight="19.0" prefWidth="81.0" textAlignment="CENTER" />
</children>
</Pane>
<JFXMasonryPane fx:id="news" prefHeight="432.0" prefWidth="423.0" styleClass="news" />
</children>
</Pane>
<Pane id="mask" opacity="0.0" prefHeight="450.0" prefWidth="694.0" visible="false" />
<Pane fx:id="bar" layoutX="694.0" prefHeight="425.0" prefWidth="43.0" styleClass="bar">
<children>
<JFXButton id="hide" alignment="CENTER" contentDisplay="CENTER" layoutY="45.0" ripplerFill="#646464" text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="MINUS" size="30" textAlignment="CENTER" />
</graphic>
</JFXButton>
<JFXButton id="close" alignment="CENTER" contentDisplay="CENTER" ripplerFill="#fb8c8c" text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="CLOSE" size="30" textAlignment="CENTER" />
</graphic>
</JFXButton>
<JFXButton id="discord" alignment="CENTER" contentDisplay="CENTER" layoutY="370.0" ripplerFill="#646464" text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="#5fd97a" glyphName="MESSAGE_TEXT" size="20" textAlignment="CENTER" />
</graphic>
</JFXButton>
</children>
</Pane>
</children>
<stylesheets>
<URL value="@../../styles.css" />
</stylesheets>
</Pane>

View file

@ -13,127 +13,98 @@
<!-- DrLeonardo Design -->
<Pane fx:id="serverPaneLayout" maxHeight="-1.0" maxWidth="-1.0" prefWidth="740.0" visible="true"
xmlns="http://javafx.com/javafx/8.0.201" xmlns:fx="http://javafx.com/fxml/1">
<Pane fx:id="serverPaneLayout" maxHeight="-1.0" maxWidth="-1.0" prefWidth="740.0" visible="true" xmlns="http://javafx.com/javafx/8.0.201" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Pane fx:id="layout" maxHeight="-1.0" maxWidth="-1.0" prefWidth="740.0" visible="true"
xmlns="http://javafx.com/javafx/8.0.201" xmlns:fx="http://javafx.com/fxml/1">
<Pane fx:id="layout" maxHeight="-1.0" maxWidth="-1.0" prefWidth="740.0" visible="true" xmlns="http://javafx.com/javafx/8.0.201" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Pane id="serverPane" prefHeight="450.0" prefWidth="693.0">
<Pane id="serverPane" prefHeight="450.0" prefWidth="694.0">
<children>
<ScrollPane id="serverlist" hbarPolicy="NEVER" layoutX="1.0" prefHeight="450.0"
prefWidth="307.0" visible="true">
<ScrollPane id="serverlist" hbarPolicy="NEVER" layoutX="1.0" prefHeight="450.0" prefWidth="307.0" visible="true">
<content>
<FlowPane focusTraversable="false" prefHeight="446.0" prefWidth="306.0"
prefWrapLength="0.0" rowValignment="TOP" vgap="10.0" visible="true">
<FlowPane focusTraversable="false" prefHeight="446.0" prefWidth="306.0" prefWrapLength="0.0" rowValignment="TOP" vgap="10.0" visible="true">
<JFXButton id="servercontainer" ripplerFill="#ffffff80" text="">
<FlowPane.margin>
<Insets bottom="10.0"/>
</FlowPane.margin>
</JFXButton>
<Insets bottom="10.0" />
</FlowPane.margin></JFXButton>
<padding>
<Insets left="10.0" top="10.0"/>
<Insets left="10.0" top="10.0" />
</padding>
</FlowPane>
</content>
</ScrollPane>
<Pane id="serverentrance" layoutX="306.0" prefHeight="425.0" prefWidth="388.0"
styleClass="serverentrance">
<Pane id="serverentrance" layoutX="308.0" prefHeight="425.0" prefWidth="388.0" styleClass="serverentrance">
<children>
<ScrollPane id="serverinfo" hbarPolicy="NEVER" layoutX="4.0" layoutY="53.0"
pannable="true" prefHeight="322.0" prefWidth="381.0" visible="true">
<ScrollPane id="serverinfo" hbarPolicy="NEVER" layoutX="4.0" layoutY="53.0" pannable="true" prefHeight="322.0" prefWidth="381.0" visible="true">
<content>
<FlowPane id="" focusTraversable="false" orientation="HORIZONTAL"
prefHeight="310.0" prefWidth="369.0" rowValignment="TOP"
visible="true">
<FlowPane id="" focusTraversable="false" orientation="HORIZONTAL" prefHeight="310.0" prefWidth="369.0" rowValignment="TOP" visible="true">
<padding>
<Insets bottom="10.0" left="15.0" top="7.0"/>
<Insets bottom="10.0" left="15.0" top="7.0" />
</padding>
<children>
<Label id="serverDescription" alignment="TOP_LEFT" contentDisplay="LEFT"
nodeOrientation="LEFT_TO_RIGHT" prefHeight="274.0"
prefWidth="349.0"
text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla convallis magna tellus, in bibendum tortor dignissim non. Phasellus vel tincidunt nulla, eu convallis ligula. Suspendisse ut diam vestibulum, tincidunt neque ut, posuere risus. Pellentesque posuere molestie eros, quis laoreet ante ornare quis. Morbi eu tortor fermentum, iaculis risus sit amet, fringilla augue. Aenean nulla purus, rutrum non sapien et, convallis tincidunt purus. Vivamus a eros pulvinar, dignissim leo lacinia, sodales nulla. Aliquam tortor augue, cursus a rutrum viverra, consequat non tellus. Donec porta nisl sed quam dictum commodo. Sed et vulputate dolor. Morbi ultrices justo vitae convallis semper. Donec sodales velit vel velit faucibus, et scelerisque felis finibus. Sed rutrum lacinia mauris, porta cursus mauris tempor eu. Duis turpis nulla, dictum vitae commodo rhoncus, pretium in turpis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos."
textAlignment="JUSTIFY" textFill="#141414" wrapText="true"/>
<Label id="serverDescription" alignment="TOP_LEFT" contentDisplay="LEFT" nodeOrientation="LEFT_TO_RIGHT" prefHeight="274.0" prefWidth="349.0" text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla convallis magna tellus, in bibendum tortor dignissim non. Phasellus vel tincidunt nulla, eu convallis ligula. Suspendisse ut diam vestibulum, tincidunt neque ut, posuere risus. Pellentesque posuere molestie eros, quis laoreet ante ornare quis. Morbi eu tortor fermentum, iaculis risus sit amet, fringilla augue. Aenean nulla purus, rutrum non sapien et, convallis tincidunt purus. Vivamus a eros pulvinar, dignissim leo lacinia, sodales nulla. Aliquam tortor augue, cursus a rutrum viverra, consequat non tellus. Donec porta nisl sed quam dictum commodo. Sed et vulputate dolor. Morbi ultrices justo vitae convallis semper. Donec sodales velit vel velit faucibus, et scelerisque felis finibus. Sed rutrum lacinia mauris, porta cursus mauris tempor eu. Duis turpis nulla, dictum vitae commodo rhoncus, pretium in turpis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos." textAlignment="JUSTIFY" textFill="#141414" wrapText="true" />
</children>
</FlowPane>
</content>
</ScrollPane>
<JFXButton id="clientLaunch" layoutX="19.0" layoutY="380.0" prefHeight="51.0"
prefWidth="285.0" styleClass="clientLaunch" text="ИГРАТЬ">
<JFXButton id="clientLaunch" layoutX="19.0" layoutY="380.0" prefHeight="51.0" prefWidth="285.0" styleClass="clientLaunch" text="ИГРАТЬ">
<font>
<Font size="22.0"/>
<Font size="22.0" />
</font>
</JFXButton>
<JFXButton id="clientSettings" alignment="CENTER" centerShape="false"
contentDisplay="CENTER" layoutX="305.0" layoutY="380.0" prefHeight="51.0"
prefWidth="60.0" ripplerFill="#84da96" styleClass="clientSettings" text=""
textAlignment="CENTER">
<JFXButton id="clientSettings" alignment="CENTER" centerShape="false" contentDisplay="CENTER" layoutX="305.0" layoutY="380.0" prefHeight="51.0" prefWidth="60.0" ripplerFill="#84da96" styleClass="clientSettings" text="" textAlignment="CENTER">
<graphic>
<FontAwesomeIconView fill="WHITE" glyphName="SLIDERS" size="30.0"/>
</graphic>
</JFXButton>
<Label id="serverStatus" alignment="TOP_RIGHT" contentDisplay="RIGHT" layoutX="165.0"
layoutY="12.0" prefHeight="25.0" prefWidth="97.0" text="12/100"
textAlignment="RIGHT" textFill="WHITE">
<FontAwesomeIconView fill="WHITE" glyphName="SLIDERS" size="30.0" />
</graphic></JFXButton>
<Label id="serverStatus" alignment="TOP_RIGHT" contentDisplay="RIGHT" layoutX="165.0" layoutY="12.0" prefHeight="25.0" prefWidth="97.0" text="12/100" textAlignment="RIGHT" textFill="WHITE">
<font>
<Font name="System Bold" size="16.0"/>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<Label id="serverLabel" layoutX="20.0" layoutY="11.0" prefHeight="27.0"
prefWidth="203.0" text="СЕРВЕР">
<Label id="serverLabel" layoutX="20.0" layoutY="11.0" prefHeight="27.0" prefWidth="203.0" text="СЕРВЕР">
<font>
<Font name="System Bold" size="18.0"/>
<Font name="System Bold" size="18.0" />
</font>
</Label>
<JFXButton id="logout" alignment="CENTER" contentDisplay="CENTER" layoutX="295.0"
layoutY="12.0" prefHeight="25.0" prefWidth="81.0" ripplerFill="#61616100"
text="Выйти" textAlignment="CENTER"/>
<JFXButton id="logout" alignment="CENTER" contentDisplay="CENTER" layoutX="295.0" layoutY="12.0" prefHeight="25.0" prefWidth="81.0" ripplerFill="#61616100" text="Выйти" textAlignment="CENTER" />
</children>
</Pane>
</children>
</Pane>
</children>
</Pane>
<Pane fx:id="bar" layoutX="696.0" prefHeight="425.0" prefWidth="43.0" styleClass="bar">
<Pane fx:id="bar" layoutX="694.0" prefHeight="425.0" prefWidth="43.0" styleClass="bar">
<children>
<JFXButton id="hide" alignment="CENTER" contentDisplay="CENTER" layoutY="45.0" ripplerFill="#646464"
text="" textAlignment="CENTER">
<JFXButton id="hide" alignment="CENTER" contentDisplay="CENTER" layoutY="45.0" ripplerFill="#646464" text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="MINUS" size="30" textAlignment="CENTER"/>
<MaterialDesignIconView fill="WHITE" glyphName="MINUS" size="30" textAlignment="CENTER" />
</graphic>
</JFXButton>
<JFXButton id="close" alignment="CENTER" contentDisplay="CENTER" ripplerFill="#fb8c8c" text=""
textAlignment="CENTER">
<JFXButton id="close" alignment="CENTER" contentDisplay="CENTER" ripplerFill="#fb8c8c" text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="CLOSE" size="30" textAlignment="CENTER"/>
<MaterialDesignIconView fill="WHITE" glyphName="CLOSE" size="30" textAlignment="CENTER" />
</graphic>
</JFXButton>
<JFXButton id="discord" alignment="CENTER" contentDisplay="CENTER" layoutY="380.0" ripplerFill="#646464"
text="" textAlignment="CENTER">
<JFXButton id="discord" alignment="CENTER" contentDisplay="CENTER" layoutY="380.0" ripplerFill="#646464" text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="#5fd97a" glyphName="MESSAGE_TEXT" size="20" smooth="false"
textAlignment="CENTER"/>
<MaterialDesignIconView fill="#5fd97a" glyphName="MESSAGE_TEXT" size="20" smooth="false" textAlignment="CENTER" />
</graphic>
</JFXButton>
<JFXButton id="settings" alignment="CENTER" contentDisplay="CENTER" layoutY="90.0" ripplerFill="#646464"
text="" textAlignment="CENTER">
<JFXButton id="settings" alignment="CENTER" contentDisplay="CENTER" layoutY="90.0" ripplerFill="#646464" text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="SETTINGS" size="20" textAlignment="CENTER"/>
<MaterialDesignIconView fill="WHITE" glyphName="SETTINGS" size="20" textAlignment="CENTER" />
</graphic>
</JFXButton>
<JFXButton id="goConsole" alignment="CENTER" contentDisplay="CENTER" layoutY="138.0"
ripplerFill="#646464" text="" textAlignment="CENTER">
<JFXButton id="goConsole" alignment="CENTER" contentDisplay="CENTER" layoutY="138.0" ripplerFill="#646464" text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="CONSOLE" size="20" textAlignment="CENTER"/>
<MaterialDesignIconView fill="WHITE" glyphName="CONSOLE" size="20" textAlignment="CENTER" />
</graphic>
</JFXButton>
</children>
</Pane>
<Pane id="mask" opacity="0.0" prefHeight="425.0" prefWidth="694.0" visible="false"/>
<Pane id="mask" opacity="0.0" prefHeight="450.0" prefWidth="694.0" visible="false" />
</children>
<stylesheets>
<URL value="@../../styles.css"/>
<URL value="@../../servers.css"/>
<URL value="@../../styles.css" />
<URL value="@../../servers.css" />
</stylesheets>
</Pane>

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXToggleButton?>
<?import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIconView?>
<?import java.net.URL?>
<?import javafx.geometry.Insets?>
@ -9,59 +8,55 @@
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Line?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<!-- DrLeonardo Design -->
<Pane fx:id="background" prefHeight="450.0" prefWidth="738.0" xmlns="http://javafx.com/javafx/8.0.201"
xmlns:fx="http://javafx.com/fxml/1">
<children>
<Pane id="optionsPane" prefHeight="450.0" prefWidth="692.0" styleClass="optionsPane">
<children>
<JFXToggleButton fx:id="presset" layoutX="30.0" layoutY="10.0" opacity="0.21" styleClass="pressetLight"
text="Presset 1"/>
<JFXToggleButton fx:id="presset" layoutX="287.0" layoutY="10.0" opacity="0.21"
styleClass="pressetMedium" text="Presset 2"/>
<JFXToggleButton fx:id="isPresset" layoutX="528.0" layoutY="10.0" opacity="0.21" prefHeight="58.0"
prefWidth="134.0" styleClass="pressetHigh" text="Presset 3" wrapText="true"/>
<Line endX="595.0" layoutX="100.0" layoutY="80.0" startX="-100.0" stroke="#5b3636"
styleClass="lineHead"/>
<ScrollPane id="modlist" layoutY="84.0" prefHeight="364.0" prefWidth="693.0">
<content>
<VBox prefHeight="360.0" prefWidth="678.0">
<children>
</children>
<padding>
<Insets left="10.0" top="8.0"/>
</padding>
</VBox>
</content>
</ScrollPane>
</children>
</Pane>
<Pane fx:id="bar" layoutX="692.0" prefHeight="425.0" prefWidth="43.0" styleClass="bar">
<children>
<JFXButton id="hide" alignment="CENTER" contentDisplay="CENTER" layoutY="45.0" ripplerFill="#646464"
text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="MINUS" size="30" textAlignment="CENTER"/>
</graphic>
</JFXButton>
<JFXButton id="close" alignment="CENTER" contentDisplay="CENTER" ripplerFill="#fb8c8c" text=""
textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="CLOSE" size="30" textAlignment="CENTER"/>
</graphic>
</JFXButton>
<JFXButton id="back" alignment="CENTER" contentDisplay="CENTER" layoutY="405.0" ripplerFill="#646464"
text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="CHEVRON_LEFT" size="30" textAlignment="CENTER"/>
</graphic>
</JFXButton>
</children>
</Pane>
</children>
<stylesheets>
<URL value="@../../styles.css"/>
</stylesheets>
<Pane fx:id="background" prefHeight="450.0" prefWidth="740.0" xmlns="http://javafx.com/javafx/8.0.201" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Pane id="optionsPane" prefHeight="450.0" prefWidth="692.0" styleClass="optionsPane">
<children>
<Line endX="595.0" layoutX="100.0" layoutY="46.0" startX="-100.0" stroke="#5b3636" styleClass="lineHead" />
<ScrollPane id="modlist" layoutY="46.0" prefHeight="402.0" prefWidth="693.0">
<content>
<VBox prefHeight="397.0" prefWidth="678.0">
<children>
</children>
<padding>
<Insets left="10.0" top="8.0" />
</padding>
</VBox>
</content>
</ScrollPane>
<Text fill="#393939" layoutX="15.0" layoutY="28.0" strokeType="OUTSIDE" strokeWidth="0.0" text="ОПЦИОНАЛЬНЫЕ МОДИФИКАЦИИ" wrappingWidth="265.904296875">
<font>
<Font name="System Bold" size="13.0" />
</font>
</Text>
</children>
</Pane>
<Pane fx:id="bar" layoutX="694.0" prefHeight="425.0" prefWidth="43.0" styleClass="bar">
<children>
<JFXButton id="hide" alignment="CENTER" contentDisplay="CENTER" layoutY="45.0" ripplerFill="#646464" text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="MINUS" size="30" textAlignment="CENTER" />
</graphic>
</JFXButton>
<JFXButton id="close" alignment="CENTER" contentDisplay="CENTER" ripplerFill="#fb8c8c" text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="CLOSE" size="30" textAlignment="CENTER" />
</graphic>
</JFXButton>
<JFXButton id="back" alignment="CENTER" contentDisplay="CENTER" layoutY="405.0" ripplerFill="#646464" text="" textAlignment="CENTER">
<graphic>
<MaterialDesignIconView fill="WHITE" glyphName="CHEVRON_LEFT" size="30" textAlignment="CENTER" />
</graphic>
</JFXButton>
</children>
</Pane>
</children>
<stylesheets>
<URL value="@../../styles.css" />
</stylesheets>
</Pane>

View file

@ -34,8 +34,6 @@ #serverStatus{
/* Mask */
#mask {
-fx-effect: DropShadow( gaussian , rgba(255,255,255,0.5) , 0,0,0,1 );
-fx-pref-width: 692px;
-fx-pref-height: 450px;
}
/** Errors **/
@ -194,7 +192,7 @@ .combologin-popup .list-view {
.combologin .list-cell:filled:selected .text,
.combologin .list-cell:filled:selected .text {
-fx-fill: #909090;
-fx-fill: #323232;
}
.combologin .arrow,
@ -211,13 +209,13 @@ .combologin-popup .list-view .list-cell:filled:selected, .combologin-popup .list
{
-fx-background: -fx-accent;
-fx-background-color: -fx-selection-bar;
-fx-text-fill: -fx-selection-bar-text;
-fx-text-fill: #909090;
}
.combologin-popup .list-view .list-cell:filled:hover
{
-fx-background-color: white;
-fx-text-fill: -fx-text-inner-color;
-fx-text-fill: #909090;
}
/** web**/

View file

@ -3,6 +3,7 @@
import ru.gravit.launcher.client.ClientModuleManager;
import ru.gravit.launcher.client.DirBridge;
import ru.gravit.launcher.client.FunctionalBridge;
import ru.gravit.launcher.client.LauncherUpdateController;
import ru.gravit.launcher.guard.LauncherGuardManager;
import ru.gravit.launcher.gui.JSRuntimeProvider;
import ru.gravit.launcher.gui.RuntimeProvider;
@ -11,6 +12,7 @@
import ru.gravit.launcher.request.Request;
import ru.gravit.launcher.request.RequestException;
import ru.gravit.launcher.request.auth.RestoreSessionRequest;
import ru.gravit.launcher.request.update.UpdateRequest;
import ru.gravit.launcher.request.websockets.StandartClientWebSocketService;
import ru.gravit.utils.helper.CommonHelper;
import ru.gravit.utils.helper.EnvHelper;
@ -95,6 +97,7 @@ public void start(String... args) throws Throwable {
};
}
LauncherGuardManager.initGuard(false);
UpdateRequest.setController(new LauncherUpdateController());
Objects.requireNonNull(args, "args");
if (started.getAndSet(true))
throw new IllegalStateException("Launcher has been already started");

View file

@ -37,6 +37,10 @@ public class NewLauncherSettings {
public List<ClientProfile> lastProfiles = new LinkedList<>();
@LauncherAPI
public Map<String, UserSettings> userSettings = new HashMap<>();
@LauncherAPI
public boolean featureStore;
@LauncherAPI
public String consoleUnlockKey;
public static class HashedStoreEntry {
@LauncherAPI
@ -45,6 +49,8 @@ public static class HashedStoreEntry {
public String name;
@LauncherAPI
public String fullPath;
@LauncherAPI
public transient boolean needSave = false;
public HashedStoreEntry(HashedDir hdir, String name, String fullPath) {
this.hdir = hdir;
@ -59,9 +65,9 @@ public HashedStoreEntry(HashedDir hdir, String name, String fullPath) {
@LauncherAPI
public void putHDir(String name, Path path, HashedDir dir) {
String fullPath = path.toAbsolutePath().toString();
for (HashedStoreEntry e : lastHDirs) {
if (e.fullPath.equals(fullPath) && e.name.equals(name)) return;
}
lastHDirs.add(new HashedStoreEntry(dir, name, fullPath));
lastHDirs.removeIf((e) -> e.fullPath.equals(fullPath) && e.name.equals(name));
HashedStoreEntry e = new HashedStoreEntry(dir, name, fullPath);
e.needSave = true;
lastHDirs.add(e);
}
}

View file

@ -0,0 +1,139 @@
package ru.gravit.launcher.client;
import ru.gravit.launcher.NewLauncherSettings;
import ru.gravit.launcher.downloader.ListDownloader;
import ru.gravit.launcher.events.request.UpdateRequestEvent;
import ru.gravit.launcher.hasher.HashedDir;
import ru.gravit.launcher.hasher.HashedEntry;
import ru.gravit.launcher.hasher.HashedFile;
import ru.gravit.launcher.managers.SettingsManager;
import ru.gravit.launcher.request.update.UpdateRequest;
import ru.gravit.utils.helper.IOHelper;
import ru.gravit.utils.helper.LogHelper;
import java.io.File;
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.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
public class LauncherUpdateController implements UpdateRequest.UpdateController {
@Override
public void preUpdate(UpdateRequest request, UpdateRequestEvent e) {
}
@Override
public void preDiff(UpdateRequest request, UpdateRequestEvent e) {
}
@Override
public void postDiff(UpdateRequest request, UpdateRequestEvent e, HashedDir.Diff diff) throws IOException {
if(e.zip) return;
if(SettingsManager.settings.featureStore)
{
LogHelper.info("Enabled HStore feature. Find");
AtomicReference<NewLauncherSettings.HashedStoreEntry> lastEn = new AtomicReference<>(null);
ArrayList<String> removed = new ArrayList<>();
diff.mismatch.walk(File.separator, (path, name, entry) -> {
if(entry.getType() == HashedEntry.Type.DIR) {
Files.createDirectories(request.getDir().resolve(path));
return HashedDir.WalkAction.CONTINUE;
}
HashedFile file = (HashedFile) entry;
//Первый экспериментальный способ - честно обходим все возможные Store
Path ret = null;
if(lastEn.get() == null)
{
for(NewLauncherSettings.HashedStoreEntry en : SettingsManager.settings.lastHDirs)
{
ret = tryFind(en, file);
if(ret != null) {
lastEn.set(en);
break;
}
}
}
else {
ret = tryFind(lastEn.get(), file);
}
if(ret == null)
{
for(NewLauncherSettings.HashedStoreEntry en : SettingsManager.settings.lastHDirs)
{
ret = tryFind(en, file);
if(ret != null) {
lastEn.set(en);
break;
}
}
}
if(ret != null)
{
//Еще раз проверим корректность хеша
//Возможно эта проверка избыточна
//if(file.isSame(ret, true))
{
Path source = request.getDir().resolve(path);
LogHelper.debug("Copy file %s to %s", ret.toAbsolutePath().toString(), source.toAbsolutePath().toString());
//Let's go!
Files.copy(ret, source);
try(InputStream input = IOHelper.newInput(ret))
{
IOHelper.transfer(input, source);
}
entry.flag = true;
//removed.add(path.replace('\\', '/'));
}
}
return HashedDir.WalkAction.CONTINUE;
});
}
}
public Path tryFind(NewLauncherSettings.HashedStoreEntry en, HashedFile file) throws IOException
{
AtomicReference<Path> ret = new AtomicReference<>(null);
en.hdir.walk(File.separator, (path, name, entry) -> {
if(entry.getType() == HashedEntry.Type.DIR) return HashedDir.WalkAction.CONTINUE;
HashedFile tfile = (HashedFile) entry;
if(tfile.isSame(file))
{
LogHelper.dev("[DIR:%s] Found file %s in %s", en.name, name, path);
Path tdir = Paths.get(en.fullPath).resolve(path);
try {
if(tfile.isSame(tdir, true))
{
LogHelper.dev("[DIR:%s] Confirmed file %s in %s", en.name, name, path);
ret.set(tdir);
return HashedDir.WalkAction.STOP;
}
} catch (IOException e)
{
LogHelper.error("Check file error %s %s", e.getClass().getName(), e.getMessage());
}
}
return HashedDir.WalkAction.CONTINUE;
});
return ret.get();
}
@Override
public void preDownload(UpdateRequest request, UpdateRequestEvent e, List<ListDownloader.DownloadTask> adds) {
}
@Override
public void postDownload(UpdateRequest request, UpdateRequestEvent e) {
}
@Override
public void postUpdate(UpdateRequest request, UpdateRequestEvent e) {
}
}

View file

@ -0,0 +1,37 @@
package ru.gravit.launcher.console;
import ru.gravit.launcher.managers.SettingsManager;
import ru.gravit.utils.command.Command;
import ru.gravit.utils.helper.LogHelper;
public class FeatureCommand extends Command {
@Override
public String getArgsDescription() {
return "[feature] [true/false]";
}
@Override
public String getUsageDescription() {
return "Enable or disable feature";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 2);
boolean enabled = Boolean.valueOf(args[1]);
switch (args[0])
{
case "store":
{
SettingsManager.settings.featureStore = enabled;
break;
}
default:
{
LogHelper.info("Features: [store]");
return;
}
}
LogHelper.info("Feature %s %s", args[0], enabled ? "enabled" : "disabled");
}
}

View file

@ -1,6 +1,7 @@
package ru.gravit.launcher.console;
import ru.gravit.launcher.managers.ConsoleManager;
import ru.gravit.launcher.managers.SettingsManager;
import ru.gravit.utils.command.Command;
import ru.gravit.utils.helper.LogHelper;
@ -22,6 +23,8 @@ public void invoke(String... args) throws Exception {
LogHelper.info("Unlock successful");
ConsoleManager.unlock();
ConsoleManager.handler.unregisterCommand("unlock");
LogHelper.info("Write unlock key");
SettingsManager.settings.consoleUnlockKey = args[0];
} else {
LogHelper.error("Unlock key incorrect");
}

View file

@ -0,0 +1,51 @@
package ru.gravit.launcher.console.store;
import ru.gravit.launcher.NewLauncherSettings;
import ru.gravit.launcher.managers.SettingsManager;
import ru.gravit.utils.command.Command;
import ru.gravit.utils.helper.LogHelper;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class CopyStoreDirCommand extends Command {
@Override
public String getArgsDescription() {
return "[index] [overwrite(true/false)]";
}
@Override
public String getUsageDescription() {
return "Copy dir in GravitLauncherStore";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 2);
int ind = 1;
int index = Integer.valueOf(args[0]);
boolean overwrite = Boolean.valueOf(args[1]);
for(NewLauncherSettings.HashedStoreEntry e : SettingsManager.settings.lastHDirs)
{
if(ind == index)
{
LogHelper.info("Copy [%d] FullPath: %s name: %s", ind, e.fullPath, e.name);
Path path = Paths.get(e.fullPath);
if(!Files.isDirectory(path))
{
LogHelper.error("Directory %s not found", path.toAbsolutePath().toString());
return;
}
Path target = Paths.get(SettingsManager.settings.updatesDirPath).resolve(e.name);
if(Files.exists(target) && !overwrite)
{
LogHelper.error("Directory %s found, flag overwrite not found", target.toAbsolutePath().toString());
return;
}
Files.copy(path, target);
}
ind++;
}
}
}

View file

@ -0,0 +1,51 @@
package ru.gravit.launcher.console.store;
import ru.gravit.launcher.NewLauncherSettings;
import ru.gravit.launcher.managers.SettingsManager;
import ru.gravit.utils.command.Command;
import ru.gravit.utils.helper.LogHelper;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class LinkStoreDirCommand extends Command {
@Override
public String getArgsDescription() {
return "[index]";
}
@Override
public String getUsageDescription() {
return "Create symlink to GravitLauncherStore directory";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
int ind = 1;
int index = Integer.valueOf(args[0]);
for(NewLauncherSettings.HashedStoreEntry e : SettingsManager.settings.lastHDirs)
{
if(ind == index)
{
LogHelper.info("Copy [%d] FullPath: %s name: %s", ind, e.fullPath, e.name);
Path path = Paths.get(e.fullPath);
if(!Files.isDirectory(path))
{
LogHelper.error("Directory %s not found", path.toAbsolutePath().toString());
return;
}
Path target = Paths.get(SettingsManager.settings.updatesDirPath).resolve(e.name);
if(Files.exists(target))
{
LogHelper.error("Directory %s already exists", target.toAbsolutePath().toString());
return;
}
Files.createSymbolicLink(path, target);
}
ind++;
}
}
}

View file

@ -0,0 +1,28 @@
package ru.gravit.launcher.console.store;
import ru.gravit.launcher.NewLauncherSettings;
import ru.gravit.launcher.managers.SettingsManager;
import ru.gravit.utils.command.Command;
import ru.gravit.utils.helper.LogHelper;
public class StoreListCommand extends Command {
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return "List GravitLauncherStore";
}
@Override
public void invoke(String... args) throws Exception {
int ind = 1;
for(NewLauncherSettings.HashedStoreEntry e : SettingsManager.settings.lastHDirs)
{
LogHelper.info("[%d] FullPath: %s name: %s", ind, e.fullPath, e.name);
ind++;
}
}
}

View file

@ -1,8 +1,13 @@
package ru.gravit.launcher.managers;
import ru.gravit.launcher.Launcher;
import ru.gravit.launcher.console.FeatureCommand;
import ru.gravit.launcher.console.UnlockCommand;
import ru.gravit.launcher.console.admin.ExecCommand;
import ru.gravit.launcher.console.admin.LogListenerCommand;
import ru.gravit.launcher.console.store.CopyStoreDirCommand;
import ru.gravit.launcher.console.store.LinkStoreDirCommand;
import ru.gravit.launcher.console.store.StoreListCommand;
import ru.gravit.utils.command.BaseCommandCategory;
import ru.gravit.utils.command.CommandHandler;
import ru.gravit.utils.command.JLineCommandHandler;
@ -15,10 +20,14 @@
import ru.gravit.utils.helper.LogHelper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class ConsoleManager {
public static CommandHandler handler;
public static Thread thread;
public static boolean isConsoleUnlock = false;
public static void initConsole() throws IOException {
CommandHandler localCommandHandler;
@ -46,14 +55,21 @@ public static void registerCommands() {
}
public static boolean checkUnlockKey(String key) {
return true;
return key.equals(Launcher.getConfig().oemUnlockKey);
}
public static void unlock() {
handler.registerCommand("debug", new DebugCommand());
handler.registerCommand("feature", new FeatureCommand());
BaseCommandCategory admin = new BaseCommandCategory();
admin.registerCommand("exec", new ExecCommand());
admin.registerCommand("logListen", new LogListenerCommand());
handler.registerCategory(new CommandHandler.Category(admin, "admin", "Server admin commands"));
BaseCommandCategory store = new BaseCommandCategory();
store.registerCommand("storeList", new StoreListCommand());
store.registerCommand("copyStoreDir", new CopyStoreDirCommand());
store.registerCommand("linkStoreDir", new LinkStoreDirCommand());
handler.registerCategory(new CommandHandler.Category(admin, "store", "Store admin commands"));
isConsoleUnlock = true;
}
}

View file

@ -8,6 +8,7 @@
import ru.gravit.launcher.serialize.HInput;
import ru.gravit.launcher.serialize.HOutput;
import ru.gravit.utils.helper.IOHelper;
import ru.gravit.utils.helper.LogHelper;
import java.io.IOException;
import java.lang.reflect.Type;
@ -24,6 +25,9 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
String fullPath = input.readString(1024);
HashedDir dir = new HashedDir(input);
settings.lastHDirs.add(new NewLauncherSettings.HashedStoreEntry(dir, dirName, fullPath));
} catch (IOException e)
{
LogHelper.error("Skip file %s exception: %s", file.toAbsolutePath().toString(), e.getMessage());
}
return super.visitFile(file, attrs);
}
@ -57,6 +61,14 @@ public void setConfig(NewLauncherSettings config) {
settings = config;
if (settings.updatesDirPath != null)
settings.updatesDir = Paths.get(settings.updatesDirPath);
if(settings.consoleUnlockKey != null && !ConsoleManager.isConsoleUnlock)
{
if(ConsoleManager.checkUnlockKey(settings.consoleUnlockKey))
{
ConsoleManager.unlock();
LogHelper.info("Console auto unlocked");
}
}
}
@LauncherAPI
@ -69,6 +81,7 @@ public void loadHDirStore(Path storePath) throws IOException {
public void saveHDirStore(Path storeProjectPath) throws IOException {
Files.createDirectories(storeProjectPath);
for (NewLauncherSettings.HashedStoreEntry e : settings.lastHDirs) {
if(!e.needSave) continue;
Path file = storeProjectPath.resolve(e.name.concat(".bin"));
if (!Files.exists(file)) Files.createFile(file);
try (HOutput output = new HOutput(IOHelper.newOutput(file))) {

View file

@ -8,6 +8,7 @@ public class AutogenConfig {
private boolean isInitModules;
public String guardType;
public String secretKeyClient;
public String oemUnlockKey;
public String guardLicenseName;
public String guardLicenseKey;
public String guardLicenseEncryptKey;

View file

@ -25,6 +25,7 @@ public static AutogenConfig getAutogenConfig() {
public final String projectname;
public final int clientPort;
public String secretKeyClient;
public String oemUnlockKey;
@LauncherAPI
public final RSAPublicKey publicKey;
@ -45,6 +46,7 @@ public LauncherConfig(HInput input) throws IOException, InvalidKeySpecException
projectname = config.projectname;
clientPort = config.clientPort;
secretKeyClient = config.secretKeyClient;
oemUnlockKey = config.oemUnlockKey;
isWarningMissArchJava = config.isWarningMissArchJava;
guardLicenseEncryptKey = config.guardLicenseEncryptKey;

View file

@ -18,6 +18,8 @@
import java.net.URL;
import java.nio.file.Path;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ListDownloader {
@FunctionalInterface
@ -49,13 +51,36 @@ public void download(String base, List<DownloadTask> applies, Path dstDirFile, D
for (DownloadTask apply : applies) {
URI u = new URL(base.concat(IOHelper.urlEncode(apply.apply).replace("%2F", "/"))).toURI();
callback.stateChanged(apply.apply, 0L, apply.size);
LogHelper.debug("Download URL: %s", u.toString());
Path targetPath = dstDirFile.resolve(apply.apply);
LogHelper.debug("Download URL: %s to file %s dir: %s", u.toString(), targetPath.toAbsolutePath().toString(), dstDirFile.toAbsolutePath().toString());
if (get == null) get = new HttpGet(u);
else {
get.reset();
get.setURI(u);
}
httpclient.execute(get, new FileDownloadResponseHandler(dstDirFile.resolve(apply.apply), apply, callback, totalCallback));
httpclient.execute(get, new FileDownloadResponseHandler(targetPath, apply, callback, totalCallback, false));
}
}
}
public void downloadZip(String base, Path dstDirFile, DownloadCallback callback, DownloadTotalCallback totalCallback) throws IOException, URISyntaxException {
/*try (CloseableHttpClient httpclient = HttpClients.custom()
.setRedirectStrategy(new LaxRedirectStrategy())
.build()) {
HttpGet get;
URI u = new URL(base).toURI();
LogHelper.debug("Download ZIP URL: %s", u.toString());
get = new HttpGet(u);
httpclient.execute(get, new FileDownloadResponseHandler(dstDirFile, callback, totalCallback, true));
}*/
try (ZipInputStream input = IOHelper.newZipInput(new URL(base))) {
for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) {
if (entry.isDirectory())
continue; // Skip directories
// Unpack entry
String name = entry.getName();
LogHelper.subInfo("Downloading file: '%s'", name);
Path fileName = IOHelper.toPath(name);
transfer(input, dstDirFile.resolve(fileName), fileName.toString(), entry.getSize(), callback, totalCallback);
}
}
}
@ -78,24 +103,62 @@ static class FileDownloadResponseHandler implements ResponseHandler<Path> {
private final DownloadTask task;
private final DownloadCallback callback;
private final DownloadTotalCallback totalCallback;
private final boolean zip;
public FileDownloadResponseHandler(Path target) {
this.target = target;
this.task = null;
this.zip = false;
callback = null;
totalCallback = null;
}
public FileDownloadResponseHandler(Path target, DownloadTask task, DownloadCallback callback, DownloadTotalCallback totalCallback) {
public FileDownloadResponseHandler(Path target, DownloadTask task, DownloadCallback callback, DownloadTotalCallback totalCallback, boolean zip) {
this.target = target;
this.task = task;
this.callback = callback;
this.totalCallback = totalCallback;
this.zip = zip;
}
public FileDownloadResponseHandler(Path target, DownloadCallback callback, DownloadTotalCallback totalCallback, boolean zip) {
this.target = target;
this.task = null;
this.callback = callback;
this.totalCallback = totalCallback;
this.zip = zip;
}
@Override
public Path handleResponse(HttpResponse response) throws IOException {
InputStream source = response.getEntity().getContent();
if(zip)
{
try(ZipInputStream input = IOHelper.newZipInput(source))
{
ZipEntry entry = input.getNextEntry();
while(entry != null)
{
if(entry.isDirectory())
{
entry = input.getNextEntry();
continue;
}
long size = entry.getSize();
String filename = entry.getName();
Path target = this.target.resolve(filename);
if(callback != null)
{
callback.stateChanged(entry.getName(), 0, entry.getSize());
}
LogHelper.dev("Resolved filename %s to %s", filename, target.toAbsolutePath().toString());
transfer(source, target, filename, size, callback, totalCallback);
entry = input.getNextEntry();
}
}
return null;
}
if (callback != null && task != null) {
callback.stateChanged(task.apply, 0, task.size);
transfer(source, this.target, task.apply, task.size, callback, totalCallback);

View file

@ -9,6 +9,8 @@ public class UpdateRequestEvent extends RequestEvent {
public HashedDir hdir;
@LauncherNetworkAPI
public String url;
@LauncherNetworkAPI
public boolean zip;
@Override
public String getType() {
@ -17,10 +19,18 @@ public String getType() {
public UpdateRequestEvent(HashedDir hdir) {
this.hdir = hdir;
this.zip = false;
}
public UpdateRequestEvent(HashedDir hdir, String url) {
this.hdir = hdir;
this.url = url;
this.zip = false;
}
public UpdateRequestEvent(HashedDir hdir, String url, boolean zip) {
this.hdir = hdir;
this.url = url;
this.zip = zip;
}
}

View file

@ -27,6 +27,20 @@
import java.util.Objects;
public final class UpdateRequest extends Request<UpdateRequestEvent> implements RequestInterface {
public interface UpdateController
{
void preUpdate(UpdateRequest request, UpdateRequestEvent e) throws IOException;
void preDiff(UpdateRequest request, UpdateRequestEvent e) throws IOException;
void postDiff(UpdateRequest request, UpdateRequestEvent e,HashedDir.Diff diff) throws IOException;
void preDownload(UpdateRequest request, UpdateRequestEvent e, List<ListDownloader.DownloadTask> adds) throws IOException;
void postDownload(UpdateRequest request, UpdateRequestEvent e) throws IOException;
void postUpdate(UpdateRequest request, UpdateRequestEvent e) throws IOException;
}
private static UpdateController controller;
public static void setController(UpdateController controller) {
UpdateRequest.controller = controller;
}
@Override
public String getType() {
@ -170,15 +184,22 @@ public double getTotalSizeMiB() {
public UpdateRequestEvent requestDo(StandartClientWebSocketService service) throws Exception {
LogHelper.debug("Start update request");
UpdateRequestEvent e = (UpdateRequestEvent) service.sendRequest(this);
if(controller != null) controller.preUpdate(this, e);
LogHelper.debug("Start update");
Launcher.profile.pushOptionalFile(e.hdir, !Launcher.profile.isUpdateFastCheck());
if(controller != null) controller.preDiff(this, e);
HashedDir.Diff diff = e.hdir.diff(localDir, matcher);
if(controller != null) controller.postDiff(this, e, diff);
final List<ListDownloader.DownloadTask> adds = new ArrayList<>();
if(controller != null) controller.preDownload(this, e, adds);
diff.mismatch.walk(IOHelper.CROSS_SEPARATOR, (path, name, entry) -> {
if (entry.getType().equals(HashedEntry.Type.FILE)) {
HashedFile file = (HashedFile) entry;
totalSize += file.size;
adds.add(new ListDownloader.DownloadTask(path, file.size));
if(!entry.flag)
{
HashedFile file = (HashedFile) entry;
totalSize += file.size;
adds.add(new ListDownloader.DownloadTask(path, file.size));
}
} else if (entry.getType().equals(HashedEntry.Type.DIR)) {
try {
Files.createDirectories(dir.resolve(path));
@ -186,13 +207,24 @@ public UpdateRequestEvent requestDo(StandartClientWebSocketService service) thro
LogHelper.error(ex);
}
}
return HashedDir.WalkAction.CONTINUE;
});
totalSize = diff.mismatch.size();
startTime = Instant.now();
updateState("UnknownFile", 0L, 100);
ListDownloader listDownloader = new ListDownloader();
listDownloader.download(e.url, adds, dir, this::updateState, (add) -> totalDownloaded += add);
LogHelper.info("Download %s to %s", dirName, dir.toAbsolutePath().toString());
if(e.zip && !adds.isEmpty())
{
listDownloader.downloadZip(e.url, dir, this::updateState, (add) -> totalDownloaded += add);
}
else
{
listDownloader.download(e.url, adds, dir, this::updateState, (add) -> totalDownloaded += add);
}
if(controller != null) controller.postDownload(this, e);
deleteExtraDir(dir, diff.extra, diff.extra.flag);
if(controller != null) controller.postUpdate(this, e);
LogHelper.debug("Update success");
return e;
}
@ -201,6 +233,11 @@ public UpdateRequestEvent requestDo(StandartClientWebSocketService service) thro
@LauncherNetworkAPI
private final String dirName;
private transient final Path dir;
public Path getDir() {
return dir;
}
private transient final FileNameMatcher matcher;
private transient final boolean digest;

View file

@ -5,10 +5,12 @@
import ru.gravit.utils.helper.LogHelper;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class WaitEventHandler implements ClientWebSocketService.EventHandler {
public HashSet<ResultEvent> requests = new HashSet<>();
public Set<ResultEvent> requests = ConcurrentHashMap.newKeySet();
@Override
public void process(ResultInterface result) {

View file

@ -6,6 +6,8 @@
import io.netty.util.CharsetUtil;
import ru.gravit.utils.helper.LogHelper;
import java.util.concurrent.TimeUnit;
public class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> {
private final WebSocketClientHandshaker handshaker;
@ -30,6 +32,9 @@ public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
handshaker.handshake(ctx.channel());
clientJSONPoint.onOpen();
ctx.executor().schedule(() -> {
ctx.channel().writeAndFlush(new PingWebSocketFrame());
}, 20L, TimeUnit.SECONDS);
}
@Override

View file

@ -338,31 +338,44 @@ public void write(HOutput output) throws IOException {
}
}
public void walk(CharSequence separator, WalkCallback callback) {
public void walk(CharSequence separator, WalkCallback callback) throws IOException {
String append = "";
walk(append, separator, callback, true);
}
public enum WalkAction
{
STOP, CONTINUE
}
@FunctionalInterface
public interface WalkCallback {
void walked(String path, String name, HashedEntry entry);
WalkAction walked(String path, String name, HashedEntry entry) throws IOException;
}
private void walk(String append, CharSequence separator, WalkCallback callback, boolean noSeparator) {
private WalkAction walk(String append, CharSequence separator, WalkCallback callback, boolean noSeparator) throws IOException {
for (Map.Entry<String, HashedEntry> entry : map.entrySet()) {
HashedEntry e = entry.getValue();
if (e.getType() == Type.FILE) {
if (noSeparator)
callback.walked(append + entry.getKey(), entry.getKey(), e);
{
WalkAction a = callback.walked(append + entry.getKey(), entry.getKey(), e);
if(a == WalkAction.STOP) return a;
}
else
callback.walked(append + separator + entry.getKey(), entry.getKey(), e);
{
WalkAction a = callback.walked(append + separator + entry.getKey(), entry.getKey(), e);
if(a == WalkAction.STOP) return a;
}
} else {
String newAppend;
if (noSeparator) newAppend = append + entry.getKey();
else newAppend = append + separator + entry.getKey();
callback.walked(newAppend, entry.getKey(), e);
((HashedDir) e).walk(newAppend, separator, callback, false);
WalkAction a = callback.walked(newAppend, entry.getKey(), e);
if(a == WalkAction.STOP) return a;
a = ((HashedDir) e).walk(newAppend, separator, callback, false);
if(a == WalkAction.STOP) return a;
}
}
return WalkAction.CONTINUE;
}
}

View file

@ -17,8 +17,8 @@ public final class Version {
public final Type release;
public static final int MAJOR = 5;
public static final int MINOR = 0;
public static final int PATCH = 0;
public static final int BUILD = 7;
public static final int PATCH = 1;
public static final int BUILD = 1;
public static final Version.Type RELEASE = Version.Type.STABLE;
@LauncherAPI

View file

@ -75,6 +75,7 @@ public String readLine() throws IOException {
try {
return reader.readLine();
} catch (UserInterruptException e) {
System.exit(0);
return null;
}
}

View file

@ -113,7 +113,10 @@ public static void debug(String format, Object... args) {
@LauncherAPI
public static void dev(String format, Object... args) {
dev(String.format(format, args));
if(isDevEnabled())
{
dev(String.format(format, args));
}
}
@LauncherAPI