Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-09-08 20:43:04 +02:00
Merge branch 'feature/1.17' of https://github.com/GeyserMC/Geyser into feature/1.17
Dieser Commit ist enthalten in:
Commit
8c0d5413c7
5
Jenkinsfile
vendored
5
Jenkinsfile
vendored
@ -22,7 +22,10 @@ pipeline {
|
|||||||
|
|
||||||
stage ('Deploy') {
|
stage ('Deploy') {
|
||||||
when {
|
when {
|
||||||
branch "master"
|
anyOf {
|
||||||
|
branch "master"
|
||||||
|
branch "floodgate-2.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
steps {
|
steps {
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>bootstrap-parent</artifactId>
|
<artifactId>bootstrap-parent</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>bootstrap-bungeecord</artifactId>
|
<artifactId>bootstrap-bungeecord</artifactId>
|
||||||
|
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>connector</artifactId>
|
<artifactId>connector</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -41,7 +41,7 @@ public final class GeyserBungeeConfiguration extends GeyserJacksonConfiguration
|
|||||||
private Path floodgateKeyPath;
|
private Path floodgateKeyPath;
|
||||||
|
|
||||||
public void loadFloodgate(GeyserBungeePlugin plugin) {
|
public void loadFloodgate(GeyserBungeePlugin plugin) {
|
||||||
Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate-bungee");
|
Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate");
|
||||||
Path geyserDataFolder = plugin.getDataFolder().toPath();
|
Path geyserDataFolder = plugin.getDataFolder().toPath();
|
||||||
Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
|
Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
|
||||||
|
|
||||||
|
@ -94,10 +94,10 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
|||||||
this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode());
|
this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode());
|
||||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||||
|
|
||||||
if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate-bungee") == null) {
|
if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate") == null) {
|
||||||
geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
|
geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
|
||||||
return;
|
return;
|
||||||
} else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) {
|
} else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate") != null) {
|
||||||
// Floodgate installed means that the user wants Floodgate authentication
|
// Floodgate installed means that the user wants Floodgate authentication
|
||||||
geyserLogger.debug("Auto-setting to Floodgate authentication.");
|
geyserLogger.debug("Auto-setting to Floodgate authentication.");
|
||||||
geyserConfig.getRemote().setAuthType("floodgate");
|
geyserConfig.getRemote().setAuthType("floodgate");
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>geyser-parent</artifactId>
|
<artifactId>geyser-parent</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>bootstrap-parent</artifactId>
|
<artifactId>bootstrap-parent</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>bootstrap-parent</artifactId>
|
<artifactId>bootstrap-parent</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>bootstrap-spigot</artifactId>
|
<artifactId>bootstrap-spigot</artifactId>
|
||||||
|
|
||||||
@ -21,7 +21,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>connector</artifactId>
|
<artifactId>connector</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -42,7 +42,7 @@ public final class GeyserSpigotConfiguration extends GeyserJacksonConfiguration
|
|||||||
private Path floodgateKeyPath;
|
private Path floodgateKeyPath;
|
||||||
|
|
||||||
public void loadFloodgate(GeyserSpigotPlugin plugin) {
|
public void loadFloodgate(GeyserSpigotPlugin plugin) {
|
||||||
Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-bukkit");
|
Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate");
|
||||||
Path geyserDataFolder = plugin.getDataFolder().toPath();
|
Path geyserDataFolder = plugin.getDataFolder().toPath();
|
||||||
Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
|
Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
|
||||||
|
|
||||||
|
@ -120,11 +120,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||||||
this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode());
|
this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode());
|
||||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||||
|
|
||||||
if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate-bukkit") == null) {
|
if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate") == null) {
|
||||||
geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
|
geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
|
||||||
this.getPluginLoader().disablePlugin(this);
|
this.getPluginLoader().disablePlugin(this);
|
||||||
return;
|
return;
|
||||||
} else if (geyserConfig.isAutoconfiguredRemote() && Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) {
|
} else if (geyserConfig.isAutoconfiguredRemote() && Bukkit.getPluginManager().getPlugin("floodgate") != null) {
|
||||||
// Floodgate installed means that the user wants Floodgate authentication
|
// Floodgate installed means that the user wants Floodgate authentication
|
||||||
geyserLogger.debug("Auto-setting to Floodgate authentication.");
|
geyserLogger.debug("Auto-setting to Floodgate authentication.");
|
||||||
geyserConfig.getRemote().setAuthType("floodgate");
|
geyserConfig.getRemote().setAuthType("floodgate");
|
||||||
|
@ -3,7 +3,7 @@ name: ${outputName}-Spigot
|
|||||||
author: ${project.organization.name}
|
author: ${project.organization.name}
|
||||||
website: ${project.organization.url}
|
website: ${project.organization.url}
|
||||||
version: ${project.version}
|
version: ${project.version}
|
||||||
softdepend: ["ViaVersion"]
|
softdepend: ["ViaVersion", "floodgate"]
|
||||||
api-version: 1.13
|
api-version: 1.13
|
||||||
commands:
|
commands:
|
||||||
geyser:
|
geyser:
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>bootstrap-parent</artifactId>
|
<artifactId>bootstrap-parent</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>bootstrap-sponge</artifactId>
|
<artifactId>bootstrap-sponge</artifactId>
|
||||||
|
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>connector</artifactId>
|
<artifactId>connector</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>bootstrap-parent</artifactId>
|
<artifactId>bootstrap-parent</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>bootstrap-standalone</artifactId>
|
<artifactId>bootstrap-standalone</artifactId>
|
||||||
|
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>connector</artifactId>
|
<artifactId>connector</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>bootstrap-parent</artifactId>
|
<artifactId>bootstrap-parent</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>bootstrap-velocity</artifactId>
|
<artifactId>bootstrap-velocity</artifactId>
|
||||||
|
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>connector</artifactId>
|
<artifactId>connector</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -6,22 +6,20 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>geyser-parent</artifactId>
|
<artifactId>geyser-parent</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.code.gson</groupId>
|
<groupId>org.geysermc.cumulus</groupId>
|
||||||
<artifactId>gson</artifactId>
|
<artifactId>cumulus</artifactId>
|
||||||
<version>2.8.2</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
<groupId>com.google.code.gson</groupId>
|
||||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
<artifactId>gson</artifactId>
|
||||||
<version>2.9.8</version>
|
<version>2.8.6</version>
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
@ -31,7 +31,6 @@ import lombok.Getter;
|
|||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public enum PlatformType {
|
public enum PlatformType {
|
||||||
|
|
||||||
ANDROID("Android"),
|
ANDROID("Android"),
|
||||||
BUNGEECORD("BungeeCord"),
|
BUNGEECORD("BungeeCord"),
|
||||||
FABRIC("Fabric"),
|
FABRIC("Fabric"),
|
||||||
|
@ -1,165 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.common.window;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import org.geysermc.common.window.button.FormImage;
|
|
||||||
import org.geysermc.common.window.component.*;
|
|
||||||
import org.geysermc.common.window.response.CustomFormResponse;
|
|
||||||
import org.geysermc.common.window.response.FormResponseData;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class CustomFormWindow extends FormWindow {
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private FormImage icon;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private List<FormComponent> content;
|
|
||||||
|
|
||||||
public CustomFormWindow(String title) {
|
|
||||||
this(title, new ArrayList<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomFormWindow(String title, List<FormComponent> content) {
|
|
||||||
this(title, content, (FormImage) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomFormWindow(String title, List<FormComponent> content, String icon) {
|
|
||||||
this(title, content, new FormImage(FormImage.FormImageType.URL, icon));
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomFormWindow(String title, List<FormComponent> content, FormImage icon) {
|
|
||||||
super("custom_form");
|
|
||||||
|
|
||||||
this.title = title;
|
|
||||||
this.content = content;
|
|
||||||
this.icon = icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addComponent(FormComponent component) {
|
|
||||||
content.add(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getJSONData() {
|
|
||||||
String toModify = "";
|
|
||||||
try {
|
|
||||||
toModify = new ObjectMapper().writeValueAsString(this);
|
|
||||||
} catch (JsonProcessingException e) { }
|
|
||||||
|
|
||||||
//We need to replace this due to Java not supporting declaring class field 'default'
|
|
||||||
return toModify.replace("defaultOptionIndex", "default")
|
|
||||||
.replace("defaultText", "default")
|
|
||||||
.replace("defaultValue", "default")
|
|
||||||
.replace("defaultStepIndex", "default");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setResponse(String data) {
|
|
||||||
if (data == null || data.trim().equalsIgnoreCase("null") || data.isEmpty()) {
|
|
||||||
closed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
Map<Integer, FormResponseData> dropdownResponses = new HashMap<Integer, FormResponseData>();
|
|
||||||
Map<Integer, String> inputResponses = new HashMap<Integer, String>();
|
|
||||||
Map<Integer, Float> sliderResponses = new HashMap<Integer, Float>();
|
|
||||||
Map<Integer, FormResponseData> stepSliderResponses = new HashMap<Integer, FormResponseData>();
|
|
||||||
Map<Integer, Boolean> toggleResponses = new HashMap<Integer, Boolean>();
|
|
||||||
Map<Integer, Object> responses = new HashMap<Integer, Object>();
|
|
||||||
Map<Integer, String> labelResponses = new HashMap<Integer, String>();
|
|
||||||
|
|
||||||
List<String> componentResponses = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
componentResponses = new ObjectMapper().readValue(data.trim(), new TypeReference<List<String>>(){});
|
|
||||||
} catch (IOException e) { }
|
|
||||||
|
|
||||||
for (String response : componentResponses) {
|
|
||||||
if (i >= content.size()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
FormComponent component = content.get(i);
|
|
||||||
if (component == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (component instanceof LabelComponent) {
|
|
||||||
LabelComponent labelComponent = (LabelComponent) component;
|
|
||||||
labelResponses.put(i, labelComponent.getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component instanceof DropdownComponent) {
|
|
||||||
DropdownComponent dropdownComponent = (DropdownComponent) component;
|
|
||||||
String option = dropdownComponent.getOptions().get(Integer.parseInt(response));
|
|
||||||
|
|
||||||
dropdownResponses.put(i, new FormResponseData(Integer.parseInt(response), option));
|
|
||||||
responses.put(i, option);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component instanceof InputComponent) {
|
|
||||||
inputResponses.put(i, response);
|
|
||||||
responses.put(i, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component instanceof SliderComponent) {
|
|
||||||
float value = Float.parseFloat(response);
|
|
||||||
sliderResponses.put(i, value);
|
|
||||||
responses.put(i, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component instanceof StepSliderComponent) {
|
|
||||||
StepSliderComponent stepSliderComponent = (StepSliderComponent) component;
|
|
||||||
String step = stepSliderComponent.getSteps().get(Integer.parseInt(response));
|
|
||||||
stepSliderResponses.put(i, new FormResponseData(Integer.parseInt(response), step));
|
|
||||||
responses.put(i, step);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component instanceof ToggleComponent) {
|
|
||||||
boolean answer = Boolean.parseBoolean(response);
|
|
||||||
toggleResponses.put(i, answer);
|
|
||||||
responses.put(i, answer);
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.response = new CustomFormResponse(responses, dropdownResponses, inputResponses,
|
|
||||||
sliderResponses, stepSliderResponses, toggleResponses, labelResponses);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.common.window;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import org.geysermc.common.window.response.FormResponse;
|
|
||||||
|
|
||||||
public abstract class FormWindow {
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final String type;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
protected FormResponse response;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
protected boolean closed;
|
|
||||||
|
|
||||||
public FormWindow(String type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lombok won't work here, so we need to make our own method
|
|
||||||
public void setResponse(FormResponse response) {
|
|
||||||
this.response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public abstract String getJSONData();
|
|
||||||
|
|
||||||
public abstract void setResponse(String response);
|
|
||||||
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.common.window;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import org.geysermc.common.window.response.ModalFormResponse;
|
|
||||||
|
|
||||||
public class ModalFormWindow extends FormWindow {
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String content;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String button1;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String button2;
|
|
||||||
|
|
||||||
public ModalFormWindow(String title, String content, String button1, String button2) {
|
|
||||||
super("modal");
|
|
||||||
|
|
||||||
this.title = title;
|
|
||||||
this.content = content;
|
|
||||||
this.button1 = button1;
|
|
||||||
this.button2 = button2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getJSONData() {
|
|
||||||
try {
|
|
||||||
return new ObjectMapper().writeValueAsString(this);
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setResponse(String data) {
|
|
||||||
if (data == null || data.equalsIgnoreCase("null")) {
|
|
||||||
closed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Boolean.parseBoolean(data)) {
|
|
||||||
response = new ModalFormResponse(0, button1);
|
|
||||||
} else {
|
|
||||||
response = new ModalFormResponse(1, button2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.common.window;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import org.geysermc.common.window.button.FormButton;
|
|
||||||
import org.geysermc.common.window.response.SimpleFormResponse;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
public class SimpleFormWindow extends FormWindow {
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String content;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private List<FormButton> buttons;
|
|
||||||
|
|
||||||
public SimpleFormWindow(String title, String content) {
|
|
||||||
this(title, content, new ArrayList<FormButton>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public SimpleFormWindow(String title, String content, List<FormButton> buttons) {
|
|
||||||
super("form");
|
|
||||||
|
|
||||||
this.title = title;
|
|
||||||
this.content = content;
|
|
||||||
this.buttons = buttons;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getJSONData() {
|
|
||||||
try {
|
|
||||||
return new ObjectMapper().writeValueAsString(this);
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setResponse(String data) {
|
|
||||||
if (data == null || data.trim().equalsIgnoreCase("null")) {
|
|
||||||
closed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int buttonID;
|
|
||||||
try {
|
|
||||||
buttonID = Integer.parseInt(data.trim());
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buttonID >= buttons.size()) {
|
|
||||||
response = new SimpleFormResponse(buttonID, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = new SimpleFormResponse(buttonID, buttons.get(buttonID));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.common.window.component;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class DropdownComponent extends FormComponent {
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String text;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private List<String> options;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private int defaultOptionIndex;
|
|
||||||
|
|
||||||
public DropdownComponent() {
|
|
||||||
super("dropdown");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addOption(String option, boolean isDefault) {
|
|
||||||
options.add(option);
|
|
||||||
if (isDefault)
|
|
||||||
defaultOptionIndex = options.size() - 1;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.common.window.component;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
public class InputComponent extends FormComponent {
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String text;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String placeholder;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String defaultText;
|
|
||||||
|
|
||||||
public InputComponent(String text, String placeholder, String defaultText) {
|
|
||||||
super("input");
|
|
||||||
|
|
||||||
this.text = text;
|
|
||||||
this.placeholder = placeholder;
|
|
||||||
this.defaultText = defaultText;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.common.window.component;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
public class SliderComponent extends FormComponent {
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String text;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private float min;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private float max;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private int step;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private float defaultValue;
|
|
||||||
|
|
||||||
public SliderComponent(String text, float min, float max, int step, float defaultValue) {
|
|
||||||
super("slider");
|
|
||||||
|
|
||||||
this.text = text;
|
|
||||||
this.min = Math.max(min, 0f);
|
|
||||||
this.max = max > this.min ? max : this.min;
|
|
||||||
if (step != -1f && step > 0)
|
|
||||||
this.step = step;
|
|
||||||
|
|
||||||
if (defaultValue != -1f)
|
|
||||||
this.defaultValue = defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.common.window.component;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class StepSliderComponent extends FormComponent {
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String text;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private List<String> steps;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private int defaultStepIndex;
|
|
||||||
|
|
||||||
public StepSliderComponent(String text) {
|
|
||||||
this(text, new ArrayList<String>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public StepSliderComponent(String text, List<String> steps) {
|
|
||||||
this(text, steps, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public StepSliderComponent(String text, List<String> steps, int defaultStepIndex) {
|
|
||||||
super("step_slider");
|
|
||||||
|
|
||||||
this.text = text;
|
|
||||||
this.steps = steps;
|
|
||||||
this.defaultStepIndex = defaultStepIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addStep(String step, boolean isDefault) {
|
|
||||||
steps.add(step);
|
|
||||||
|
|
||||||
if (isDefault)
|
|
||||||
defaultStepIndex = steps.size() - 1;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.common.window.component;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
public class ToggleComponent extends FormComponent {
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String text;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private boolean defaultValue;
|
|
||||||
|
|
||||||
public ToggleComponent(String text) {
|
|
||||||
this(text, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ToggleComponent(String text, boolean defaultValue) {
|
|
||||||
super("toggle");
|
|
||||||
|
|
||||||
this.text = text;
|
|
||||||
this.defaultValue = defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.common.window.response;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.geysermc.common.window.response.FormResponse;
|
|
||||||
import org.geysermc.common.window.response.FormResponseData;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class CustomFormResponse implements FormResponse {
|
|
||||||
|
|
||||||
private Map<Integer, Object> responses;
|
|
||||||
private Map<Integer, FormResponseData> dropdownResponses;
|
|
||||||
private Map<Integer, String> inputResponses;
|
|
||||||
private Map<Integer, Float> sliderResponses;
|
|
||||||
private Map<Integer, FormResponseData> stepSliderResponses;
|
|
||||||
private Map<Integer, Boolean> toggleResponses;
|
|
||||||
private Map<Integer, String> labelResponses;
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.common.window.response;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.geysermc.common.window.button.FormButton;
|
|
||||||
import org.geysermc.common.window.response.FormResponse;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class SimpleFormResponse implements FormResponse {
|
|
||||||
|
|
||||||
private int clickedButtonId;
|
|
||||||
private FormButton clickedButton;
|
|
||||||
}
|
|
125
common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java
Normale Datei
125
common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java
Normale Datei
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Floodgate
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.crypto;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.GCMParameterSpec;
|
||||||
|
import java.nio.Buffer;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public final class AesCipher implements FloodgateCipher {
|
||||||
|
public static final int IV_LENGTH = 12;
|
||||||
|
private static final int TAG_BIT_LENGTH = 128;
|
||||||
|
private static final String CIPHER_NAME = "AES/GCM/NoPadding";
|
||||||
|
|
||||||
|
private final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
private final Topping topping;
|
||||||
|
private SecretKey secretKey;
|
||||||
|
|
||||||
|
public void init(Key key) {
|
||||||
|
if (!"AES".equals(key.getAlgorithm())) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Algorithm was expected to be AES, but got " + key.getAlgorithm()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
secretKey = (SecretKey) key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encrypt(byte[] data) throws Exception {
|
||||||
|
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
|
||||||
|
|
||||||
|
byte[] iv = new byte[IV_LENGTH];
|
||||||
|
secureRandom.nextBytes(iv);
|
||||||
|
|
||||||
|
GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);
|
||||||
|
byte[] cipherText = cipher.doFinal(data);
|
||||||
|
|
||||||
|
if (topping != null) {
|
||||||
|
iv = topping.encode(iv);
|
||||||
|
cipherText = topping.encode(cipherText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ByteBuffer.allocate(iv.length + cipherText.length + HEADER_LENGTH + 1)
|
||||||
|
.put(IDENTIFIER) // header
|
||||||
|
.put(iv)
|
||||||
|
.put((byte) 0x21)
|
||||||
|
.put(cipherText)
|
||||||
|
.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] decrypt(byte[] cipherTextWithIv) throws Exception {
|
||||||
|
checkHeader(cipherTextWithIv);
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
|
||||||
|
|
||||||
|
int bufferLength = cipherTextWithIv.length - HEADER_LENGTH;
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(cipherTextWithIv, HEADER_LENGTH, bufferLength);
|
||||||
|
|
||||||
|
int ivLength = IV_LENGTH;
|
||||||
|
|
||||||
|
if (topping != null) {
|
||||||
|
int mark = buffer.position();
|
||||||
|
|
||||||
|
// we need the first index, the second is for the optional RawSkin
|
||||||
|
boolean found = false;
|
||||||
|
while (buffer.hasRemaining() && !found) {
|
||||||
|
if (buffer.get() == 0x21) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ivLength = buffer.position() - mark - 1; // don't include the splitter itself
|
||||||
|
// don't remove this cast, it'll cause problems if you remove it
|
||||||
|
((Buffer) buffer).position(mark); // reset to the pre-while index
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] iv = new byte[ivLength];
|
||||||
|
buffer.get(iv);
|
||||||
|
|
||||||
|
// don't remove this cast, it'll cause problems if you remove it
|
||||||
|
((Buffer) buffer).position(buffer.position() + 1); // skip splitter
|
||||||
|
|
||||||
|
byte[] cipherText = new byte[buffer.remaining()];
|
||||||
|
buffer.get(cipherText);
|
||||||
|
|
||||||
|
if (topping != null) {
|
||||||
|
iv = topping.decode(iv);
|
||||||
|
cipherText = topping.decode(cipherText);
|
||||||
|
}
|
||||||
|
|
||||||
|
GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
|
||||||
|
return cipher.doFinal(cipherText);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Floodgate
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.crypto;
|
||||||
|
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
public final class AesKeyProducer implements KeyProducer {
|
||||||
|
public static int KEY_SIZE = 128;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey produce() {
|
||||||
|
try {
|
||||||
|
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
|
||||||
|
keyGenerator.init(KEY_SIZE, getSecureRandom());
|
||||||
|
return keyGenerator.generateKey();
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new RuntimeException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey produceFrom(byte[] keyFileData) {
|
||||||
|
try {
|
||||||
|
return new SecretKeySpec(keyFileData, "AES");
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new RuntimeException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecureRandom getSecureRandom() throws NoSuchAlgorithmException {
|
||||||
|
// use Windows-PRNG for windows (default impl is SHA1PRNG)
|
||||||
|
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||||
|
return SecureRandom.getInstance("Windows-PRNG");
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// NativePRNG (which should be the default on unix-systems) can still block your
|
||||||
|
// system. Even though it isn't as bad as NativePRNGBlocking, we still try to
|
||||||
|
// prevent that if possible
|
||||||
|
return SecureRandom.getInstance("NativePRNGNonBlocking");
|
||||||
|
} catch (NoSuchAlgorithmException ignored) {
|
||||||
|
// at this point we just have to go with the default impl even if it blocks
|
||||||
|
return new SecureRandom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,16 +23,18 @@
|
|||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.common.window.response;
|
package org.geysermc.floodgate.crypto;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import java.util.Base64;
|
||||||
import lombok.Getter;
|
|
||||||
import org.geysermc.common.window.response.FormResponse;
|
|
||||||
|
|
||||||
@Getter
|
public final class Base64Topping implements Topping {
|
||||||
@AllArgsConstructor
|
@Override
|
||||||
public class ModalFormResponse implements FormResponse {
|
public byte[] encode(byte[] data) {
|
||||||
|
return Base64.getEncoder().encode(data);
|
||||||
|
}
|
||||||
|
|
||||||
private int clickedButtonId;
|
@Override
|
||||||
private String clickedButtonText;
|
public byte[] decode(byte[] data) {
|
||||||
|
return Base64.getDecoder().decode(data);
|
||||||
|
}
|
||||||
}
|
}
|
163
common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java
Normale Datei
163
common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java
Normale Datei
@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Floodgate
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.crypto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.geysermc.floodgate.util.InvalidFormatException;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.Key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for both encrypting and decrypting data
|
||||||
|
*/
|
||||||
|
public interface FloodgateCipher {
|
||||||
|
// use invalid username characters at the beginning and the end of the identifier,
|
||||||
|
// to make sure that it doesn't get messed up with usernames
|
||||||
|
byte[] IDENTIFIER = "^Floodgate^".getBytes(StandardCharsets.UTF_8);
|
||||||
|
int HEADER_LENGTH = IDENTIFIER.length;
|
||||||
|
|
||||||
|
static boolean hasHeader(String data) {
|
||||||
|
if (data.length() < IDENTIFIER.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < IDENTIFIER.length; i++) {
|
||||||
|
if (IDENTIFIER[i] != data.charAt(i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the instance by giving it the key it needs to encrypt or decrypt data
|
||||||
|
*
|
||||||
|
* @param key the key used to encrypt and decrypt data
|
||||||
|
*/
|
||||||
|
void init(Key key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts the given data using the Key provided in {@link #init(Key)}
|
||||||
|
*
|
||||||
|
* @param data the data to encrypt
|
||||||
|
* @return the encrypted data
|
||||||
|
* @throws Exception when the encryption failed
|
||||||
|
*/
|
||||||
|
byte[] encrypt(byte[] data) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts data from a String.<br> This method internally calls {@link #encrypt(byte[])}
|
||||||
|
*
|
||||||
|
* @param data the data to encrypt
|
||||||
|
* @return the encrypted data
|
||||||
|
* @throws Exception when the encryption failed
|
||||||
|
*/
|
||||||
|
default byte[] encryptFromString(String data) throws Exception {
|
||||||
|
return encrypt(data.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the given data using the Key provided in {@link #init(Key)}
|
||||||
|
*
|
||||||
|
* @param data the data to decrypt
|
||||||
|
* @return the decrypted data
|
||||||
|
* @throws Exception when the decrypting failed
|
||||||
|
*/
|
||||||
|
byte[] decrypt(byte[] data) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts a byte[] and turn it into a String.<br> This method internally calls {@link
|
||||||
|
* #decrypt(byte[])} and converts the returned byte[] into a String.
|
||||||
|
*
|
||||||
|
* @param data the data to encrypt
|
||||||
|
* @return the decrypted data in a UTF-8 String
|
||||||
|
* @throws Exception when the decrypting failed
|
||||||
|
*/
|
||||||
|
default String decryptToString(byte[] data) throws Exception {
|
||||||
|
byte[] decrypted = decrypt(data);
|
||||||
|
if (decrypted == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new String(decrypted, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts a String.<br> This method internally calls {@link #decrypt(byte[])} by converting
|
||||||
|
* the UTF-8 String into a byte[]
|
||||||
|
*
|
||||||
|
* @param data the data to decrypt
|
||||||
|
* @return the decrypted data in a byte[]
|
||||||
|
* @throws Exception when the decrypting failed
|
||||||
|
*/
|
||||||
|
default byte[] decryptFromString(String data) throws Exception {
|
||||||
|
return decrypt(data.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the header is valid. This method will throw an InvalidFormatException when the
|
||||||
|
* header is invalid.
|
||||||
|
*
|
||||||
|
* @param data the data to check
|
||||||
|
* @throws InvalidFormatException when the header is invalid
|
||||||
|
*/
|
||||||
|
default void checkHeader(byte[] data) throws InvalidFormatException {
|
||||||
|
final int identifierLength = IDENTIFIER.length;
|
||||||
|
|
||||||
|
if (data.length <= HEADER_LENGTH) {
|
||||||
|
throw new InvalidFormatException("Data length is smaller then header." +
|
||||||
|
"Needed " + HEADER_LENGTH + ", got " + data.length,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < identifierLength; i++) {
|
||||||
|
if (IDENTIFIER[i] != data[i]) {
|
||||||
|
StringBuilder receivedIdentifier = new StringBuilder();
|
||||||
|
for (byte b : IDENTIFIER) {
|
||||||
|
receivedIdentifier.append(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidFormatException(
|
||||||
|
String.format("Expected identifier %s, got %s",
|
||||||
|
new String(IDENTIFIER, StandardCharsets.UTF_8),
|
||||||
|
receivedIdentifier.toString()
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
class HeaderResult {
|
||||||
|
private int version;
|
||||||
|
private int startIndex;
|
||||||
|
}
|
||||||
|
}
|
41
common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java
Normale Datei
41
common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java
Normale Datei
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Floodgate
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.crypto;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.Key;
|
||||||
|
|
||||||
|
public interface KeyProducer {
|
||||||
|
Key produce();
|
||||||
|
Key produceFrom(byte[] keyFileData);
|
||||||
|
|
||||||
|
default Key produceFrom(Path keyFileLocation) throws IOException {
|
||||||
|
return produceFrom(Files.readAllBytes(keyFileLocation));
|
||||||
|
}
|
||||||
|
}
|
@ -23,15 +23,9 @@
|
|||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.common.window.response;
|
package org.geysermc.floodgate.crypto;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
public interface Topping {
|
||||||
import lombok.Getter;
|
byte[] encode(byte[] data);
|
||||||
|
byte[] decode(byte[] data);
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
public class FormResponseData {
|
|
||||||
|
|
||||||
private int elementID;
|
|
||||||
private String elementContent;
|
|
||||||
}
|
}
|
139
common/src/main/java/org/geysermc/floodgate/news/NewsItem.java
Normale Datei
139
common/src/main/java/org/geysermc/floodgate/news/NewsItem.java
Normale Datei
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.news;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.geysermc.floodgate.news.data.ItemData;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class NewsItem {
|
||||||
|
private final int id;
|
||||||
|
private final String project;
|
||||||
|
private final boolean active;
|
||||||
|
private final NewsType type;
|
||||||
|
private final ItemData data;
|
||||||
|
private final boolean priority;
|
||||||
|
private final String message;
|
||||||
|
private final Set<NewsItemAction> actions;
|
||||||
|
private final String url;
|
||||||
|
|
||||||
|
private NewsItem(int id, String project, boolean active, NewsType type, ItemData data,
|
||||||
|
boolean priority, String message, Set<NewsItemAction> actions, String url) {
|
||||||
|
this.id = id;
|
||||||
|
this.project = project;
|
||||||
|
this.active = active;
|
||||||
|
this.type = type;
|
||||||
|
this.data = data;
|
||||||
|
this.priority = priority;
|
||||||
|
this.message = message;
|
||||||
|
this.actions = Collections.unmodifiableSet(actions);
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NewsItem readItem(JsonObject newsItem) {
|
||||||
|
NewsType newsType = NewsType.getByName(newsItem.get("type").getAsString());
|
||||||
|
if (newsType == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject messageObject = newsItem.getAsJsonObject("message");
|
||||||
|
NewsItemMessage itemMessage = NewsItemMessage.getById(messageObject.get("id").getAsInt());
|
||||||
|
|
||||||
|
String message = "Received an unknown news message type. Please update";
|
||||||
|
if (itemMessage != null) {
|
||||||
|
message = itemMessage.getFormattedMessage(messageObject.getAsJsonArray("args"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<NewsItemAction> actions = new HashSet<>();
|
||||||
|
for (JsonElement actionElement : newsItem.getAsJsonArray("actions")) {
|
||||||
|
NewsItemAction action = NewsItemAction.getByName(actionElement.getAsString());
|
||||||
|
if (action != null) {
|
||||||
|
actions.add(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NewsItem(
|
||||||
|
newsItem.get("id").getAsInt(),
|
||||||
|
newsItem.get("project").getAsString(),
|
||||||
|
newsItem.get("active").getAsBoolean(),
|
||||||
|
newsType,
|
||||||
|
newsType.read(newsItem.getAsJsonObject("data")),
|
||||||
|
newsItem.get("priority").getAsBoolean(),
|
||||||
|
message,
|
||||||
|
actions,
|
||||||
|
newsItem.get("url").getAsString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProject() {
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NewsType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemData getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends ItemData> T getDataAs(Class<T> type) {
|
||||||
|
return (T) data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRawMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message + " See " + getUrl() + " for more information.";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<NewsItemAction> getActions() {
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
44
common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java
Normale Datei
44
common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java
Normale Datei
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.news;
|
||||||
|
|
||||||
|
public enum NewsItemAction {
|
||||||
|
ON_SERVER_STARTED,
|
||||||
|
ON_OPERATOR_JOIN,
|
||||||
|
BROADCAST_TO_CONSOLE,
|
||||||
|
BROADCAST_TO_OPERATORS;
|
||||||
|
|
||||||
|
private static final NewsItemAction[] VALUES = values();
|
||||||
|
|
||||||
|
public static NewsItemAction getByName(String actionName) {
|
||||||
|
for (NewsItemAction type : VALUES) {
|
||||||
|
if (type.name().equalsIgnoreCase(actionName)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.news;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
|
||||||
|
// {} is used for things that have to be filled in by the server,
|
||||||
|
// {@} is for things that have to be filled in by us
|
||||||
|
public enum NewsItemMessage {
|
||||||
|
UPDATE_AVAILABLE("There is an update available for {}. The newest version is: {}"),
|
||||||
|
UPDATE_RECOMMENDED(UPDATE_AVAILABLE + ". Your version is quite old, updating is recommend."),
|
||||||
|
UPDATE_HIGHLY_RECOMMENDED(UPDATE_AVAILABLE + ". We highly recommend updating because some important changes have been made."),
|
||||||
|
UPDATE_ANCIENT_VERSION(UPDATE_AVAILABLE + ". You are running an ancient version, updating is recommended."),
|
||||||
|
|
||||||
|
DOWNTIME_GENERIC("The {} is temporarily going down for maintenance soon."),
|
||||||
|
DOWNTIME_WITH_START("The {} is temporarily going down for maintenance on {}."),
|
||||||
|
DOWNTIME_TIMEFRAME(DOWNTIME_WITH_START + " The maintenance is expected to last till {}.");
|
||||||
|
|
||||||
|
private static final NewsItemMessage[] VALUES = values();
|
||||||
|
|
||||||
|
private final String messageFormat;
|
||||||
|
private final String[] messageSplitted;
|
||||||
|
|
||||||
|
NewsItemMessage(String messageFormat) {
|
||||||
|
this.messageFormat = messageFormat;
|
||||||
|
this.messageSplitted = messageFormat.split(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NewsItemMessage getById(int id) {
|
||||||
|
return VALUES.length > id ? VALUES[id] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageFormat() {
|
||||||
|
return messageFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFormattedMessage(JsonArray serverArguments) {
|
||||||
|
int serverArgumentsIndex = 0;
|
||||||
|
|
||||||
|
StringBuilder message = new StringBuilder();
|
||||||
|
for (String split : messageSplitted) {
|
||||||
|
if (message.length() > 0) {
|
||||||
|
message.append(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = split;
|
||||||
|
|
||||||
|
if (serverArgumentsIndex < serverArguments.size()) {
|
||||||
|
String argument = serverArguments.get(serverArgumentsIndex).getAsString();
|
||||||
|
result = result.replace("{}", argument);
|
||||||
|
if (!result.equals(split)) {
|
||||||
|
serverArgumentsIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message.append(result);
|
||||||
|
}
|
||||||
|
return message.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getMessageFormat();
|
||||||
|
}
|
||||||
|
}
|
@ -23,40 +23,37 @@
|
|||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.common.window.button;
|
package org.geysermc.floodgate.news;
|
||||||
|
|
||||||
import lombok.Getter;
|
import com.google.gson.JsonObject;
|
||||||
import lombok.Setter;
|
import org.geysermc.floodgate.news.data.BuildSpecificData;
|
||||||
|
import org.geysermc.floodgate.news.data.CheckAfterData;
|
||||||
|
import org.geysermc.floodgate.news.data.ItemData;
|
||||||
|
|
||||||
public class FormImage {
|
import java.util.function.Function;
|
||||||
|
|
||||||
@Getter
|
public enum NewsType {
|
||||||
@Setter
|
BUILD_SPECIFIC(BuildSpecificData::read),
|
||||||
private String type;
|
CHECK_AFTER(CheckAfterData::read);
|
||||||
|
|
||||||
@Getter
|
private static final NewsType[] VALUES = values();
|
||||||
@Setter
|
|
||||||
private String data;
|
|
||||||
|
|
||||||
public FormImage(FormImageType type, String data) {
|
private final Function<JsonObject, ? extends ItemData> readFunction;
|
||||||
this.type = type.getName();
|
|
||||||
this.data = data;
|
NewsType(Function<JsonObject, ? extends ItemData> readFunction) {
|
||||||
|
this.readFunction = readFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FormImageType {
|
public static NewsType getByName(String newsType) {
|
||||||
PATH("path"),
|
for (NewsType type : VALUES) {
|
||||||
URL("url");
|
if (type.name().equalsIgnoreCase(newsType)) {
|
||||||
|
return type;
|
||||||
@Getter
|
}
|
||||||
private String name;
|
|
||||||
|
|
||||||
FormImageType(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
public ItemData read(JsonObject data) {
|
||||||
public String toString() {
|
return readFunction.apply(data);
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -23,49 +23,38 @@
|
|||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.common.window;
|
package org.geysermc.floodgate.news.data;
|
||||||
|
|
||||||
import lombok.Getter;
|
import com.google.gson.JsonObject;
|
||||||
import org.geysermc.common.window.CustomFormWindow;
|
|
||||||
import org.geysermc.common.window.button.FormImage;
|
|
||||||
import org.geysermc.common.window.component.FormComponent;
|
|
||||||
import org.geysermc.common.window.response.CustomFormResponse;
|
|
||||||
|
|
||||||
public class CustomFormBuilder {
|
public final class BuildSpecificData implements ItemData {
|
||||||
|
private String branch;
|
||||||
|
|
||||||
@Getter
|
private boolean allAffected;
|
||||||
private CustomFormWindow form;
|
private int affectedGreaterThan;
|
||||||
|
private int affectedLessThan;
|
||||||
|
|
||||||
public CustomFormBuilder(String title) {
|
public static BuildSpecificData read(JsonObject data) {
|
||||||
form = new CustomFormWindow(title);
|
BuildSpecificData updateData = new BuildSpecificData();
|
||||||
|
updateData.branch = data.get("branch").getAsString();
|
||||||
|
|
||||||
|
JsonObject affectedBuilds = data.getAsJsonObject("affected_builds");
|
||||||
|
if (affectedBuilds.has("all")) {
|
||||||
|
updateData.allAffected = affectedBuilds.get("all").getAsBoolean();
|
||||||
|
}
|
||||||
|
if (!updateData.allAffected) {
|
||||||
|
updateData.affectedGreaterThan = affectedBuilds.get("gt").getAsInt();
|
||||||
|
updateData.affectedLessThan = affectedBuilds.get("lt").getAsInt();
|
||||||
|
}
|
||||||
|
return updateData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomFormBuilder setTitle(String title) {
|
public boolean isAffected(String branch, int buildId) {
|
||||||
form.setTitle(title);
|
return this.branch.equals(branch) &&
|
||||||
return this;
|
(allAffected || buildId > affectedGreaterThan && buildId < affectedLessThan);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomFormBuilder setIcon(FormImage icon) {
|
public String getBranch() {
|
||||||
form.setIcon(icon);
|
return branch;
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomFormBuilder setResponse(String data) {
|
|
||||||
form.setResponse(data);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomFormBuilder setResponse(CustomFormResponse response) {
|
|
||||||
form.setResponse(response);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomFormBuilder addComponent(FormComponent component) {
|
|
||||||
form.addComponent(component);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomFormWindow build() {
|
|
||||||
return form;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.news.data;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
public class CheckAfterData implements ItemData {
|
||||||
|
private long checkAfter;
|
||||||
|
|
||||||
|
public static CheckAfterData read(JsonObject data) {
|
||||||
|
CheckAfterData checkAfterData = new CheckAfterData();
|
||||||
|
checkAfterData.checkAfter = data.get("check_after").getAsLong();
|
||||||
|
return checkAfterData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCheckAfter() {
|
||||||
|
return checkAfter;
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@
|
|||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.common.window.response;
|
package org.geysermc.floodgate.news.data;
|
||||||
|
|
||||||
public interface FormResponse {
|
public interface ItemData {
|
||||||
}
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.time;
|
||||||
|
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Thanks:
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc1769
|
||||||
|
* https://github.com/jonsagara/SimpleNtpClient
|
||||||
|
* https://stackoverflow.com/a/29138806
|
||||||
|
*/
|
||||||
|
public final class SntpClientUtils {
|
||||||
|
private static final int NTP_PORT = 123;
|
||||||
|
|
||||||
|
private static final int NTP_PACKET_SIZE = 48;
|
||||||
|
private static final int NTP_MODE = 3; // client
|
||||||
|
private static final int NTP_VERSION = 3;
|
||||||
|
private static final int RECEIVE_TIME_POSITION = 32;
|
||||||
|
|
||||||
|
private static final long NTP_TIME_OFFSET = ((365L * 70L) + 17L) * 24L * 60L * 60L;
|
||||||
|
|
||||||
|
public static long requestTimeOffset(String host, int timeout) {
|
||||||
|
try (DatagramSocket socket = new DatagramSocket()) {
|
||||||
|
socket.setSoTimeout(timeout);
|
||||||
|
|
||||||
|
InetAddress address = InetAddress.getByName(host);
|
||||||
|
|
||||||
|
ByteBuffer buff = ByteBuffer.allocate(NTP_PACKET_SIZE);
|
||||||
|
|
||||||
|
DatagramPacket request = new DatagramPacket(
|
||||||
|
buff.array(), NTP_PACKET_SIZE, address, NTP_PORT
|
||||||
|
);
|
||||||
|
|
||||||
|
// mode is in the least signification 3 bits
|
||||||
|
// version is in bits 3-5
|
||||||
|
buff.put((byte) (NTP_MODE | (NTP_VERSION << 3)));
|
||||||
|
|
||||||
|
long originateTime = System.currentTimeMillis();
|
||||||
|
socket.send(request);
|
||||||
|
|
||||||
|
DatagramPacket response = new DatagramPacket(buff.array(), NTP_PACKET_SIZE);
|
||||||
|
socket.receive(response);
|
||||||
|
|
||||||
|
long responseTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// everything before isn't important for us
|
||||||
|
buff.position(RECEIVE_TIME_POSITION);
|
||||||
|
|
||||||
|
long receiveTime = readTimestamp(buff);
|
||||||
|
long transmitTime = readTimestamp(buff);
|
||||||
|
|
||||||
|
return ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return Long.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long readTimestamp(ByteBuffer buffer) {
|
||||||
|
//todo look into the ntp 2036 problem
|
||||||
|
long seconds = buffer.getInt() & 0xffffffffL;
|
||||||
|
long fraction = buffer.getInt() & 0xffffffffL;
|
||||||
|
return ((seconds - NTP_TIME_OFFSET) * 1000) + ((fraction * 1000) / 0x100000000L);
|
||||||
|
}
|
||||||
|
}
|
67
common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java
Normale Datei
67
common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java
Normale Datei
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.time;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public final class TimeSyncer {
|
||||||
|
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
|
||||||
|
private long timeOffset = Long.MIN_VALUE; // value when it failed to get the offset
|
||||||
|
|
||||||
|
public TimeSyncer(String timeServer) {
|
||||||
|
executorService.scheduleWithFixedDelay(() -> {
|
||||||
|
// 5 tries to get the time offset, since UDP doesn't guaranty a response
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
long offset = SntpClientUtils.requestTimeOffset(timeServer, 3000);
|
||||||
|
if (offset != Long.MIN_VALUE) {
|
||||||
|
timeOffset = offset;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, 30, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeOffset() {
|
||||||
|
return timeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRealMillis() {
|
||||||
|
if (hasUsefulOffset()) {
|
||||||
|
return System.currentTimeMillis() + getTimeOffset();
|
||||||
|
}
|
||||||
|
return System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasUsefulOffset() {
|
||||||
|
return timeOffset != Long.MIN_VALUE;
|
||||||
|
}
|
||||||
|
}
|
@ -23,20 +23,13 @@
|
|||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.common.window.component;
|
package org.geysermc.floodgate.util;
|
||||||
|
|
||||||
import lombok.Getter;
|
public final class Base64Utils {
|
||||||
import lombok.Setter;
|
public static int getEncodedLength(int length) {
|
||||||
|
if (length <= 0) {
|
||||||
public class LabelComponent extends FormComponent {
|
return -1;
|
||||||
|
}
|
||||||
@Getter
|
return 4 * ((length + 2) / 3);
|
||||||
@Setter
|
|
||||||
private String text;
|
|
||||||
|
|
||||||
public LabelComponent(String text) {
|
|
||||||
super("label");
|
|
||||||
|
|
||||||
this.text = text;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -25,47 +25,91 @@
|
|||||||
|
|
||||||
package org.geysermc.floodgate.util;
|
package org.geysermc.floodgate.util;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.geysermc.floodgate.time.TimeSyncer;
|
||||||
|
|
||||||
import java.util.UUID;
|
/**
|
||||||
|
* This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This
|
||||||
@AllArgsConstructor
|
* class is only used internally, and you should look at FloodgatePlayer instead (FloodgatePlayer is
|
||||||
|
* present in the API module of the Floodgate repo)
|
||||||
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
public class BedrockData {
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public static final int EXPECTED_LENGTH = 7;
|
public final class BedrockData implements Cloneable {
|
||||||
public static final String FLOODGATE_IDENTIFIER = "Geyser-Floodgate";
|
public static final int EXPECTED_LENGTH = 13;
|
||||||
|
|
||||||
private String version;
|
private final String version;
|
||||||
private String username;
|
private final String username;
|
||||||
private String xuid;
|
private final String xuid;
|
||||||
private int deviceId;
|
private final int deviceOs;
|
||||||
private String languageCode;
|
private final String languageCode;
|
||||||
private int inputMode;
|
private final int uiProfile;
|
||||||
private String ip;
|
private final int inputMode;
|
||||||
private int dataLength;
|
private final String ip;
|
||||||
|
private final LinkedPlayer linkedPlayer;
|
||||||
|
private final boolean fromProxy;
|
||||||
|
|
||||||
public BedrockData(String version, String username, String xuid, int deviceId, String languageCode, int inputMode, String ip) {
|
private final int subscribeId;
|
||||||
this(version, username, xuid, deviceId, languageCode, inputMode, ip, EXPECTED_LENGTH);
|
private final String verifyCode;
|
||||||
|
|
||||||
|
private final long timestamp;
|
||||||
|
private final int dataLength;
|
||||||
|
|
||||||
|
public static BedrockData of(
|
||||||
|
String version, String username, String xuid, int deviceOs,
|
||||||
|
String languageCode, int uiProfile, int inputMode, String ip,
|
||||||
|
LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId,
|
||||||
|
String verifyCode, TimeSyncer timeSyncer) {
|
||||||
|
return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode,
|
||||||
|
uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode,
|
||||||
|
timeSyncer.getRealMillis(), EXPECTED_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BedrockData of(
|
||||||
|
String version, String username, String xuid, int deviceOs,
|
||||||
|
String languageCode, int uiProfile, int inputMode, String ip,
|
||||||
|
int subscribeId, String verifyCode, TimeSyncer timeSyncer) {
|
||||||
|
return of(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null,
|
||||||
|
false, subscribeId, verifyCode, timeSyncer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BedrockData fromString(String data) {
|
public static BedrockData fromString(String data) {
|
||||||
String[] split = data.split("\0");
|
String[] split = data.split("\0");
|
||||||
if (split.length != EXPECTED_LENGTH) return null;
|
if (split.length != EXPECTED_LENGTH) {
|
||||||
|
return emptyData(split.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedPlayer linkedPlayer = LinkedPlayer.fromString(split[8]);
|
||||||
|
// The format is the same as the order of the fields in this class
|
||||||
return new BedrockData(
|
return new BedrockData(
|
||||||
split[0], split[1], split[2], Integer.parseInt(split[3]),
|
split[0], split[1], split[2], Integer.parseInt(split[3]), split[4],
|
||||||
split[4], Integer.parseInt(split[5]), split[6], split.length
|
Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], linkedPlayer,
|
||||||
|
"1".equals(split[9]), Integer.parseInt(split[10]), split[11], Long.parseLong(split[12]), split.length
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BedrockData fromRawData(byte[] data) {
|
private static BedrockData emptyData(int dataLength) {
|
||||||
return fromString(new String(data));
|
return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, -1,
|
||||||
|
dataLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPlayerLink() {
|
||||||
|
return linkedPlayer != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return version +'\0'+ username +'\0'+ xuid +'\0'+ deviceId +'\0'+ languageCode +'\0'+
|
// The format is the same as the order of the fields in this class
|
||||||
inputMode +'\0'+ ip;
|
return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' +
|
||||||
|
languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' +
|
||||||
|
(linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' +
|
||||||
|
(fromProxy ? 1 : 0) + '\0' + subscribeId + '\0' + verifyCode + '\0' + timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BedrockData clone() throws CloneNotSupportedException {
|
||||||
|
return (BedrockData) super.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,36 +25,41 @@
|
|||||||
|
|
||||||
package org.geysermc.floodgate.util;
|
package org.geysermc.floodgate.util;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
|
import lombok.AccessLevel;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
public enum DeviceOS {
|
/**
|
||||||
|
* The Operation Systems where Bedrock players can connect with
|
||||||
@JsonEnumDefaultValue
|
*/
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public enum DeviceOs {
|
||||||
UNKNOWN("Unknown"),
|
UNKNOWN("Unknown"),
|
||||||
ANDROID("Android"),
|
GOOGLE("Android"),
|
||||||
IOS("iOS"),
|
IOS("iOS"),
|
||||||
OSX("macOS"),
|
OSX("macOS"),
|
||||||
FIREOS("FireOS"),
|
AMAZON("Amazon"),
|
||||||
GEARVR("Gear VR"),
|
GEARVR("Gear VR"),
|
||||||
HOLOLENS("Hololens"),
|
HOLOLENS("Hololens"),
|
||||||
WIN10("Windows 10"),
|
UWP("Windows 10"),
|
||||||
WIN32("Windows"),
|
WIN32("Windows x86"),
|
||||||
DEDICATED("Dedicated"),
|
DEDICATED("Dedicated"),
|
||||||
ORBIS("PS4"),
|
TVOS("Apple TV"),
|
||||||
|
PS4("PS4"),
|
||||||
NX("Switch"),
|
NX("Switch"),
|
||||||
SWITCH("Switch"),
|
XBOX("Xbox One"),
|
||||||
XBOX_ONE("Xbox One"),
|
WINDOWS_PHONE("Windows Phone");
|
||||||
WIN_PHONE("Windows Phone");
|
|
||||||
|
|
||||||
private static final DeviceOS[] VALUES = values();
|
private static final DeviceOs[] VALUES = values();
|
||||||
|
|
||||||
private final String displayName;
|
private final String displayName;
|
||||||
|
|
||||||
DeviceOS(final String displayName) {
|
/**
|
||||||
this.displayName = displayName;
|
* Get the DeviceOs instance from the identifier.
|
||||||
}
|
*
|
||||||
|
* @param id the DeviceOs identifier
|
||||||
public static DeviceOS getById(int id) {
|
* @return The DeviceOs or {@link #UNKNOWN} if the DeviceOs wasn't found
|
||||||
|
*/
|
||||||
|
public static DeviceOs getById(int id) {
|
||||||
return id < VALUES.length ? VALUES[id] : VALUES[0];
|
return id < VALUES.length ? VALUES[id] : VALUES[0];
|
||||||
}
|
}
|
||||||
|
|
@ -1,101 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.floodgate.util;
|
|
||||||
|
|
||||||
import javax.crypto.*;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.security.*;
|
|
||||||
import java.security.spec.EncodedKeySpec;
|
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
public class EncryptionUtil {
|
|
||||||
public static String encrypt(Key key, String data) throws IllegalBlockSizeException,
|
|
||||||
InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
|
|
||||||
KeyGenerator generator = KeyGenerator.getInstance("AES");
|
|
||||||
generator.init(128);
|
|
||||||
SecretKey secretKey = generator.generateKey();
|
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance("AES");
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
|
||||||
byte[] encryptedText = cipher.doFinal(data.getBytes());
|
|
||||||
|
|
||||||
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
|
||||||
cipher.init(key instanceof PublicKey ? Cipher.PUBLIC_KEY : Cipher.PRIVATE_KEY, key);
|
|
||||||
return Base64.getEncoder().encodeToString(cipher.doFinal(secretKey.getEncoded())) + '\0' +
|
|
||||||
Base64.getEncoder().encodeToString(encryptedText);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String encryptBedrockData(Key key, BedrockData data) throws IllegalBlockSizeException,
|
|
||||||
InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
|
|
||||||
return encrypt(key, data.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] decrypt(Key key, String encryptedData) throws IllegalBlockSizeException,
|
|
||||||
InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
|
|
||||||
String[] split = encryptedData.split("\0");
|
|
||||||
if (split.length != 2) {
|
|
||||||
throw new IllegalArgumentException("Expected two arguments, got " + split.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
|
||||||
cipher.init(key instanceof PublicKey ? Cipher.PUBLIC_KEY : Cipher.PRIVATE_KEY, key);
|
|
||||||
byte[] decryptedKey = cipher.doFinal(Base64.getDecoder().decode(split[0]));
|
|
||||||
|
|
||||||
SecretKey secretKey = new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES");
|
|
||||||
cipher = Cipher.getInstance("AES");
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKey);
|
|
||||||
return cipher.doFinal(Base64.getDecoder().decode(split[1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BedrockData decryptBedrockData(Key key, String encryptedData) throws IllegalBlockSizeException,
|
|
||||||
InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
|
|
||||||
return BedrockData.fromRawData(decrypt(key, encryptedData));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <T extends Key> T getKeyFromFile(Path fileLocation, Class<T> keyType) throws
|
|
||||||
IOException, InvalidKeySpecException, NoSuchAlgorithmException {
|
|
||||||
boolean isPublicKey = keyType == PublicKey.class;
|
|
||||||
if (!isPublicKey && keyType != PrivateKey.class) {
|
|
||||||
throw new RuntimeException("I can only read public and private keys!");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] key = Files.readAllBytes(fileLocation);
|
|
||||||
|
|
||||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
|
||||||
EncodedKeySpec keySpec = isPublicKey ? new X509EncodedKeySpec(key) : new PKCS8EncodedKeySpec(key);
|
|
||||||
return (T) (isPublicKey ?
|
|
||||||
keyFactory.generatePublic(keySpec) :
|
|
||||||
keyFactory.generatePrivate(keySpec)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -23,16 +23,13 @@
|
|||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.common.window.component;
|
package org.geysermc.floodgate.util;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
public abstract class FormComponent {
|
public final class FloodgateConfigHolder {
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final String type;
|
@Setter
|
||||||
|
private static Object config;
|
||||||
public FormComponent(String type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
}
|
}
|
47
common/src/main/java/org/geysermc/floodgate/util/InputMode.java
Normale Datei
47
common/src/main/java/org/geysermc/floodgate/util/InputMode.java
Normale Datei
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.util;
|
||||||
|
|
||||||
|
public enum InputMode {
|
||||||
|
UNKNOWN,
|
||||||
|
KEYBOARD_MOUSE,
|
||||||
|
TOUCH,
|
||||||
|
CONTROLLER,
|
||||||
|
VR;
|
||||||
|
|
||||||
|
private static final InputMode[] VALUES = values();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the InputMode instance from the identifier.
|
||||||
|
*
|
||||||
|
* @param id the InputMode identifier
|
||||||
|
* @return The InputMode or {@link #UNKNOWN} if the DeviceOs wasn't found
|
||||||
|
*/
|
||||||
|
public static InputMode getById(int id) {
|
||||||
|
return VALUES.length > id ? VALUES[id] : VALUES[0];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.util;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class InvalidFormatException extends Exception {
|
||||||
|
private boolean header = false;
|
||||||
|
|
||||||
|
public InvalidFormatException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidFormatException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidFormatException(String message, boolean header) {
|
||||||
|
super(message);
|
||||||
|
this.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidFormatException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
82
common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java
Normale Datei
82
common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java
Normale Datei
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.util;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public final class LinkedPlayer implements Cloneable {
|
||||||
|
/**
|
||||||
|
* The Java username of the linked player
|
||||||
|
*/
|
||||||
|
private final String javaUsername;
|
||||||
|
/**
|
||||||
|
* The Java UUID of the linked player
|
||||||
|
*/
|
||||||
|
private final UUID javaUniqueId;
|
||||||
|
/**
|
||||||
|
* The UUID of the Bedrock player
|
||||||
|
*/
|
||||||
|
private final UUID bedrockId;
|
||||||
|
/**
|
||||||
|
* If the LinkedPlayer is sent from a different platform. For example the LinkedPlayer is from
|
||||||
|
* Bungee but the data has been sent to the Bukkit server.
|
||||||
|
*/
|
||||||
|
private boolean fromDifferentPlatform = false;
|
||||||
|
|
||||||
|
public static LinkedPlayer of(String javaUsername, UUID javaUniqueId, UUID bedrockId) {
|
||||||
|
return new LinkedPlayer(javaUsername, javaUniqueId, bedrockId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LinkedPlayer fromString(String data) {
|
||||||
|
String[] split = data.split(";");
|
||||||
|
if (split.length != 3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedPlayer player = new LinkedPlayer(
|
||||||
|
split[0], UUID.fromString(split[1]), UUID.fromString(split[2])
|
||||||
|
);
|
||||||
|
player.fromDifferentPlatform = true;
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return javaUsername + ';' + javaUniqueId.toString() + ';' + bedrockId.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedPlayer clone() throws CloneNotSupportedException {
|
||||||
|
return (LinkedPlayer) super.clone();
|
||||||
|
}
|
||||||
|
}
|
44
common/src/main/java/org/geysermc/floodgate/util/UiProfile.java
Normale Datei
44
common/src/main/java/org/geysermc/floodgate/util/UiProfile.java
Normale Datei
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.util;
|
||||||
|
|
||||||
|
public enum UiProfile {
|
||||||
|
CLASSIC,
|
||||||
|
POCKET;
|
||||||
|
|
||||||
|
private static final UiProfile[] VALUES = values();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the UiProfile instance from the identifier.
|
||||||
|
*
|
||||||
|
* @param id the UiProfile identifier
|
||||||
|
* @return The UiProfile or {@link #CLASSIC} if the UiProfile wasn't found
|
||||||
|
*/
|
||||||
|
public static UiProfile getById(int id) {
|
||||||
|
return VALUES.length > id ? VALUES[id] : VALUES[0];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.util;
|
||||||
|
|
||||||
|
public enum WebsocketEventType {
|
||||||
|
/**
|
||||||
|
* Sent once we successfully connected to the server
|
||||||
|
*/
|
||||||
|
SUBSCRIBER_CREATED(0),
|
||||||
|
/**
|
||||||
|
* Sent every time a subscriber got added or disconnected
|
||||||
|
*/
|
||||||
|
SUBSCRIBER_COUNT(1),
|
||||||
|
/**
|
||||||
|
* Sent once the creator disconnected. After this packet the server will automatically close the
|
||||||
|
* connection once the queue size (sent in {@link #ADDED_TO_QUEUE} and {@link #SKIN_UPLOADED}
|
||||||
|
* reaches 0.
|
||||||
|
*/
|
||||||
|
CREATOR_DISCONNECTED(4),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sent every time a skin got added to the upload queue
|
||||||
|
*/
|
||||||
|
ADDED_TO_QUEUE(2),
|
||||||
|
/**
|
||||||
|
* Sent every time a skin got successfully uploaded
|
||||||
|
*/
|
||||||
|
SKIN_UPLOADED(3),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sent every time a news item was added
|
||||||
|
*/
|
||||||
|
NEWS_ADDED(6),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sent when the server wants you to know something. Currently used for violations that aren't
|
||||||
|
* bad enough to close the connection
|
||||||
|
*/
|
||||||
|
LOG_MESSAGE(5);
|
||||||
|
|
||||||
|
private static final WebsocketEventType[] VALUES;
|
||||||
|
|
||||||
|
static {
|
||||||
|
WebsocketEventType[] values = values();
|
||||||
|
VALUES = new WebsocketEventType[values.length];
|
||||||
|
for (WebsocketEventType value : values) {
|
||||||
|
VALUES[value.id] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID is based of the time it got added. However, to keep the enum organized as time goes on,
|
||||||
|
* it looks nicer to sort the events based of categories.
|
||||||
|
*/
|
||||||
|
private final int id;
|
||||||
|
|
||||||
|
WebsocketEventType(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WebsocketEventType getById(int id) {
|
||||||
|
return VALUES.length > id ? VALUES[id] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>geyser-parent</artifactId>
|
<artifactId>geyser-parent</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>connector</artifactId>
|
<artifactId>connector</artifactId>
|
||||||
|
|
||||||
@ -20,7 +20,13 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
<version>2.10.2</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -29,6 +35,12 @@
|
|||||||
<version>2.10.2</version>
|
<version>2.10.2</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.java-websocket</groupId>
|
||||||
|
<artifactId>Java-WebSocket</artifactId>
|
||||||
|
<version>1.5.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.CloudburstMC.Protocol</groupId>
|
<groupId>com.github.CloudburstMC.Protocol</groupId>
|
||||||
<artifactId>bedrock-v440</artifactId>
|
<artifactId>bedrock-v440</artifactId>
|
||||||
@ -206,11 +218,13 @@
|
|||||||
<groupId>org.reflections</groupId>
|
<groupId>org.reflections</groupId>
|
||||||
<artifactId>reflections</artifactId>
|
<artifactId>reflections</artifactId>
|
||||||
<version>0.9.11</version> <!-- This isn't the latest version to get round https://github.com/ronmamo/reflections/issues/273 -->
|
<version>0.9.11</version> <!-- This isn't the latest version to get round https://github.com/ronmamo/reflections/issues/273 -->
|
||||||
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.dom4j</groupId>
|
<groupId>org.dom4j</groupId>
|
||||||
<artifactId>dom4j</artifactId>
|
<artifactId>dom4j</artifactId>
|
||||||
<version>2.1.3</version>
|
<version>2.1.3</version>
|
||||||
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.kyori</groupId>
|
<groupId>net.kyori</groupId>
|
||||||
@ -246,6 +260,7 @@
|
|||||||
<groupId>com.github.GeyserMC</groupId>
|
<groupId>com.github.GeyserMC</groupId>
|
||||||
<artifactId>MCAuthLib</artifactId>
|
<artifactId>MCAuthLib</artifactId>
|
||||||
<version>0e48a094f2</version>
|
<version>0e48a094f2</version>
|
||||||
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@ -33,11 +33,20 @@ import java.nio.file.Path;
|
|||||||
|
|
||||||
public class FloodgateKeyLoader {
|
public class FloodgateKeyLoader {
|
||||||
public static Path getKeyPath(GeyserJacksonConfiguration config, Object floodgate, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) {
|
public static Path getKeyPath(GeyserJacksonConfiguration config, Object floodgate, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) {
|
||||||
|
if (!config.getRemote().getAuthType().equals("floodgate")) {
|
||||||
|
return geyserDataFolder.resolve(config.getFloodgateKeyFile());
|
||||||
|
}
|
||||||
|
|
||||||
Path floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile());
|
Path floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile());
|
||||||
|
|
||||||
if (!Files.exists(floodgateKey) && config.getRemote().getAuthType().equals("floodgate")) {
|
if (config.getFloodgateKeyFile().equals("public-key.pem")) {
|
||||||
|
logger.info("Floodgate 2.0 doesn't use a public/private key system anymore. We'll search for key.pem instead");
|
||||||
|
floodgateKey = geyserDataFolder.resolve("key.pem");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Files.exists(floodgateKey)) {
|
||||||
if (floodgate != null) {
|
if (floodgate != null) {
|
||||||
Path autoKey = floodgateDataFolder.resolve("public-key.pem");
|
Path autoKey = floodgateDataFolder.resolve("key.pem");
|
||||||
if (Files.exists(autoKey)) {
|
if (Files.exists(autoKey)) {
|
||||||
logger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.auto_loaded"));
|
logger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.auto_loaded"));
|
||||||
floodgateKey = autoKey;
|
floodgateKey = autoKey;
|
||||||
|
@ -58,7 +58,14 @@ import org.geysermc.connector.network.translators.world.WorldManager;
|
|||||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||||
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
|
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
|
||||||
import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator;
|
import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator;
|
||||||
|
import org.geysermc.connector.skin.FloodgateSkinUploader;
|
||||||
import org.geysermc.connector.utils.*;
|
import org.geysermc.connector.utils.*;
|
||||||
|
import org.geysermc.floodgate.crypto.AesCipher;
|
||||||
|
import org.geysermc.floodgate.crypto.AesKeyProducer;
|
||||||
|
import org.geysermc.floodgate.crypto.Base64Topping;
|
||||||
|
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||||
|
import org.geysermc.floodgate.news.NewsItemAction;
|
||||||
|
import org.geysermc.floodgate.time.TimeSyncer;
|
||||||
import org.jetbrains.annotations.Contract;
|
import org.jetbrains.annotations.Contract;
|
||||||
|
|
||||||
import javax.naming.directory.Attribute;
|
import javax.naming.directory.Attribute;
|
||||||
@ -66,6 +73,7 @@ import javax.naming.directory.InitialDirContext;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
import java.security.Key;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
@ -75,7 +83,6 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class GeyserConnector {
|
public class GeyserConnector {
|
||||||
|
|
||||||
public static final ObjectMapper JSON_MAPPER = new ObjectMapper()
|
public static final ObjectMapper JSON_MAPPER = new ObjectMapper()
|
||||||
.enable(JsonParser.Feature.IGNORE_UNDEFINED)
|
.enable(JsonParser.Feature.IGNORE_UNDEFINED)
|
||||||
.enable(JsonParser.Feature.ALLOW_COMMENTS)
|
.enable(JsonParser.Feature.ALLOW_COMMENTS)
|
||||||
@ -102,6 +109,11 @@ public class GeyserConnector {
|
|||||||
@Setter
|
@Setter
|
||||||
private AuthType defaultAuthType;
|
private AuthType defaultAuthType;
|
||||||
|
|
||||||
|
private final TimeSyncer timeSyncer;
|
||||||
|
private FloodgateCipher cipher;
|
||||||
|
private FloodgateSkinUploader skinUploader;
|
||||||
|
private final NewsHandler newsHandler;
|
||||||
|
|
||||||
private boolean shuttingDown = false;
|
private boolean shuttingDown = false;
|
||||||
|
|
||||||
private final ScheduledExecutorService generalThreadPool;
|
private final ScheduledExecutorService generalThreadPool;
|
||||||
@ -190,6 +202,36 @@ public class GeyserConnector {
|
|||||||
|
|
||||||
defaultAuthType = AuthType.getByName(config.getRemote().getAuthType());
|
defaultAuthType = AuthType.getByName(config.getRemote().getAuthType());
|
||||||
|
|
||||||
|
TimeSyncer timeSyncer = null;
|
||||||
|
if (defaultAuthType == AuthType.FLOODGATE) {
|
||||||
|
timeSyncer = new TimeSyncer(Constants.NTP_SERVER);
|
||||||
|
try {
|
||||||
|
Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath());
|
||||||
|
cipher = new AesCipher(new Base64Topping());
|
||||||
|
cipher.init(key);
|
||||||
|
logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
|
||||||
|
skinUploader = new FloodgateSkinUploader(this).start();
|
||||||
|
} catch (Exception exception) {
|
||||||
|
logger.severe(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.timeSyncer = timeSyncer;
|
||||||
|
|
||||||
|
String branch = "unknown";
|
||||||
|
int buildNumber = -1;
|
||||||
|
try {
|
||||||
|
Properties gitProperties = new Properties();
|
||||||
|
gitProperties.load(FileUtils.getResource("git.properties"));
|
||||||
|
branch = gitProperties.getProperty("git.branch");
|
||||||
|
String build = gitProperties.getProperty("git.build.number");
|
||||||
|
if (build != null) {
|
||||||
|
buildNumber = Integer.parseInt(build);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to read git.properties", e);
|
||||||
|
}
|
||||||
|
newsHandler = new NewsHandler(branch, buildNumber);
|
||||||
|
|
||||||
CooldownUtils.setDefaultShowCooldown(config.getShowCooldown());
|
CooldownUtils.setDefaultShowCooldown(config.getShowCooldown());
|
||||||
DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether
|
DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether
|
||||||
SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS = config.isAllowCustomSkulls();
|
SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS = config.isAllowCustomSkulls();
|
||||||
@ -241,7 +283,7 @@ public class GeyserConnector {
|
|||||||
for (GeyserSession session : players) {
|
for (GeyserSession session : players) {
|
||||||
if (session == null) continue;
|
if (session == null) continue;
|
||||||
if (session.getClientData() == null) continue;
|
if (session.getClientData() == null) continue;
|
||||||
String os = session.getClientData().getDeviceOS().toString();
|
String os = session.getClientData().getDeviceOs().toString();
|
||||||
if (!valueMap.containsKey(os)) {
|
if (!valueMap.containsKey(os)) {
|
||||||
valueMap.put(os, 1);
|
valueMap.put(os, 1);
|
||||||
} else {
|
} else {
|
||||||
@ -303,6 +345,8 @@ public class GeyserConnector {
|
|||||||
if (platformType == PlatformType.STANDALONE) {
|
if (platformType == PlatformType.STANDALONE) {
|
||||||
logger.warning(LanguageUtils.getLocaleStringLog("geyser.core.movement_warn"));
|
logger.warning(LanguageUtils.getLocaleStringLog("geyser.core.movement_warn"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
@ -347,6 +391,8 @@ public class GeyserConnector {
|
|||||||
|
|
||||||
generalThreadPool.shutdown();
|
generalThreadPool.shutdown();
|
||||||
bedrockServer.close();
|
bedrockServer.close();
|
||||||
|
timeSyncer.shutdown();
|
||||||
|
newsHandler.shutdown();
|
||||||
players.clear();
|
players.clear();
|
||||||
defaultAuthType = null;
|
defaultAuthType = null;
|
||||||
this.getCommandManager().getCommands().clear();
|
this.getCommandManager().getCommands().clear();
|
||||||
@ -425,6 +471,10 @@ public class GeyserConnector {
|
|||||||
return bootstrap.getWorldManager();
|
return bootstrap.getWorldManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TimeSyncer getTimeSyncer() {
|
||||||
|
return timeSyncer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to use XML reflections in the jar or manually find the reflections.
|
* Whether to use XML reflections in the jar or manually find the reflections.
|
||||||
* Will return true if the version number is not 'DEV' and the platform is not Fabric.
|
* Will return true if the version number is not 'DEV' and the platform is not Fabric.
|
||||||
|
@ -53,7 +53,7 @@ public abstract class CommandManager {
|
|||||||
registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version"));
|
registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version"));
|
||||||
registerCommand(new SettingsCommand(connector, "settings", "geyser.commands.settings.desc", "geyser.command.settings"));
|
registerCommand(new SettingsCommand(connector, "settings", "geyser.commands.settings.desc", "geyser.command.settings"));
|
||||||
registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics"));
|
registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics"));
|
||||||
registerCommand(new AdvancementsCommand(connector, "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements"));
|
registerCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerCommand(GeyserCommand command) {
|
public void registerCommand(GeyserCommand command) {
|
||||||
|
@ -25,25 +25,20 @@
|
|||||||
|
|
||||||
package org.geysermc.connector.command.defaults;
|
package org.geysermc.connector.command.defaults;
|
||||||
|
|
||||||
import org.geysermc.common.window.SimpleFormWindow;
|
|
||||||
import org.geysermc.connector.GeyserConnector;
|
|
||||||
import org.geysermc.connector.command.CommandSender;
|
import org.geysermc.connector.command.CommandSender;
|
||||||
import org.geysermc.connector.command.GeyserCommand;
|
import org.geysermc.connector.command.GeyserCommand;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.session.cache.AdvancementsCache;
|
|
||||||
|
|
||||||
public class AdvancementsCommand extends GeyserCommand {
|
public class AdvancementsCommand extends GeyserCommand {
|
||||||
|
public AdvancementsCommand(String name, String description, String permission) {
|
||||||
public AdvancementsCommand(GeyserConnector connector, String name, String description, String permission) {
|
|
||||||
super(name, description, permission);
|
super(name, description, permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
||||||
if (session == null) return;
|
if (session != null) {
|
||||||
|
session.getAdvancementsCache().buildAndShowMenuForm();
|
||||||
SimpleFormWindow window = session.getAdvancementsCache().buildMenuForm();
|
}
|
||||||
session.sendForm(window, AdvancementsCache.ADVANCEMENTS_MENU_FORM_ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -32,17 +32,15 @@ import org.geysermc.connector.network.session.GeyserSession;
|
|||||||
import org.geysermc.connector.utils.SettingsUtils;
|
import org.geysermc.connector.utils.SettingsUtils;
|
||||||
|
|
||||||
public class SettingsCommand extends GeyserCommand {
|
public class SettingsCommand extends GeyserCommand {
|
||||||
|
|
||||||
public SettingsCommand(GeyserConnector connector, String name, String description, String permission) {
|
public SettingsCommand(GeyserConnector connector, String name, String description, String permission) {
|
||||||
super(name, description, permission);
|
super(name, description, permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
||||||
if (session == null) return;
|
if (session != null) {
|
||||||
|
session.sendForm(SettingsUtils.buildForm(session));
|
||||||
SettingsUtils.buildForm(session);
|
}
|
||||||
session.sendForm(session.getSettingsForm(), SettingsUtils.SETTINGS_FORM_ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -34,8 +34,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class BootstrapDumpInfo {
|
public class BootstrapDumpInfo {
|
||||||
|
private final PlatformType platform;
|
||||||
private PlatformType platform;
|
|
||||||
|
|
||||||
public BootstrapDumpInfo() {
|
public BootstrapDumpInfo() {
|
||||||
this.platform = GeyserConnector.getInstance().getPlatformType();
|
this.platform = GeyserConnector.getInstance().getPlatformType();
|
||||||
@ -44,7 +43,6 @@ public class BootstrapDumpInfo {
|
|||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public static class PluginInfo {
|
public static class PluginInfo {
|
||||||
|
|
||||||
public boolean enabled;
|
public boolean enabled;
|
||||||
public String name;
|
public String name;
|
||||||
public String version;
|
public String version;
|
||||||
@ -55,7 +53,6 @@ public class BootstrapDumpInfo {
|
|||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public static class ListenerInfo {
|
public static class ListenerInfo {
|
||||||
|
|
||||||
public String ip;
|
public String ip;
|
||||||
public int port;
|
public int port;
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,8 @@ import org.geysermc.connector.network.BedrockProtocol;
|
|||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.utils.DockerCheck;
|
import org.geysermc.connector.utils.DockerCheck;
|
||||||
import org.geysermc.connector.utils.FileUtils;
|
import org.geysermc.connector.utils.FileUtils;
|
||||||
import org.geysermc.floodgate.util.DeviceOS;
|
import org.geysermc.floodgate.util.DeviceOs;
|
||||||
|
import org.geysermc.floodgate.util.FloodgateConfigHolder;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -53,27 +54,29 @@ import java.util.Properties;
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class DumpInfo {
|
public class DumpInfo {
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private static final long MEGABYTE = 1024L * 1024L;
|
private static final long MEGABYTE = 1024L * 1024L;
|
||||||
|
|
||||||
private final DumpInfo.VersionInfo versionInfo;
|
private final DumpInfo.VersionInfo versionInfo;
|
||||||
private Properties gitInfo;
|
private Properties gitInfo;
|
||||||
private final GeyserConfiguration config;
|
private final GeyserConfiguration config;
|
||||||
|
private final Object floodgateConfig;
|
||||||
private final HashInfo hashInfo;
|
private final HashInfo hashInfo;
|
||||||
private final Object2IntMap<DeviceOS> userPlatforms;
|
private final Object2IntMap<DeviceOs> userPlatforms;
|
||||||
private final RamInfo ramInfo;
|
private final RamInfo ramInfo;
|
||||||
private final BootstrapDumpInfo bootstrapInfo;
|
private final BootstrapDumpInfo bootstrapInfo;
|
||||||
|
|
||||||
public DumpInfo() {
|
public DumpInfo() {
|
||||||
this.versionInfo = new DumpInfo.VersionInfo();
|
this.versionInfo = new VersionInfo();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.gitInfo = new Properties();
|
this.gitInfo = new Properties();
|
||||||
this.gitInfo.load(FileUtils.getResource("git.properties"));
|
this.gitInfo.load(FileUtils.getResource("git.properties"));
|
||||||
} catch (IOException ignored) { }
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
this.config = GeyserConnector.getInstance().getConfig();
|
this.config = GeyserConnector.getInstance().getConfig();
|
||||||
|
this.floodgateConfig = FloodgateConfigHolder.getConfig();
|
||||||
|
|
||||||
String md5Hash = "unknown";
|
String md5Hash = "unknown";
|
||||||
String sha256Hash = "unknown";
|
String sha256Hash = "unknown";
|
||||||
@ -99,7 +102,7 @@ public class DumpInfo {
|
|||||||
|
|
||||||
this.userPlatforms = new Object2IntOpenHashMap<>();
|
this.userPlatforms = new Object2IntOpenHashMap<>();
|
||||||
for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) {
|
for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) {
|
||||||
DeviceOS device = session.getClientData().getDeviceOS();
|
DeviceOs device = session.getClientData().getDeviceOs();
|
||||||
userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1);
|
userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +111,6 @@ public class DumpInfo {
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public static class VersionInfo {
|
public static class VersionInfo {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String version;
|
private final String version;
|
||||||
private final String javaVersion;
|
private final String javaVersion;
|
||||||
@ -123,7 +125,8 @@ public class DumpInfo {
|
|||||||
this.name = GeyserConnector.NAME;
|
this.name = GeyserConnector.NAME;
|
||||||
this.version = GeyserConnector.VERSION;
|
this.version = GeyserConnector.VERSION;
|
||||||
this.javaVersion = System.getProperty("java.version");
|
this.javaVersion = System.getProperty("java.version");
|
||||||
this.architecture = System.getProperty("os.arch"); // Usually gives Java architecture but still may be helpful.
|
// Usually gives Java architecture but still may be helpful.
|
||||||
|
this.architecture = System.getProperty("os.arch");
|
||||||
this.operatingSystem = System.getProperty("os.name");
|
this.operatingSystem = System.getProperty("os.name");
|
||||||
this.operatingSystemVersion = System.getProperty("os.version");
|
this.operatingSystemVersion = System.getProperty("os.version");
|
||||||
|
|
||||||
@ -141,9 +144,8 @@ public class DumpInfo {
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public static class NetworkInfo {
|
public static class NetworkInfo {
|
||||||
|
|
||||||
private String internalIP;
|
|
||||||
private final boolean dockerCheck;
|
private final boolean dockerCheck;
|
||||||
|
private String internalIP;
|
||||||
|
|
||||||
NetworkInfo() {
|
NetworkInfo() {
|
||||||
if (AsteriskSerializer.showSensitive) {
|
if (AsteriskSerializer.showSensitive) {
|
||||||
@ -156,7 +158,8 @@ public class DumpInfo {
|
|||||||
try {
|
try {
|
||||||
// Fallback to the normal way of getting the local IP
|
// Fallback to the normal way of getting the local IP
|
||||||
this.internalIP = InetAddress.getLocalHost().getHostAddress();
|
this.internalIP = InetAddress.getLocalHost().getHostAddress();
|
||||||
} catch (UnknownHostException ignored) { }
|
} catch (UnknownHostException ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Sometimes the internal IP is the external IP...
|
// Sometimes the internal IP is the external IP...
|
||||||
@ -169,7 +172,6 @@ public class DumpInfo {
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public static class MCInfo {
|
public static class MCInfo {
|
||||||
|
|
||||||
private final String bedrockVersion;
|
private final String bedrockVersion;
|
||||||
private final int bedrockProtocol;
|
private final int bedrockProtocol;
|
||||||
private final String javaVersion;
|
private final String javaVersion;
|
||||||
@ -185,7 +187,6 @@ public class DumpInfo {
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public static class RamInfo {
|
public static class RamInfo {
|
||||||
|
|
||||||
private final long free;
|
private final long free;
|
||||||
private final long total;
|
private final long total;
|
||||||
private final long max;
|
private final long max;
|
||||||
|
@ -41,7 +41,7 @@ import org.geysermc.connector.entity.type.EntityType;
|
|||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.utils.FireworkColor;
|
import org.geysermc.connector.utils.FireworkColor;
|
||||||
import org.geysermc.connector.utils.MathUtils;
|
import org.geysermc.connector.utils.MathUtils;
|
||||||
import org.geysermc.floodgate.util.DeviceOS;
|
import org.geysermc.floodgate.util.DeviceOs;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -68,7 +68,8 @@ public class FireworkEntity extends Entity {
|
|||||||
|
|
||||||
// TODO: Remove once Mojang fixes bugs with fireworks crashing clients on these specific devices.
|
// TODO: Remove once Mojang fixes bugs with fireworks crashing clients on these specific devices.
|
||||||
// https://bugs.mojang.com/browse/MCPE-89115
|
// https://bugs.mojang.com/browse/MCPE-89115
|
||||||
if (session.getClientData().getDeviceOS() == DeviceOS.XBOX_ONE || session.getClientData().getDeviceOS() == DeviceOS.ORBIS) {
|
if (session.getClientData().getDeviceOs() == DeviceOs.XBOX
|
||||||
|
|| session.getClientData().getDeviceOs() == DeviceOs.PS4) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,6 @@ import org.geysermc.connector.GeyserConnector;
|
|||||||
import org.geysermc.connector.common.AuthType;
|
import org.geysermc.connector.common.AuthType;
|
||||||
import org.geysermc.connector.configuration.GeyserConfiguration;
|
import org.geysermc.connector.configuration.GeyserConfiguration;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.session.cache.AdvancementsCache;
|
|
||||||
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
|
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
|
||||||
import org.geysermc.connector.network.translators.item.ItemRegistry;
|
import org.geysermc.connector.network.translators.item.ItemRegistry;
|
||||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator1_17_0;
|
import org.geysermc.connector.network.translators.world.block.BlockTranslator1_17_0;
|
||||||
@ -150,22 +149,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(ModalFormResponsePacket packet) {
|
public boolean handle(ModalFormResponsePacket packet) {
|
||||||
switch (packet.getFormId()) {
|
session.getFormCache().handleResponse(packet);
|
||||||
case AdvancementsCache.ADVANCEMENT_INFO_FORM_ID:
|
return true;
|
||||||
return session.getAdvancementsCache().handleInfoForm(packet.getFormData());
|
|
||||||
case AdvancementsCache.ADVANCEMENTS_LIST_FORM_ID:
|
|
||||||
return session.getAdvancementsCache().handleListForm(packet.getFormData());
|
|
||||||
case AdvancementsCache.ADVANCEMENTS_MENU_FORM_ID:
|
|
||||||
return session.getAdvancementsCache().handleMenuForm(packet.getFormData());
|
|
||||||
case SettingsUtils.SETTINGS_FORM_ID:
|
|
||||||
return SettingsUtils.handleSettingsForm(session, packet.getFormData());
|
|
||||||
case StatisticsUtils.STATISTICS_LIST_FORM_ID:
|
|
||||||
return StatisticsUtils.handleListForm(session, packet.getFormData());
|
|
||||||
case StatisticsUtils.STATISTICS_MENU_FORM_ID:
|
|
||||||
return StatisticsUtils.handleMenuForm(session, packet.getFormData());
|
|
||||||
}
|
|
||||||
|
|
||||||
return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean couldLoginUserByName(String bedrockUsername) {
|
private boolean couldLoginUserByName(String bedrockUsername) {
|
||||||
@ -193,7 +178,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||||||
if (!session.isLoggedIn() && !session.isLoggingIn() && session.getRemoteAuthType() == AuthType.ONLINE) {
|
if (!session.isLoggedIn() && !session.isLoggingIn() && session.getRemoteAuthType() == AuthType.ONLINE) {
|
||||||
// TODO it is safer to key authentication on something that won't change (UUID, not username)
|
// TODO it is safer to key authentication on something that won't change (UUID, not username)
|
||||||
if (!couldLoginUserByName(session.getAuthData().getName())) {
|
if (!couldLoginUserByName(session.getAuthData().getName())) {
|
||||||
LoginEncryptionUtils.showLoginWindow(session);
|
LoginEncryptionUtils.buildAndShowLoginWindow(session);
|
||||||
}
|
}
|
||||||
// else we were able to log the user in
|
// else we were able to log the user in
|
||||||
}
|
}
|
||||||
|
@ -70,8 +70,6 @@ import lombok.AccessLevel;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.geysermc.common.window.CustomFormWindow;
|
|
||||||
import org.geysermc.common.window.FormWindow;
|
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
import org.geysermc.connector.command.CommandSender;
|
import org.geysermc.connector.command.CommandSender;
|
||||||
import org.geysermc.connector.common.AuthType;
|
import org.geysermc.connector.common.AuthType;
|
||||||
@ -96,18 +94,17 @@ import org.geysermc.connector.network.translators.collision.CollisionManager;
|
|||||||
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
|
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
|
||||||
import org.geysermc.connector.network.translators.item.ItemRegistry;
|
import org.geysermc.connector.network.translators.item.ItemRegistry;
|
||||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||||
|
import org.geysermc.connector.skin.FloodgateSkinUploader;
|
||||||
import org.geysermc.connector.skin.SkinManager;
|
import org.geysermc.connector.skin.SkinManager;
|
||||||
import org.geysermc.connector.utils.*;
|
import org.geysermc.connector.utils.*;
|
||||||
|
import org.geysermc.cumulus.Form;
|
||||||
|
import org.geysermc.cumulus.util.FormBuilder;
|
||||||
|
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||||
import org.geysermc.floodgate.util.BedrockData;
|
import org.geysermc.floodgate.util.BedrockData;
|
||||||
import org.geysermc.floodgate.util.EncryptionUtil;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@ -143,10 +140,10 @@ public class GeyserSession implements CommandSender {
|
|||||||
private ChunkCache chunkCache;
|
private ChunkCache chunkCache;
|
||||||
private EntityCache entityCache;
|
private EntityCache entityCache;
|
||||||
private EntityEffectCache effectCache;
|
private EntityEffectCache effectCache;
|
||||||
|
private final FormCache formCache;
|
||||||
private final PreferencesCache preferencesCache;
|
private final PreferencesCache preferencesCache;
|
||||||
private final TagCache tagCache;
|
private final TagCache tagCache;
|
||||||
private WorldCache worldCache;
|
private WorldCache worldCache;
|
||||||
private WindowCache windowCache;
|
|
||||||
private final Int2ObjectMap<TeleportCache> teleportMap = new Int2ObjectOpenHashMap<>();
|
private final Int2ObjectMap<TeleportCache> teleportMap = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
private final PlayerInventory playerInventory;
|
private final PlayerInventory playerInventory;
|
||||||
@ -362,9 +359,6 @@ public class GeyserSession implements CommandSender {
|
|||||||
|
|
||||||
private boolean reducedDebugInfo = false;
|
private boolean reducedDebugInfo = false;
|
||||||
|
|
||||||
@Setter
|
|
||||||
private CustomFormWindow settingsForm;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The op permission level set by the server
|
* The op permission level set by the server
|
||||||
*/
|
*/
|
||||||
@ -408,7 +402,7 @@ public class GeyserSession implements CommandSender {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the last text inputted into a sign.
|
* Stores the last text inputted into a sign.
|
||||||
*
|
* <p>
|
||||||
* Bedrock sends packets every time you update the sign, Java only wants the final packet.
|
* Bedrock sends packets every time you update the sign, Java only wants the final packet.
|
||||||
* Until we determine that the user has finished editing, we save the sign's current status.
|
* Until we determine that the user has finished editing, we save the sign's current status.
|
||||||
*/
|
*/
|
||||||
@ -445,10 +439,10 @@ public class GeyserSession implements CommandSender {
|
|||||||
this.chunkCache = new ChunkCache(this);
|
this.chunkCache = new ChunkCache(this);
|
||||||
this.entityCache = new EntityCache(this);
|
this.entityCache = new EntityCache(this);
|
||||||
this.effectCache = new EntityEffectCache();
|
this.effectCache = new EntityEffectCache();
|
||||||
|
this.formCache = new FormCache(this);
|
||||||
this.preferencesCache = new PreferencesCache(this);
|
this.preferencesCache = new PreferencesCache(this);
|
||||||
this.tagCache = new TagCache();
|
this.tagCache = new TagCache();
|
||||||
this.worldCache = new WorldCache(this);
|
this.worldCache = new WorldCache(this);
|
||||||
this.windowCache = new WindowCache(this);
|
|
||||||
|
|
||||||
this.collisionManager = new CollisionManager(this);
|
this.collisionManager = new CollisionManager(this);
|
||||||
|
|
||||||
@ -577,7 +571,16 @@ public class GeyserSession implements CommandSender {
|
|||||||
|
|
||||||
protocol = new MinecraftProtocol(authenticationService.getSelectedProfile(), authenticationService.getAccessToken());
|
protocol = new MinecraftProtocol(authenticationService.getSelectedProfile(), authenticationService.getAccessToken());
|
||||||
} else {
|
} else {
|
||||||
protocol = new MinecraftProtocol(username);
|
// always replace spaces when using Floodgate,
|
||||||
|
// as usernames with spaces cause issues with Bungeecord's login cycle.
|
||||||
|
// However, this doesn't affect the final username as Floodgate is still in charge of that.
|
||||||
|
// So if you have (for example) replace spaces enabled on Floodgate the spaces will re-appear.
|
||||||
|
String validUsername = username;
|
||||||
|
if (remoteAuthType == AuthType.FLOODGATE) {
|
||||||
|
validUsername = username.replace(' ', '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol = new MinecraftProtocol(validUsername);
|
||||||
}
|
}
|
||||||
|
|
||||||
connectDownstream();
|
connectDownstream();
|
||||||
@ -606,7 +609,7 @@ public class GeyserSession implements CommandSender {
|
|||||||
MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID);
|
MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID);
|
||||||
|
|
||||||
MsaAuthenticationService.MsCodeResponse response = msaAuthenticationService.getAuthCode();
|
MsaAuthenticationService.MsCodeResponse response = msaAuthenticationService.getAuthCode();
|
||||||
LoginEncryptionUtils.showMicrosoftCodeWindow(this, response);
|
LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response);
|
||||||
|
|
||||||
// This just looks cool
|
// This just looks cool
|
||||||
SetTimePacket packet = new SetTimePacket();
|
SetTimePacket packet = new SetTimePacket();
|
||||||
@ -651,24 +654,6 @@ public class GeyserSession implements CommandSender {
|
|||||||
*/
|
*/
|
||||||
private void connectDownstream() {
|
private void connectDownstream() {
|
||||||
boolean floodgate = this.remoteAuthType == AuthType.FLOODGATE;
|
boolean floodgate = this.remoteAuthType == AuthType.FLOODGATE;
|
||||||
final PublicKey publicKey;
|
|
||||||
|
|
||||||
if (floodgate) {
|
|
||||||
PublicKey key = null;
|
|
||||||
try {
|
|
||||||
key = EncryptionUtil.getKeyFromFile(
|
|
||||||
connector.getConfig().getFloodgateKeyPath(),
|
|
||||||
PublicKey.class
|
|
||||||
);
|
|
||||||
} catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
|
|
||||||
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e);
|
|
||||||
}
|
|
||||||
publicKey = key;
|
|
||||||
} else publicKey = null;
|
|
||||||
|
|
||||||
if (publicKey != null) {
|
|
||||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start ticking
|
// Start ticking
|
||||||
tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS);
|
tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS);
|
||||||
@ -690,22 +675,40 @@ public class GeyserSession implements CommandSender {
|
|||||||
if (event.getPacket() instanceof HandshakePacket) {
|
if (event.getPacket() instanceof HandshakePacket) {
|
||||||
String addressSuffix;
|
String addressSuffix;
|
||||||
if (floodgate) {
|
if (floodgate) {
|
||||||
String encrypted = "";
|
byte[] encryptedData;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData(
|
FloodgateSkinUploader skinUploader = connector.getSkinUploader();
|
||||||
|
FloodgateCipher cipher = connector.getCipher();
|
||||||
|
|
||||||
|
encryptedData = cipher.encryptFromString(BedrockData.of(
|
||||||
clientData.getGameVersion(),
|
clientData.getGameVersion(),
|
||||||
authData.getName(),
|
authData.getName(),
|
||||||
authData.getXboxUUID(),
|
authData.getXboxUUID(),
|
||||||
clientData.getDeviceOS().ordinal(),
|
clientData.getDeviceOs().ordinal(),
|
||||||
clientData.getLanguageCode(),
|
clientData.getLanguageCode(),
|
||||||
|
clientData.getUiProfile().ordinal(),
|
||||||
clientData.getCurrentInputMode().ordinal(),
|
clientData.getCurrentInputMode().ordinal(),
|
||||||
upstream.getAddress().getAddress().getHostAddress()
|
upstream.getAddress().getAddress().getHostAddress(),
|
||||||
));
|
skinUploader.getId(),
|
||||||
|
skinUploader.getVerifyCode(),
|
||||||
|
connector.getTimeSyncer()
|
||||||
|
).toString());
|
||||||
|
|
||||||
|
if (!connector.getTimeSyncer().hasUsefulOffset()) {
|
||||||
|
connector.getLogger().warning(
|
||||||
|
"We couldn't make sure that your system clock is accurate. " +
|
||||||
|
"This can cause issues with logging in."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
|
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
|
||||||
|
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode()));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addressSuffix = '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted;
|
addressSuffix = '\0' + new String(encryptedData, StandardCharsets.UTF_8);
|
||||||
} else {
|
} else {
|
||||||
addressSuffix = "";
|
addressSuffix = "";
|
||||||
}
|
}
|
||||||
@ -788,6 +791,12 @@ public class GeyserSession implements CommandSender {
|
|||||||
if (remoteAuthType == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
|
if (remoteAuthType == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
|
||||||
SkinManager.handleBedrockSkin(playerEntity, clientData);
|
SkinManager.handleBedrockSkin(playerEntity, clientData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (remoteAuthType == AuthType.FLOODGATE) {
|
||||||
|
// We'll send the skin upload a bit after the handshake packet (aka this packet),
|
||||||
|
// because otherwise the global server returns the data too fast.
|
||||||
|
getAuthData().upload(connector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);
|
PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);
|
||||||
@ -832,7 +841,6 @@ public class GeyserSession implements CommandSender {
|
|||||||
this.entityCache = null;
|
this.entityCache = null;
|
||||||
this.effectCache = null;
|
this.effectCache = null;
|
||||||
this.worldCache = null;
|
this.worldCache = null;
|
||||||
this.windowCache = null;
|
|
||||||
|
|
||||||
closed = true;
|
closed = true;
|
||||||
}
|
}
|
||||||
@ -973,10 +981,6 @@ public class GeyserSession implements CommandSender {
|
|||||||
return clientData.getLanguageCode();
|
return clientData.getLanguageCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendForm(FormWindow window, int id) {
|
|
||||||
windowCache.showWindow(window, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRenderDistance(int renderDistance) {
|
public void setRenderDistance(int renderDistance) {
|
||||||
renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle
|
renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle
|
||||||
this.renderDistance = renderDistance;
|
this.renderDistance = renderDistance;
|
||||||
@ -990,8 +994,12 @@ public class GeyserSession implements CommandSender {
|
|||||||
return this.upstream.getAddress();
|
return this.upstream.getAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendForm(FormWindow window) {
|
public void sendForm(Form form) {
|
||||||
windowCache.showWindow(window);
|
formCache.showForm(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendForm(FormBuilder<?, ?> formBuilder) {
|
||||||
|
formCache.showForm(formBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startGame() {
|
private void startGame() {
|
||||||
@ -1050,7 +1058,7 @@ public class GeyserSession implements CommandSender {
|
|||||||
settings.setRewindHistorySize(0);
|
settings.setRewindHistorySize(0);
|
||||||
settings.setServerAuthoritativeBlockBreaking(false);
|
settings.setServerAuthoritativeBlockBreaking(false);
|
||||||
startGamePacket.setPlayerMovementSettings(settings);
|
startGamePacket.setPlayerMovementSettings(settings);
|
||||||
|
|
||||||
upstream.sendPacket(startGamePacket);
|
upstream.sendPacket(startGamePacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1168,7 +1176,7 @@ public class GeyserSession implements CommandSender {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Queue a packet to be sent to player.
|
* Queue a packet to be sent to player.
|
||||||
*
|
*
|
||||||
* @param packet the bedrock packet from the NukkitX protocol lib
|
* @param packet the bedrock packet from the NukkitX protocol lib
|
||||||
*/
|
*/
|
||||||
public void sendUpstreamPacket(BedrockPacket packet) {
|
public void sendUpstreamPacket(BedrockPacket packet) {
|
||||||
@ -1233,7 +1241,7 @@ public class GeyserSession implements CommandSender {
|
|||||||
* Send a gamerule value to the client
|
* Send a gamerule value to the client
|
||||||
*
|
*
|
||||||
* @param gameRule The gamerule to send
|
* @param gameRule The gamerule to send
|
||||||
* @param value The value of the gamerule
|
* @param value The value of the gamerule
|
||||||
*/
|
*/
|
||||||
public void sendGameRule(String gameRule, Object value) {
|
public void sendGameRule(String gameRule, Object value) {
|
||||||
GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket();
|
GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket();
|
||||||
|
@ -25,16 +25,26 @@
|
|||||||
|
|
||||||
package org.geysermc.connector.network.session.auth;
|
package org.geysermc.connector.network.session.auth;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.geysermc.connector.GeyserConnector;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Getter
|
@RequiredArgsConstructor
|
||||||
@AllArgsConstructor
|
|
||||||
public class AuthData {
|
public class AuthData {
|
||||||
|
@Getter private final String name;
|
||||||
|
@Getter private final UUID UUID;
|
||||||
|
@Getter private final String xboxUUID;
|
||||||
|
|
||||||
private String name;
|
private final JsonNode certChainData;
|
||||||
private UUID UUID;
|
private final String clientData;
|
||||||
private String xboxUUID;
|
|
||||||
|
public void upload(GeyserConnector connector) {
|
||||||
|
// we can't upload the skin in LoginEncryptionUtil since the global server would return
|
||||||
|
// the skin too fast, that's why we upload it after we know for sure that the target server
|
||||||
|
// is ready to handle the result of the global server
|
||||||
|
connector.getSkinUploader().uploadSkin(certChainData, clientData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,17 +25,18 @@
|
|||||||
|
|
||||||
package org.geysermc.connector.network.session.auth;
|
package org.geysermc.connector.network.session.auth;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.geysermc.floodgate.util.DeviceOS;
|
import org.geysermc.floodgate.util.DeviceOs;
|
||||||
|
import org.geysermc.floodgate.util.InputMode;
|
||||||
|
import org.geysermc.floodgate.util.UiProfile;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
@Getter
|
@Getter
|
||||||
public class BedrockClientData {
|
public final class BedrockClientData {
|
||||||
@JsonProperty(value = "GameVersion")
|
@JsonProperty(value = "GameVersion")
|
||||||
private String gameVersion;
|
private String gameVersion;
|
||||||
@JsonProperty(value = "ServerAddress")
|
@JsonProperty(value = "ServerAddress")
|
||||||
@ -77,9 +78,9 @@ public class BedrockClientData {
|
|||||||
@JsonProperty(value = "DeviceModel")
|
@JsonProperty(value = "DeviceModel")
|
||||||
private String deviceModel;
|
private String deviceModel;
|
||||||
@JsonProperty(value = "DeviceOS")
|
@JsonProperty(value = "DeviceOS")
|
||||||
private DeviceOS deviceOS;
|
private DeviceOs deviceOs;
|
||||||
@JsonProperty(value = "UIProfile")
|
@JsonProperty(value = "UIProfile")
|
||||||
private UIProfile uiProfile;
|
private UiProfile uiProfile;
|
||||||
@JsonProperty(value = "GuiScale")
|
@JsonProperty(value = "GuiScale")
|
||||||
private int guiScale;
|
private int guiScale;
|
||||||
@JsonProperty(value = "CurrentInputMode")
|
@JsonProperty(value = "CurrentInputMode")
|
||||||
@ -106,18 +107,19 @@ public class BedrockClientData {
|
|||||||
@JsonProperty(value = "PlayFabId")
|
@JsonProperty(value = "PlayFabId")
|
||||||
private String playFabId;
|
private String playFabId;
|
||||||
|
|
||||||
public enum UIProfile {
|
public DeviceOs getDeviceOs() {
|
||||||
@JsonEnumDefaultValue
|
return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN;
|
||||||
CLASSIC,
|
|
||||||
POCKET
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum InputMode {
|
public InputMode getCurrentInputMode() {
|
||||||
@JsonEnumDefaultValue
|
return currentInputMode != null ? currentInputMode : InputMode.UNKNOWN;
|
||||||
UNKNOWN,
|
}
|
||||||
KEYBOARD_MOUSE,
|
|
||||||
TOUCH, // I guess Touch?
|
public InputMode getDefaultInputMode() {
|
||||||
CONTROLLER,
|
return defaultInputMode != null ? defaultInputMode : InputMode.UNKNOWN;
|
||||||
VR
|
}
|
||||||
|
|
||||||
|
public UiProfile getUiProfile() {
|
||||||
|
return uiProfile != null ? uiProfile : UiProfile.CLASSIC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,26 +29,19 @@ import com.github.steveice10.mc.protocol.data.game.advancement.Advancement;
|
|||||||
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientAdvancementTabPacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientAdvancementTabPacket;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.geysermc.common.window.SimpleFormWindow;
|
|
||||||
import org.geysermc.common.window.button.FormButton;
|
|
||||||
import org.geysermc.common.window.response.SimpleFormResponse;
|
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.chat.MessageTranslator;
|
import org.geysermc.connector.network.translators.chat.MessageTranslator;
|
||||||
import org.geysermc.connector.utils.GeyserAdvancement;
|
import org.geysermc.connector.utils.GeyserAdvancement;
|
||||||
import org.geysermc.connector.utils.LanguageUtils;
|
import org.geysermc.connector.utils.LanguageUtils;
|
||||||
import org.geysermc.connector.utils.LocaleUtils;
|
import org.geysermc.connector.utils.LocaleUtils;
|
||||||
|
import org.geysermc.cumulus.SimpleForm;
|
||||||
|
import org.geysermc.cumulus.response.SimpleFormResponse;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class AdvancementsCache {
|
public class AdvancementsCache {
|
||||||
|
|
||||||
// Different form IDs
|
|
||||||
public static final int ADVANCEMENTS_MENU_FORM_ID = 1341;
|
|
||||||
public static final int ADVANCEMENTS_LIST_FORM_ID = 1342;
|
|
||||||
public static final int ADVANCEMENT_INFO_FORM_ID = 1343;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the player's advancement progress
|
* Stores the player's advancement progress
|
||||||
*/
|
*/
|
||||||
@ -74,73 +67,128 @@ public class AdvancementsCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a form with all advancement categories
|
* Build and send a form with all advancement categories
|
||||||
*
|
|
||||||
* @return The built advancement category menu
|
|
||||||
*/
|
*/
|
||||||
public SimpleFormWindow buildMenuForm() {
|
public void buildAndShowMenuForm() {
|
||||||
// Cache the language for cleaner access
|
SimpleForm.Builder builder =
|
||||||
String language = session.getClientData().getLanguageCode();
|
SimpleForm.builder()
|
||||||
|
.translator(LocaleUtils::getLocaleString, session.getLocale())
|
||||||
|
.title("gui.advancements");
|
||||||
|
|
||||||
// Created menu window for advancement categories
|
boolean hasAdvancements = false;
|
||||||
SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.advancements", language), "");
|
|
||||||
for (Map.Entry<String, GeyserAdvancement> advancement : storedAdvancements.entrySet()) {
|
for (Map.Entry<String, GeyserAdvancement> advancement : storedAdvancements.entrySet()) {
|
||||||
if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement
|
if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement
|
||||||
window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), language)));
|
hasAdvancements = true;
|
||||||
|
builder.button(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), session.getLocale()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.getButtons().isEmpty()) {
|
if (!hasAdvancements) {
|
||||||
window.setContent(LocaleUtils.getLocaleString("advancements.empty", language));
|
builder.content("advancements.empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
return window;
|
builder.responseHandler((form, responseData) -> {
|
||||||
|
SimpleFormResponse response = form.parseResponse(responseData);
|
||||||
|
if (!response.isCorrect()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String id = "";
|
||||||
|
|
||||||
|
int advancementIndex = 0;
|
||||||
|
for (Map.Entry<String, GeyserAdvancement> advancement : storedAdvancements.entrySet()) {
|
||||||
|
if (advancement.getValue().getParentId() == null) { // Root advancement
|
||||||
|
if (advancementIndex == response.getClickedButtonId()) {
|
||||||
|
id = advancement.getKey();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
advancementIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!id.equals("")) {
|
||||||
|
if (id.equals(currentAdvancementCategoryId)) {
|
||||||
|
// The server thinks we are already on this tab
|
||||||
|
buildAndShowListForm();
|
||||||
|
} else {
|
||||||
|
// Send a packet indicating that we intend to open this particular advancement window
|
||||||
|
ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id);
|
||||||
|
session.sendDownstreamPacket(packet);
|
||||||
|
// Wait for a response there
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
session.sendForm(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the list of advancements
|
* Build and send the list of advancements
|
||||||
*
|
|
||||||
* @return The built list form
|
|
||||||
*/
|
*/
|
||||||
public SimpleFormWindow buildListForm() {
|
public void buildAndShowListForm() {
|
||||||
// Cache the language for easier access
|
|
||||||
String language = session.getLocale();
|
|
||||||
String id = currentAdvancementCategoryId;
|
|
||||||
GeyserAdvancement categoryAdvancement = storedAdvancements.get(currentAdvancementCategoryId);
|
GeyserAdvancement categoryAdvancement = storedAdvancements.get(currentAdvancementCategoryId);
|
||||||
|
String language = session.getLocale();
|
||||||
|
|
||||||
// Create the window
|
SimpleForm.Builder builder =
|
||||||
SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language),
|
SimpleForm.builder()
|
||||||
MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language));
|
.title(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language))
|
||||||
|
.content(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language));
|
||||||
|
|
||||||
if (id != null) {
|
if (currentAdvancementCategoryId != null) {
|
||||||
for (Map.Entry<String, GeyserAdvancement> advancementEntry : storedAdvancements.entrySet()) {
|
for (GeyserAdvancement advancement : storedAdvancements.values()) {
|
||||||
GeyserAdvancement advancement = advancementEntry.getValue();
|
|
||||||
if (advancement != null) {
|
if (advancement != null) {
|
||||||
if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) {
|
if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) {
|
||||||
boolean earned = isEarned(advancement);
|
boolean color = isEarned(advancement) || !advancement.getDisplayData().isShowToast();
|
||||||
|
builder.button((color ? "§6" : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n');
|
||||||
if (earned || !advancement.getDisplayData().isShowToast()) {
|
|
||||||
window.getButtons().add(new FormButton("§6" + MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n"));
|
|
||||||
} else {
|
|
||||||
window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language)));
|
builder.button(LanguageUtils.getPlayerLocaleString("gui.back", language));
|
||||||
|
|
||||||
return window;
|
builder.responseHandler((form, responseData) -> {
|
||||||
|
SimpleFormResponse response = form.parseResponse(responseData);
|
||||||
|
if (!response.isCorrect()) {
|
||||||
|
// Indicate that we have closed the current advancement tab
|
||||||
|
session.sendDownstreamPacket(new ClientAdvancementTabPacket());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GeyserAdvancement advancement = null;
|
||||||
|
int advancementIndex = 0;
|
||||||
|
// Loop around to find the advancement that the client pressed
|
||||||
|
for (GeyserAdvancement advancementEntry : storedAdvancements.values()) {
|
||||||
|
if (advancementEntry.getParentId() != null &&
|
||||||
|
currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) {
|
||||||
|
if (advancementIndex == response.getClickedButtonId()) {
|
||||||
|
advancement = advancementEntry;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
advancementIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (advancement != null) {
|
||||||
|
buildAndShowInfoForm(advancement);
|
||||||
|
} else {
|
||||||
|
buildAndShowMenuForm();
|
||||||
|
// Indicate that we have closed the current advancement tab
|
||||||
|
session.sendDownstreamPacket(new ClientAdvancementTabPacket());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
session.sendForm(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the advancement display info based on the chosen category
|
* Builds the advancement display info based on the chosen category
|
||||||
*
|
*
|
||||||
* @param advancement The advancement used to create the info display
|
* @param advancement The advancement used to create the info display
|
||||||
* @return The information for the chosen advancement
|
|
||||||
*/
|
*/
|
||||||
public SimpleFormWindow buildInfoForm(GeyserAdvancement advancement) {
|
public void buildAndShowInfoForm(GeyserAdvancement advancement) {
|
||||||
// Cache language for easier access
|
// Cache language for easier access
|
||||||
String language = session.getLocale();
|
String language = session.getLocale();
|
||||||
|
|
||||||
@ -160,16 +208,24 @@ public class AdvancementsCache {
|
|||||||
Parent Advancement: Minecraft // If relevant
|
Parent Advancement: Minecraft // If relevant
|
||||||
*/
|
*/
|
||||||
|
|
||||||
String content = description + "\n\n§f" +
|
String content = description + "\n\n§f" + earnedString + "\n";
|
||||||
earnedString + "\n";
|
|
||||||
if (!currentAdvancementCategoryId.equals(advancement.getParentId())) {
|
if (!currentAdvancementCategoryId.equals(advancement.getParentId())) {
|
||||||
// Only display the parent if it is not the category
|
// Only display the parent if it is not the category
|
||||||
content += LanguageUtils.getPlayerLocaleString("geyser.advancements.parentid", language, MessageTranslator.convertMessage(storedAdvancements.get(advancement.getParentId()).getDisplayData().getTitle(), language));
|
content += LanguageUtils.getPlayerLocaleString("geyser.advancements.parentid", language, MessageTranslator.convertMessage(storedAdvancements.get(advancement.getParentId()).getDisplayData().getTitle(), language));
|
||||||
}
|
}
|
||||||
SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()), content);
|
|
||||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language)));
|
|
||||||
|
|
||||||
return window;
|
session.sendForm(
|
||||||
|
SimpleForm.builder()
|
||||||
|
.title(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()))
|
||||||
|
.content(content)
|
||||||
|
.button(LanguageUtils.getPlayerLocaleString("gui.back", language))
|
||||||
|
.responseHandler((form, responseData) -> {
|
||||||
|
SimpleFormResponse response = form.parseResponse(responseData);
|
||||||
|
if (response.isCorrect()) {
|
||||||
|
buildAndShowListForm();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -209,108 +265,6 @@ public class AdvancementsCache {
|
|||||||
return earned;
|
return earned;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the menu form response
|
|
||||||
*
|
|
||||||
* @param response The response string to parse
|
|
||||||
* @return True if the form was parsed correctly, false if not
|
|
||||||
*/
|
|
||||||
public boolean handleMenuForm(String response) {
|
|
||||||
SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_MENU_FORM_ID);
|
|
||||||
menuForm.setResponse(response);
|
|
||||||
|
|
||||||
SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse();
|
|
||||||
|
|
||||||
String id = "";
|
|
||||||
if (formResponse != null && formResponse.getClickedButton() != null) {
|
|
||||||
int advancementIndex = 0;
|
|
||||||
for (Map.Entry<String, GeyserAdvancement> advancement : storedAdvancements.entrySet()) {
|
|
||||||
if (advancement.getValue().getParentId() == null) { // Root advancement
|
|
||||||
if (advancementIndex == formResponse.getClickedButtonId()) {
|
|
||||||
id = advancement.getKey();
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
advancementIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!id.equals("")) {
|
|
||||||
if (id.equals(currentAdvancementCategoryId)) {
|
|
||||||
// The server thinks we are already on this tab
|
|
||||||
session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID);
|
|
||||||
} else {
|
|
||||||
// Send a packet indicating that we intend to open this particular advancement window
|
|
||||||
ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id);
|
|
||||||
session.sendDownstreamPacket(packet);
|
|
||||||
// Wait for a response there
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the list form response (Advancement category choice)
|
|
||||||
*
|
|
||||||
* @param response The response string to parse
|
|
||||||
* @return True if the form was parsed correctly, false if not
|
|
||||||
*/
|
|
||||||
public boolean handleListForm(String response) {
|
|
||||||
SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_LIST_FORM_ID);
|
|
||||||
listForm.setResponse(response);
|
|
||||||
|
|
||||||
SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse();
|
|
||||||
|
|
||||||
if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) {
|
|
||||||
GeyserAdvancement advancement = null;
|
|
||||||
int advancementIndex = 0;
|
|
||||||
// Loop around to find the advancement that the client pressed
|
|
||||||
for (GeyserAdvancement advancementEntry : storedAdvancements.values()) {
|
|
||||||
if (advancementEntry.getParentId() != null &&
|
|
||||||
currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) {
|
|
||||||
if (advancementIndex == formResponse.getClickedButtonId()) {
|
|
||||||
advancement = advancementEntry;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
advancementIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (advancement != null) {
|
|
||||||
session.sendForm(buildInfoForm(advancement), ADVANCEMENT_INFO_FORM_ID);
|
|
||||||
} else {
|
|
||||||
session.sendForm(buildMenuForm(), ADVANCEMENTS_MENU_FORM_ID);
|
|
||||||
// Indicate that we have closed the current advancement tab
|
|
||||||
session.sendDownstreamPacket(new ClientAdvancementTabPacket());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Indicate that we have closed the current advancement tab
|
|
||||||
session.sendDownstreamPacket(new ClientAdvancementTabPacket());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the info form response
|
|
||||||
*
|
|
||||||
* @param response The response string to parse
|
|
||||||
* @return True if the form was parsed correctly, false if not
|
|
||||||
*/
|
|
||||||
public boolean handleInfoForm(String response) {
|
|
||||||
SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENT_INFO_FORM_ID);
|
|
||||||
listForm.setResponse(response);
|
|
||||||
|
|
||||||
SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse();
|
|
||||||
|
|
||||||
if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) {
|
|
||||||
session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) {
|
public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) {
|
||||||
String base = "\u00a7";
|
String base = "\u00a7";
|
||||||
if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) {
|
if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) {
|
||||||
|
92
connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java
vendored
Normale Datei
92
connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java
vendored
Normale Datei
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.network.session.cache;
|
||||||
|
|
||||||
|
import com.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket;
|
||||||
|
import com.nukkitx.protocol.bedrock.packet.ModalFormResponsePacket;
|
||||||
|
import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.cumulus.Form;
|
||||||
|
import org.geysermc.cumulus.SimpleForm;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class FormCache {
|
||||||
|
private final AtomicInteger formId = new AtomicInteger(0);
|
||||||
|
private final Int2ObjectMap<Form> forms = new Int2ObjectOpenHashMap<>();
|
||||||
|
private final GeyserSession session;
|
||||||
|
|
||||||
|
public int addForm(Form form) {
|
||||||
|
int windowId = formId.getAndIncrement();
|
||||||
|
forms.put(windowId, form);
|
||||||
|
return windowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int showForm(Form form) {
|
||||||
|
int windowId = addForm(form);
|
||||||
|
|
||||||
|
ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket();
|
||||||
|
formRequestPacket.setFormId(windowId);
|
||||||
|
formRequestPacket.setFormData(form.getJsonData());
|
||||||
|
session.sendUpstreamPacket(formRequestPacket);
|
||||||
|
|
||||||
|
// Hack to fix the (url) image loading bug
|
||||||
|
if (form instanceof SimpleForm) {
|
||||||
|
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
|
||||||
|
latencyPacket.setFromServer(true);
|
||||||
|
latencyPacket.setTimestamp(-System.currentTimeMillis());
|
||||||
|
session.getConnector().getGeneralThreadPool().schedule(
|
||||||
|
() -> session.sendUpstreamPacket(latencyPacket),
|
||||||
|
500, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return windowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleResponse(ModalFormResponsePacket response) {
|
||||||
|
Form form = forms.get(response.getFormId());
|
||||||
|
if (form == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Consumer<String> responseConsumer = form.getResponseHandler();
|
||||||
|
if (responseConsumer != null) {
|
||||||
|
responseConsumer.accept(response.getFormData());
|
||||||
|
}
|
||||||
|
|
||||||
|
removeWindow(response.getFormId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeWindow(int id) {
|
||||||
|
return forms.remove(id) != null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.connector.network.session.cache;
|
|
||||||
|
|
||||||
import com.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket;
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import org.geysermc.common.window.FormWindow;
|
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
|
||||||
|
|
||||||
public class WindowCache {
|
|
||||||
|
|
||||||
private final GeyserSession session;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final Int2ObjectMap<FormWindow> windows = new Int2ObjectOpenHashMap<>();
|
|
||||||
|
|
||||||
public WindowCache(GeyserSession session) {
|
|
||||||
this.session = session;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addWindow(FormWindow window) {
|
|
||||||
windows.put(windows.size() + 1, window);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addWindow(FormWindow window, int id) {
|
|
||||||
windows.put(id, window);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showWindow(FormWindow window) {
|
|
||||||
showWindow(window, windows.size() + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showWindow(int id) {
|
|
||||||
if (!windows.containsKey(id))
|
|
||||||
return;
|
|
||||||
|
|
||||||
ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket();
|
|
||||||
formRequestPacket.setFormId(id);
|
|
||||||
formRequestPacket.setFormData(windows.get(id).getJSONData());
|
|
||||||
|
|
||||||
session.sendUpstreamPacket(formRequestPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showWindow(FormWindow window, int id) {
|
|
||||||
ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket();
|
|
||||||
formRequestPacket.setFormId(id);
|
|
||||||
formRequestPacket.setFormData(window.getJSONData());
|
|
||||||
|
|
||||||
session.sendUpstreamPacket(formRequestPacket);
|
|
||||||
|
|
||||||
addWindow(window, id);
|
|
||||||
}
|
|
||||||
}
|
|
@ -27,10 +27,17 @@ package org.geysermc.connector.network.translators.bedrock;
|
|||||||
|
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientKeepAlivePacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientKeepAlivePacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket;
|
import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket;
|
||||||
|
import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||||
|
import org.geysermc.connector.entity.attribute.Attribute;
|
||||||
|
import org.geysermc.connector.entity.attribute.AttributeType;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
import org.geysermc.connector.network.translators.Translator;
|
import org.geysermc.connector.network.translators.Translator;
|
||||||
import org.geysermc.floodgate.util.DeviceOS;
|
import org.geysermc.connector.utils.AttributeUtils;
|
||||||
|
import org.geysermc.floodgate.util.DeviceOs;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to send the forwarded keep alive packet back to the server
|
* Used to send the forwarded keep alive packet back to the server
|
||||||
@ -40,18 +47,38 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<Netwo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(NetworkStackLatencyPacket packet, GeyserSession session) {
|
public void translate(NetworkStackLatencyPacket packet, GeyserSession session) {
|
||||||
if (session.getConnector().getConfig().isForwardPlayerPing()) {
|
long pingId;
|
||||||
long pingId;
|
// so apparently, as of 1.16.200
|
||||||
// so apparently, as of 1.16.200
|
// PS4 divides the network stack latency timestamp FOR US!!!
|
||||||
// PS4 divides the network stack latency timestamp FOR US!!!
|
// WTF
|
||||||
// WTF
|
if (session.getClientData().getDeviceOs().equals(DeviceOs.PS4)) {
|
||||||
if (session.getClientData().getDeviceOS().equals(DeviceOS.NX)) {
|
pingId = packet.getTimestamp();
|
||||||
// Ignore the weird DeviceOS, our order is wrong and will be fixed in Floodgate 2.0
|
} else {
|
||||||
pingId = packet.getTimestamp();
|
pingId = packet.getTimestamp() / 1000;
|
||||||
} else {
|
|
||||||
pingId = packet.getTimestamp() / 1000;
|
|
||||||
}
|
|
||||||
session.sendDownstreamPacket(new ClientKeepAlivePacket(pingId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// negative timestamps are used as hack to fix the url image loading bug
|
||||||
|
if (packet.getTimestamp() > 0) {
|
||||||
|
if (session.getConnector().getConfig().isForwardPlayerPing()) {
|
||||||
|
ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(pingId);
|
||||||
|
session.sendDownstreamPacket(keepAlivePacket);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hack to fix the url image loading bug
|
||||||
|
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||||
|
attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
|
||||||
|
|
||||||
|
Attribute attribute = session.getPlayerEntity().getAttributes().get(AttributeType.EXPERIENCE_LEVEL);
|
||||||
|
if (attribute != null) {
|
||||||
|
attributesPacket.setAttributes(Collections.singletonList(AttributeUtils.getBedrockAttribute(attribute)));
|
||||||
|
} else {
|
||||||
|
attributesPacket.setAttributes(Collections.singletonList(AttributeUtils.getBedrockAttribute(AttributeType.EXPERIENCE_LEVEL.getAttribute(0))));
|
||||||
|
}
|
||||||
|
|
||||||
|
session.getConnector().getGeneralThreadPool().schedule(
|
||||||
|
() -> session.sendUpstreamPacket(attributesPacket),
|
||||||
|
500, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,21 +31,22 @@ import org.geysermc.connector.network.session.GeyserSession;
|
|||||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
import org.geysermc.connector.network.translators.Translator;
|
import org.geysermc.connector.network.translators.Translator;
|
||||||
import org.geysermc.connector.utils.SettingsUtils;
|
import org.geysermc.connector.utils.SettingsUtils;
|
||||||
|
import org.geysermc.cumulus.CustomForm;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@Translator(packet = ServerSettingsRequestPacket.class)
|
@Translator(packet = ServerSettingsRequestPacket.class)
|
||||||
public class BedrockServerSettingsRequestTranslator extends PacketTranslator<ServerSettingsRequestPacket> {
|
public class BedrockServerSettingsRequestTranslator extends PacketTranslator<ServerSettingsRequestPacket> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(ServerSettingsRequestPacket packet, GeyserSession session) {
|
public void translate(ServerSettingsRequestPacket packet, GeyserSession session) {
|
||||||
SettingsUtils.buildForm(session);
|
CustomForm window = SettingsUtils.buildForm(session);
|
||||||
|
int windowId = session.getFormCache().addForm(window);
|
||||||
|
|
||||||
// Fixes https://bugs.mojang.com/browse/MCPE-94012 because of the delay
|
// Fixes https://bugs.mojang.com/browse/MCPE-94012 because of the delay
|
||||||
session.getConnector().getGeneralThreadPool().schedule(() -> {
|
session.getConnector().getGeneralThreadPool().schedule(() -> {
|
||||||
ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket();
|
ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket();
|
||||||
serverSettingsResponsePacket.setFormData(session.getSettingsForm().getJSONData());
|
serverSettingsResponsePacket.setFormData(window.getJsonData());
|
||||||
serverSettingsResponsePacket.setFormId(SettingsUtils.SETTINGS_FORM_ID);
|
serverSettingsResponsePacket.setFormId(windowId);
|
||||||
session.sendUpstreamPacket(serverSettingsResponsePacket);
|
session.sendUpstreamPacket(serverSettingsResponsePacket);
|
||||||
}, 1, TimeUnit.SECONDS);
|
}, 1, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
@ -36,10 +36,10 @@ import org.geysermc.connector.network.translators.Translator;
|
|||||||
*/
|
*/
|
||||||
@Translator(packet = ServerAdvancementTabPacket.class)
|
@Translator(packet = ServerAdvancementTabPacket.class)
|
||||||
public class JavaAdvancementsTabTranslator extends PacketTranslator<ServerAdvancementTabPacket> {
|
public class JavaAdvancementsTabTranslator extends PacketTranslator<ServerAdvancementTabPacket> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(ServerAdvancementTabPacket packet, GeyserSession session) {
|
public void translate(ServerAdvancementTabPacket packet, GeyserSession session) {
|
||||||
session.getAdvancementsCache().setCurrentAdvancementCategoryId(packet.getTabId());
|
AdvancementsCache advancementsCache = session.getAdvancementsCache();
|
||||||
session.sendForm(session.getAdvancementsCache().buildListForm(), AdvancementsCache.ADVANCEMENTS_LIST_FORM_ID);
|
advancementsCache.setCurrentAdvancementCategoryId(packet.getTabId());
|
||||||
|
advancementsCache.buildAndShowListForm();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePack
|
|||||||
import com.nukkitx.protocol.bedrock.data.GameRuleData;
|
import com.nukkitx.protocol.bedrock.data.GameRuleData;
|
||||||
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
|
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
|
||||||
import com.nukkitx.protocol.bedrock.packet.*;
|
import com.nukkitx.protocol.bedrock.packet.*;
|
||||||
|
import org.geysermc.connector.common.AuthType;
|
||||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
@ -100,6 +101,11 @@ public class JavaJoinGameTranslator extends PacketTranslator<ServerJoinGamePacke
|
|||||||
|
|
||||||
session.sendDownstreamPacket(new ClientPluginMessagePacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData()));
|
session.sendDownstreamPacket(new ClientPluginMessagePacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData()));
|
||||||
|
|
||||||
|
// register the plugin messaging channels used in Floodgate
|
||||||
|
if (session.getConnector().getDefaultAuthType() == AuthType.FLOODGATE) {
|
||||||
|
session.sendDownstreamPacket(new ClientPluginMessagePacket("minecraft:register", PluginMessageUtils.getFloodgateRegisterData()));
|
||||||
|
}
|
||||||
|
|
||||||
if (!newDimension.equals(session.getDimension())) {
|
if (!newDimension.equals(session.getDimension())) {
|
||||||
DimensionUtils.switchDimension(session, newDimension);
|
DimensionUtils.switchDimension(session, newDimension);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.network.translators.java;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket;
|
||||||
|
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import org.geysermc.connector.common.AuthType;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
|
import org.geysermc.connector.network.translators.Translator;
|
||||||
|
import org.geysermc.cumulus.Form;
|
||||||
|
import org.geysermc.cumulus.Forms;
|
||||||
|
import org.geysermc.cumulus.util.FormType;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
@Translator(packet = ServerPluginMessagePacket.class)
|
||||||
|
public class JavaPluginMessageTranslator extends PacketTranslator<ServerPluginMessagePacket> {
|
||||||
|
@Override
|
||||||
|
public void translate(ServerPluginMessagePacket packet, GeyserSession session) {
|
||||||
|
// The only plugin messages it has to listen for are Floodgate plugin messages
|
||||||
|
if (session.getConnector().getDefaultAuthType() != AuthType.FLOODGATE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String channel = packet.getChannel();
|
||||||
|
|
||||||
|
if (channel.equals("floodgate:form")) {
|
||||||
|
byte[] data = packet.getData();
|
||||||
|
|
||||||
|
// receive: first byte is form type, second and third are the id, remaining is the form data
|
||||||
|
// respond: first and second byte id, remaining is form response data
|
||||||
|
|
||||||
|
FormType type = FormType.getByOrdinal(data[0]);
|
||||||
|
if (type == null) {
|
||||||
|
throw new NullPointerException(
|
||||||
|
"Got type " + data[0] + " which isn't a valid form type!");
|
||||||
|
}
|
||||||
|
|
||||||
|
String dataString = new String(data, 3, data.length - 3, Charsets.UTF_8);
|
||||||
|
|
||||||
|
Form form = Forms.fromJson(dataString, type);
|
||||||
|
form.setResponseHandler(response -> {
|
||||||
|
byte[] raw = response.getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] finalData = new byte[raw.length + 2];
|
||||||
|
|
||||||
|
finalData[0] = data[1];
|
||||||
|
finalData[1] = data[2];
|
||||||
|
System.arraycopy(raw, 0, finalData, 2, raw.length);
|
||||||
|
|
||||||
|
session.sendDownstreamPacket(new ClientPluginMessagePacket(channel, finalData));
|
||||||
|
});
|
||||||
|
session.sendForm(form);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,7 +40,7 @@ public class JavaStatisticsTranslator extends PacketTranslator<ServerStatisticsP
|
|||||||
|
|
||||||
if (session.isWaitingForStatistics()) {
|
if (session.isWaitingForStatistics()) {
|
||||||
session.setWaitingForStatistics(false);
|
session.setWaitingForStatistics(false);
|
||||||
session.sendForm(StatisticsUtils.buildMenuForm(session), StatisticsUtils.STATISTICS_MENU_FORM_ID);
|
StatisticsUtils.buildAndSendStatisticsMenu(session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,235 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.skin;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.geysermc.connector.GeyserConnector;
|
||||||
|
import org.geysermc.connector.GeyserLogger;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.connector.utils.Constants;
|
||||||
|
import org.geysermc.connector.utils.PluginMessageUtils;
|
||||||
|
import org.geysermc.floodgate.util.WebsocketEventType;
|
||||||
|
import org.java_websocket.client.WebSocketClient;
|
||||||
|
import org.java_websocket.handshake.ServerHandshake;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLException;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.geysermc.connector.utils.PluginMessageUtils.getSkinChannel;
|
||||||
|
|
||||||
|
public final class FloodgateSkinUploader {
|
||||||
|
private final ObjectMapper JACKSON = new ObjectMapper();
|
||||||
|
private final List<String> skinQueue = new ArrayList<>();
|
||||||
|
|
||||||
|
private final GeyserLogger logger;
|
||||||
|
private final WebSocketClient client;
|
||||||
|
|
||||||
|
@Getter private int id;
|
||||||
|
@Getter private String verifyCode;
|
||||||
|
@Getter private int subscribersCount;
|
||||||
|
|
||||||
|
public FloodgateSkinUploader(GeyserConnector connector) {
|
||||||
|
this.logger = connector.getLogger();
|
||||||
|
this.client = new WebSocketClient(Constants.GLOBAL_API_WS_URI) {
|
||||||
|
@Override
|
||||||
|
public void onOpen(ServerHandshake handshake) {
|
||||||
|
setConnectionLostTimeout(11);
|
||||||
|
|
||||||
|
Iterator<String> queueIterator = skinQueue.iterator();
|
||||||
|
while (isOpen() && queueIterator.hasNext()) {
|
||||||
|
send(queueIterator.next());
|
||||||
|
queueIterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String message) {
|
||||||
|
// The reason why I don't like Jackson
|
||||||
|
try {
|
||||||
|
JsonNode node = JACKSON.readTree(message);
|
||||||
|
if (node.has("error")) {
|
||||||
|
logger.error("Got an error: " + node.get("error").asText());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int typeId = node.get("event_id").asInt();
|
||||||
|
WebsocketEventType type = WebsocketEventType.getById(typeId);
|
||||||
|
if (type == null) {
|
||||||
|
logger.warning(String.format(
|
||||||
|
"Got (unknown) type %s. Ensure that Geyser is on the latest version and report this issue!",
|
||||||
|
typeId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case SUBSCRIBER_CREATED:
|
||||||
|
id = node.get("id").asInt();
|
||||||
|
verifyCode = node.get("verify_code").asText();
|
||||||
|
break;
|
||||||
|
case SUBSCRIBER_COUNT:
|
||||||
|
subscribersCount = node.get("subscribers_count").asInt();
|
||||||
|
break;
|
||||||
|
case SKIN_UPLOADED:
|
||||||
|
// if Geyser is the only subscriber we have send it to the server manually
|
||||||
|
// otherwise it's handled by the Floodgate plugin subscribers
|
||||||
|
if (subscribersCount != 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
String xuid = node.get("xuid").asText();
|
||||||
|
GeyserSession session = connector.getPlayerByXuid(xuid);
|
||||||
|
|
||||||
|
if (session != null) {
|
||||||
|
if (!node.get("success").asBoolean()) {
|
||||||
|
logger.info("Failed to upload skin for " + session.getName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode data = node.get("data");
|
||||||
|
|
||||||
|
String value = data.get("value").asText();
|
||||||
|
String signature = data.get("signature").asText();
|
||||||
|
|
||||||
|
byte[] bytes = (value + '\0' + signature)
|
||||||
|
.getBytes(StandardCharsets.UTF_8);
|
||||||
|
PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LOG_MESSAGE:
|
||||||
|
String logMessage = node.get("message").asText();
|
||||||
|
switch (node.get("priority").asInt()) {
|
||||||
|
case -1:
|
||||||
|
logger.debug("Got a message from skin uploader: " + logMessage);
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
logger.info("Got a message from skin uploader: " +logMessage);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
logger.error("Got a message from skin uploader: " + logMessage);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.info(logMessage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NEWS_ADDED:
|
||||||
|
//todo
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error while receiving a message", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose(int code, String reason, boolean remote) {
|
||||||
|
if (reason != null && !reason.isEmpty()) {
|
||||||
|
// The reason why I don't like Jackson
|
||||||
|
try {
|
||||||
|
JsonNode node = JACKSON.readTree(reason);
|
||||||
|
// info means that the uploader itself did nothing wrong
|
||||||
|
if (node.has("info")) {
|
||||||
|
String info = node.get("info").asText();
|
||||||
|
logger.debug("Got disconnected from the skin uploader: " + info);
|
||||||
|
}
|
||||||
|
// error means that the uploader did something wrong
|
||||||
|
if (node.has("error")) {
|
||||||
|
String error = node.get("error").asText();
|
||||||
|
logger.info("Got disconnected from the skin uploader: " + error);
|
||||||
|
}
|
||||||
|
} catch (JsonProcessingException ignored) {
|
||||||
|
// ignore invalid json
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error while handling onClose", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// try to reconnect (which will make a new id and verify token) after a few seconds
|
||||||
|
reconnectLater(connector);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception ex) {
|
||||||
|
if (ex instanceof ConnectException || ex instanceof SSLException) {
|
||||||
|
if (logger.isDebug()) {
|
||||||
|
logger.error("[debug] Got an error", ex);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.error("Got an error", ex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void uploadSkin(JsonNode chainData, String clientData) {
|
||||||
|
if (chainData == null || !chainData.isArray() || clientData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectNode node = JACKSON.createObjectNode();
|
||||||
|
node.set("chain_data", chainData);
|
||||||
|
node.put("client_data", clientData);
|
||||||
|
|
||||||
|
// The reason why I don't like Jackson
|
||||||
|
String jsonString;
|
||||||
|
try {
|
||||||
|
jsonString = JACKSON.writeValueAsString(node);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to upload skin", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.isOpen()) {
|
||||||
|
client.send(jsonString);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
skinQueue.add(jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reconnectLater(GeyserConnector connector) {
|
||||||
|
long additionalTime = ThreadLocalRandom.current().nextInt(7);
|
||||||
|
// we don't have to check the result. onClose will handle that for us
|
||||||
|
connector.getGeneralThreadPool()
|
||||||
|
.schedule(client::reconnect, 8 + additionalTime, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FloodgateSkinUploader start() {
|
||||||
|
client.connect();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
}
|
@ -86,7 +86,7 @@ public class SkinProvider {
|
|||||||
public static final String EARS_GEOMETRY_SLIM;
|
public static final String EARS_GEOMETRY_SLIM;
|
||||||
public static final SkinGeometry SKULL_GEOMETRY;
|
public static final SkinGeometry SKULL_GEOMETRY;
|
||||||
|
|
||||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
/* Load in the normal ears geometry */
|
/* Load in the normal ears geometry */
|
||||||
@ -521,7 +521,7 @@ public class SkinProvider {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BufferedImage scale(BufferedImage bufferedImage, int newWidth, int newHeight) {
|
public static BufferedImage scale(BufferedImage bufferedImage, int newWidth, int newHeight) {
|
||||||
BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
|
BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
|
||||||
Graphics2D g2 = resized.createGraphics();
|
Graphics2D g2 = resized.createGraphics();
|
||||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
@ -581,7 +581,6 @@ public class SkinProvider {
|
|||||||
outputStream.write((rgba >> 24) & 0xFF);
|
outputStream.write((rgba >> 24) & 0xFF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return outputStream.toByteArray();
|
return outputStream.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,35 +23,32 @@
|
|||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.common.window.button;
|
package org.geysermc.connector.utils;
|
||||||
|
|
||||||
import lombok.Getter;
|
import java.net.URI;
|
||||||
import lombok.Setter;
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class FormButton {
|
public final class Constants {
|
||||||
|
public static final URI GLOBAL_API_WS_URI;
|
||||||
|
public static final String NTP_SERVER = "time.cloudflare.com";
|
||||||
|
|
||||||
@Getter
|
public static final Set<String> NEWS_PROJECT_LIST = Collections.unmodifiableSet(
|
||||||
@Setter
|
new HashSet<>(Arrays.asList("geyser", "floodgate"))
|
||||||
private String text;
|
);
|
||||||
|
|
||||||
@Getter
|
public static final String NEWS_OVERVIEW_URL = "https://api.geysermc.org/v1/news";
|
||||||
private FormImage image;
|
|
||||||
|
|
||||||
public FormButton(String text) {
|
static {
|
||||||
this.text = text;
|
URI wsUri = null;
|
||||||
}
|
try {
|
||||||
|
wsUri = new URI("wss://api.geysermc.org/ws");
|
||||||
public FormButton(String text, FormImage image) {
|
} catch (URISyntaxException e) {
|
||||||
this.text = text;
|
e.printStackTrace();
|
||||||
|
|
||||||
if (image.getData() != null && !image.getData().isEmpty()) {
|
|
||||||
this.image = image;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setImage(FormImage image) {
|
|
||||||
if (image.getData() != null && !image.getData().isEmpty()) {
|
|
||||||
this.image = image;
|
|
||||||
}
|
}
|
||||||
|
GLOBAL_API_WS_URI = wsUri;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -60,7 +60,9 @@ public class LanguageUtils {
|
|||||||
public static void loadGeyserLocale(String locale) {
|
public static void loadGeyserLocale(String locale) {
|
||||||
locale = formatLocale(locale);
|
locale = formatLocale(locale);
|
||||||
// Don't load the locale if it's already loaded.
|
// Don't load the locale if it's already loaded.
|
||||||
if (LOCALE_MAPPINGS.containsKey(locale)) return;
|
if (LOCALE_MAPPINGS.containsKey(locale)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
InputStream localeStream = GeyserConnector.class.getClassLoader().getResourceAsStream("languages/texts/" + locale + ".properties");
|
InputStream localeStream = GeyserConnector.class.getClassLoader().getResourceAsStream("languages/texts/" + locale + ".properties");
|
||||||
|
|
||||||
@ -113,7 +115,7 @@ public class LanguageUtils {
|
|||||||
|
|
||||||
// Try and get the key from the default locale
|
// Try and get the key from the default locale
|
||||||
if (formatString == null) {
|
if (formatString == null) {
|
||||||
properties = LOCALE_MAPPINGS.get(formatLocale(getDefaultLocale()));
|
properties = LOCALE_MAPPINGS.get(getDefaultLocale());
|
||||||
formatString = properties.getProperty(key);
|
formatString = properties.getProperty(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +127,7 @@ public class LanguageUtils {
|
|||||||
|
|
||||||
// Final fallback
|
// Final fallback
|
||||||
if (formatString == null) {
|
if (formatString == null) {
|
||||||
formatString = key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MessageFormat.format(formatString.replace("'", "''").replace("&", "\u00a7"), values);
|
return MessageFormat.format(formatString.replace("'", "''").replace("&", "\u00a7"), values);
|
||||||
@ -151,7 +153,10 @@ public class LanguageUtils {
|
|||||||
* @return the current default locale
|
* @return the current default locale
|
||||||
*/
|
*/
|
||||||
public static String getDefaultLocale() {
|
public static String getDefaultLocale() {
|
||||||
if (CACHED_LOCALE != null) return CACHED_LOCALE; // We definitely know the locale the user is using
|
if (CACHED_LOCALE != null) {
|
||||||
|
return CACHED_LOCALE; // We definitely know the locale the user is using
|
||||||
|
}
|
||||||
|
|
||||||
String locale;
|
String locale;
|
||||||
boolean isValid = true;
|
boolean isValid = true;
|
||||||
if (GeyserConnector.getInstance() != null &&
|
if (GeyserConnector.getInstance() != null &&
|
||||||
|
@ -35,18 +35,17 @@ import com.nukkitx.network.util.Preconditions;
|
|||||||
import com.nukkitx.protocol.bedrock.packet.LoginPacket;
|
import com.nukkitx.protocol.bedrock.packet.LoginPacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket;
|
import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket;
|
||||||
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
|
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
|
||||||
import org.geysermc.common.window.*;
|
|
||||||
import org.geysermc.common.window.button.FormButton;
|
|
||||||
import org.geysermc.common.window.component.InputComponent;
|
|
||||||
import org.geysermc.common.window.component.LabelComponent;
|
|
||||||
import org.geysermc.common.window.response.CustomFormResponse;
|
|
||||||
import org.geysermc.common.window.response.ModalFormResponse;
|
|
||||||
import org.geysermc.common.window.response.SimpleFormResponse;
|
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
|
import org.geysermc.connector.configuration.GeyserConfiguration;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.session.auth.AuthData;
|
import org.geysermc.connector.network.session.auth.AuthData;
|
||||||
import org.geysermc.connector.network.session.auth.BedrockClientData;
|
import org.geysermc.connector.network.session.auth.BedrockClientData;
|
||||||
import org.geysermc.connector.network.session.cache.WindowCache;
|
import org.geysermc.cumulus.CustomForm;
|
||||||
|
import org.geysermc.cumulus.ModalForm;
|
||||||
|
import org.geysermc.cumulus.SimpleForm;
|
||||||
|
import org.geysermc.cumulus.response.CustomFormResponse;
|
||||||
|
import org.geysermc.cumulus.response.ModalFormResponse;
|
||||||
|
import org.geysermc.cumulus.response.SimpleFormResponse;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -73,7 +72,7 @@ public class LoginEncryptionUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lastKey != null) {
|
if (lastKey != null) {
|
||||||
if (!EncryptionUtils.verifyJwt(jwt, lastKey)) return false;
|
if (!EncryptionUtils.verifyJwt(jwt, lastKey)) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode payloadNode = JSON_MAPPER.readTree(jwt.getPayload().toString());
|
JsonNode payloadNode = JSON_MAPPER.readTree(jwt.getPayload().toString());
|
||||||
@ -121,7 +120,8 @@ public class LoginEncryptionUtils {
|
|||||||
session.setAuthenticationData(new AuthData(
|
session.setAuthenticationData(new AuthData(
|
||||||
extraData.get("displayName").asText(),
|
extraData.get("displayName").asText(),
|
||||||
UUID.fromString(extraData.get("identity").asText()),
|
UUID.fromString(extraData.get("identity").asText()),
|
||||||
extraData.get("XUID").asText()
|
extraData.get("XUID").asText(),
|
||||||
|
certChainData, clientData
|
||||||
));
|
));
|
||||||
|
|
||||||
if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) {
|
if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) {
|
||||||
@ -132,7 +132,9 @@ public class LoginEncryptionUtils {
|
|||||||
JWSObject clientJwt = JWSObject.parse(clientData);
|
JWSObject clientJwt = JWSObject.parse(clientData);
|
||||||
EncryptionUtils.verifyJwt(clientJwt, identityPublicKey);
|
EncryptionUtils.verifyJwt(clientJwt, identityPublicKey);
|
||||||
|
|
||||||
session.setClientData(JSON_MAPPER.convertValue(JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()), BedrockClientData.class));
|
JsonNode clientDataJson = JSON_MAPPER.readTree(clientJwt.getPayload().toBytes());
|
||||||
|
BedrockClientData data = JSON_MAPPER.convertValue(clientDataJson, BedrockClientData.class);
|
||||||
|
session.setClientData(data);
|
||||||
|
|
||||||
if (EncryptionUtils.canUseEncryption()) {
|
if (EncryptionUtils.canUseEncryption()) {
|
||||||
try {
|
try {
|
||||||
@ -176,132 +178,118 @@ public class LoginEncryptionUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int AUTH_MSA_DETAILS_FORM_ID = 1334;
|
public static void buildAndShowLoginWindow(GeyserSession session) {
|
||||||
private static final int AUTH_MSA_CODE_FORM_ID = 1335;
|
|
||||||
private static final int AUTH_FORM_ID = 1336;
|
|
||||||
private static final int AUTH_DETAILS_FORM_ID = 1337;
|
|
||||||
|
|
||||||
public static void showLoginWindow(GeyserSession session) {
|
|
||||||
// Set DoDaylightCycle to false so the time doesn't accelerate while we're here
|
// Set DoDaylightCycle to false so the time doesn't accelerate while we're here
|
||||||
session.setDaylightCycle(false);
|
session.setDaylightCycle(false);
|
||||||
|
|
||||||
String userLanguage = session.getLocale();
|
GeyserConfiguration config = session.getConnector().getConfig();
|
||||||
SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.title", userLanguage), LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.desc", userLanguage));
|
boolean isPasswordAuthEnabled = config.getRemote().isPasswordAuthentication();
|
||||||
if (session.getConnector().getConfig().getRemote().isPasswordAuthentication()) {
|
|
||||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.mojang", userLanguage)));
|
|
||||||
}
|
|
||||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.microsoft", userLanguage)));
|
|
||||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage)));
|
|
||||||
|
|
||||||
session.sendForm(window, AUTH_FORM_ID);
|
session.sendForm(
|
||||||
|
SimpleForm.builder()
|
||||||
|
.translator(LanguageUtils::getPlayerLocaleString, session.getLocale())
|
||||||
|
.title("geyser.auth.login.form.notice.title")
|
||||||
|
.content("geyser.auth.login.form.notice.desc")
|
||||||
|
.optionalButton("geyser.auth.login.form.notice.btn_login.mojang", isPasswordAuthEnabled)
|
||||||
|
.button("geyser.auth.login.form.notice.btn_login.microsoft")
|
||||||
|
.button("geyser.auth.login.form.notice.btn_disconnect")
|
||||||
|
.responseHandler((form, responseData) -> {
|
||||||
|
SimpleFormResponse response = form.parseResponse(responseData);
|
||||||
|
if (!response.isCorrect()) {
|
||||||
|
buildAndShowLoginWindow(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPasswordAuthEnabled && response.getClickedButtonId() == 0) {
|
||||||
|
session.setMicrosoftAccount(false);
|
||||||
|
buildAndShowLoginDetailsWindow(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPasswordAuthEnabled && response.getClickedButtonId() == 1) {
|
||||||
|
session.setMicrosoftAccount(true);
|
||||||
|
buildAndShowMicrosoftAuthenticationWindow(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getClickedButtonId() == 0) {
|
||||||
|
// Just show the OAuth code
|
||||||
|
session.authenticateWithMicrosoftCode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showLoginDetailsWindow(GeyserSession session) {
|
public static void buildAndShowLoginDetailsWindow(GeyserSession session) {
|
||||||
String userLanguage = session.getLocale();
|
session.sendForm(
|
||||||
CustomFormWindow window = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.title", userLanguage))
|
CustomForm.builder()
|
||||||
.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.desc", userLanguage)))
|
.translator(LanguageUtils::getPlayerLocaleString, session.getLocale())
|
||||||
.addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.email", userLanguage), "account@geysermc.org", ""))
|
.title("geyser.auth.login.form.details.title")
|
||||||
.addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.pass", userLanguage), "123456", ""))
|
.label("geyser.auth.login.form.details.desc")
|
||||||
.build();
|
.input("geyser.auth.login.form.details.email", "account@geysermc.org", "")
|
||||||
|
.input("geyser.auth.login.form.details.pass", "123456", "")
|
||||||
|
.responseHandler((form, responseData) -> {
|
||||||
|
CustomFormResponse response = form.parseResponse(responseData);
|
||||||
|
if (!response.isCorrect()) {
|
||||||
|
buildAndShowLoginDetailsWindow(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
session.sendForm(window, AUTH_DETAILS_FORM_ID);
|
session.authenticate(response.next(), response.next());
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompts the user between either OAuth code login or manual password authentication
|
* Prompts the user between either OAuth code login or manual password authentication
|
||||||
*/
|
*/
|
||||||
public static void showMicrosoftAuthenticationWindow(GeyserSession session) {
|
public static void buildAndShowMicrosoftAuthenticationWindow(GeyserSession session) {
|
||||||
String userLanguage = session.getLocale();
|
session.sendForm(
|
||||||
SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.microsoft", userLanguage), "");
|
SimpleForm.builder()
|
||||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.method.browser", userLanguage)));
|
.translator(LanguageUtils::getPlayerLocaleString, session.getLocale())
|
||||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.method.password", userLanguage))); // This form won't show if password authentication is disabled
|
.title("geyser.auth.login.form.notice.btn_login.microsoft")
|
||||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage)));
|
.button("geyser.auth.login.method.browser")
|
||||||
session.sendForm(window, AUTH_MSA_DETAILS_FORM_ID);
|
.button("geyser.auth.login.method.password")
|
||||||
|
.button("geyser.auth.login.form.notice.btn_disconnect")
|
||||||
|
.responseHandler((form, responseData) -> {
|
||||||
|
SimpleFormResponse response = form.parseResponse(responseData);
|
||||||
|
if (!response.isCorrect()) {
|
||||||
|
buildAndShowLoginWindow(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getClickedButtonId() == 0) {
|
||||||
|
session.authenticateWithMicrosoftCode();
|
||||||
|
} else if (response.getClickedButtonId() == 1) {
|
||||||
|
buildAndShowLoginDetailsWindow(session);
|
||||||
|
} else {
|
||||||
|
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the code that a user must input into their browser
|
* Shows the code that a user must input into their browser
|
||||||
*/
|
*/
|
||||||
public static void showMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse response) {
|
public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse msCode) {
|
||||||
ModalFormWindow msaCodeWindow = new ModalFormWindow("%xbox.signin", "%xbox.signin.website\n%xbox.signin.url\n%xbox.signin.enterCode\n" +
|
session.sendForm(
|
||||||
response.user_code, "%gui.done", "%menu.disconnect");
|
ModalForm.builder()
|
||||||
session.sendForm(msaCodeWindow, LoginEncryptionUtils.AUTH_MSA_CODE_FORM_ID);
|
.title("%xbox.signin")
|
||||||
}
|
.content("%xbox.signin.website\n%xbox.signin.url\n%xbox.signin.enterCode\n" + msCode.user_code)
|
||||||
|
.button1("%gui.done")
|
||||||
public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) {
|
.button2("%menu.disconnect")
|
||||||
WindowCache windowCache = session.getWindowCache();
|
.responseHandler((form, responseData) -> {
|
||||||
if (!windowCache.getWindows().containsKey(formId))
|
ModalFormResponse response = form.parseResponse(responseData);
|
||||||
return false;
|
if (!response.isCorrect()) {
|
||||||
|
buildAndShowMicrosoftAuthenticationWindow(session);
|
||||||
if (formId == AUTH_MSA_DETAILS_FORM_ID || formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID || formId == AUTH_MSA_CODE_FORM_ID) {
|
return;
|
||||||
FormWindow window = windowCache.getWindows().remove(formId);
|
|
||||||
window.setResponse(formData.trim());
|
|
||||||
|
|
||||||
if (!session.isLoggedIn()) {
|
|
||||||
if (formId == AUTH_DETAILS_FORM_ID && window instanceof CustomFormWindow) {
|
|
||||||
CustomFormWindow customFormWindow = (CustomFormWindow) window;
|
|
||||||
|
|
||||||
CustomFormResponse response = (CustomFormResponse) customFormWindow.getResponse();
|
|
||||||
if (response != null) {
|
|
||||||
String email = response.getInputResponses().get(1);
|
|
||||||
String password = response.getInputResponses().get(2);
|
|
||||||
|
|
||||||
session.authenticate(email, password);
|
|
||||||
|
|
||||||
// Clear windows so authentication data isn't accidentally cached
|
|
||||||
windowCache.getWindows().clear();
|
|
||||||
} else {
|
|
||||||
showLoginDetailsWindow(session);
|
|
||||||
}
|
|
||||||
} else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) {
|
|
||||||
boolean isPasswordAuthentication = session.getConnector().getConfig().getRemote().isPasswordAuthentication();
|
|
||||||
int microsoftButton = isPasswordAuthentication ? 1 : 0;
|
|
||||||
int disconnectButton = isPasswordAuthentication ? 2 : 1;
|
|
||||||
SimpleFormResponse response = (SimpleFormResponse) window.getResponse();
|
|
||||||
if (response != null) {
|
|
||||||
if (isPasswordAuthentication && response.getClickedButtonId() == 0) {
|
|
||||||
session.setMicrosoftAccount(false);
|
|
||||||
showLoginDetailsWindow(session);
|
|
||||||
} else if (response.getClickedButtonId() == microsoftButton) {
|
|
||||||
session.setMicrosoftAccount(true);
|
|
||||||
if (isPasswordAuthentication) {
|
|
||||||
showMicrosoftAuthenticationWindow(session);
|
|
||||||
} else {
|
|
||||||
// Just show the OAuth code
|
|
||||||
session.authenticateWithMicrosoftCode();
|
|
||||||
}
|
}
|
||||||
} else if (response.getClickedButtonId() == disconnectButton) {
|
|
||||||
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showLoginWindow(session);
|
|
||||||
}
|
|
||||||
} else if (formId == AUTH_MSA_DETAILS_FORM_ID && window instanceof SimpleFormWindow) {
|
|
||||||
SimpleFormResponse response = (SimpleFormResponse) window.getResponse();
|
|
||||||
if (response != null) {
|
|
||||||
if (response.getClickedButtonId() == 0) {
|
|
||||||
session.authenticateWithMicrosoftCode();
|
|
||||||
} else if (response.getClickedButtonId() == 1) {
|
|
||||||
showLoginDetailsWindow(session);
|
|
||||||
} else if (response.getClickedButtonId() == 2) {
|
|
||||||
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showLoginWindow(session);
|
|
||||||
}
|
|
||||||
} else if (formId == AUTH_MSA_CODE_FORM_ID && window instanceof ModalFormWindow) {
|
|
||||||
ModalFormResponse response = (ModalFormResponse) window.getResponse();
|
|
||||||
if (response != null) {
|
|
||||||
if (response.getClickedButtonId() == 1) {
|
|
||||||
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showMicrosoftAuthenticationWindow(session);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (response.getClickedButtonId() == 1) {
|
||||||
|
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
175
connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java
Normale Datei
175
connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java
Normale Datei
@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.utils;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
import org.geysermc.connector.GeyserConnector;
|
||||||
|
import org.geysermc.connector.GeyserLogger;
|
||||||
|
import org.geysermc.connector.common.ChatColor;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.floodgate.news.NewsItem;
|
||||||
|
import org.geysermc.floodgate.news.NewsItemAction;
|
||||||
|
import org.geysermc.floodgate.news.data.BuildSpecificData;
|
||||||
|
import org.geysermc.floodgate.news.data.CheckAfterData;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class NewsHandler {
|
||||||
|
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
|
||||||
|
private final GeyserLogger logger = GeyserConnector.getInstance().getLogger();
|
||||||
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
|
private final Map<Integer, NewsItem> activeNewsItems = new HashMap<>();
|
||||||
|
private final String branch;
|
||||||
|
private final int build;
|
||||||
|
|
||||||
|
private boolean geyserStarted;
|
||||||
|
|
||||||
|
public NewsHandler(String branch, int build) {
|
||||||
|
this.branch = branch;
|
||||||
|
this.build = build;
|
||||||
|
|
||||||
|
executorService.scheduleWithFixedDelay(this::checkNews, 0, 30, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void schedule(long delayMs) {
|
||||||
|
executorService.schedule(this::checkNews, delayMs, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkNews() {
|
||||||
|
try {
|
||||||
|
String body = WebUtils.getBody(Constants.NEWS_OVERVIEW_URL);
|
||||||
|
JsonArray array = gson.fromJson(body, JsonArray.class);
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (JsonElement newsItemElement : array) {
|
||||||
|
NewsItem newsItem = NewsItem.readItem(newsItemElement.getAsJsonObject());
|
||||||
|
if (newsItem != null) {
|
||||||
|
addNews(newsItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger.isDebug()) {
|
||||||
|
logger.error("Error while reading news item", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (JsonSyntaxException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGeyserStarted() {
|
||||||
|
geyserStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleNews(GeyserSession session, NewsItemAction action) {
|
||||||
|
for (NewsItem news : getActiveNews(action)) {
|
||||||
|
handleNewsItem(session, news, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleNewsItem(GeyserSession session, NewsItem news, NewsItemAction action) {
|
||||||
|
switch (action) {
|
||||||
|
case ON_SERVER_STARTED:
|
||||||
|
if (!geyserStarted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case BROADCAST_TO_CONSOLE:
|
||||||
|
logger.info(news.getMessage());
|
||||||
|
break;
|
||||||
|
case ON_OPERATOR_JOIN:
|
||||||
|
//todo doesn't work, it's called before we know the op level.
|
||||||
|
// if (session != null && session.getOpPermissionLevel() >= 2) {
|
||||||
|
// session.sendMessage(ChatColor.GREEN + news.getMessage());
|
||||||
|
// }
|
||||||
|
break;
|
||||||
|
case BROADCAST_TO_OPERATORS:
|
||||||
|
for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) {
|
||||||
|
if (player.getOpPermissionLevel() >= 2) {
|
||||||
|
session.sendMessage(ChatColor.GREEN + news.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<NewsItem> getActiveNews() {
|
||||||
|
return activeNewsItems.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<NewsItem> getActiveNews(NewsItemAction action) {
|
||||||
|
List<NewsItem> news = new ArrayList<>();
|
||||||
|
for (NewsItem item : getActiveNews()) {
|
||||||
|
if (item.getActions().contains(action)) {
|
||||||
|
news.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return news;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNews(NewsItem item) {
|
||||||
|
if (activeNewsItems.containsKey(item.getId())) {
|
||||||
|
if (!item.isActive()) {
|
||||||
|
activeNewsItems.remove(item.getId());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Constants.NEWS_PROJECT_LIST.contains(item.getProject())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (item.getType()) {
|
||||||
|
case BUILD_SPECIFIC:
|
||||||
|
if (!item.getDataAs(BuildSpecificData.class).isAffected(branch, build)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CHECK_AFTER:
|
||||||
|
long checkAfter = item.getDataAs(CheckAfterData.class).getCheckAfter();
|
||||||
|
long delayMs = System.currentTimeMillis() - checkAfter;
|
||||||
|
schedule(delayMs > 0 ? delayMs : 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
activeNewsItems.put(item.getId(), item);
|
||||||
|
activateNews(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void activateNews(NewsItem item) {
|
||||||
|
for (NewsItemAction action : item.getActions()) {
|
||||||
|
handleNewsItem(null, item, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
}
|
@ -25,35 +25,63 @@
|
|||||||
|
|
||||||
package org.geysermc.connector.utils;
|
package org.geysermc.connector.utils;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
public class PluginMessageUtils {
|
public class PluginMessageUtils {
|
||||||
|
private static final String SKIN_CHANNEL = "floodgate:skin";
|
||||||
private static final byte[] BRAND_DATA;
|
private static final byte[] GEYSER_BRAND_DATA;
|
||||||
|
private static final byte[] FLOODGATE_REGISTER_DATA;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
byte[] data = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8);
|
byte[] data = GeyserConnector.NAME.getBytes(Charsets.UTF_8);
|
||||||
byte[] varInt = writeVarInt(data.length);
|
GEYSER_BRAND_DATA =
|
||||||
BRAND_DATA = new byte[varInt.length + data.length];
|
ByteBuffer.allocate(data.length + getVarIntLength(data.length))
|
||||||
System.arraycopy(varInt, 0, BRAND_DATA, 0, varInt.length);
|
.put(writeVarInt(data.length))
|
||||||
System.arraycopy(data, 0, BRAND_DATA, varInt.length, data.length);
|
.put(data)
|
||||||
|
.array();
|
||||||
|
|
||||||
|
FLOODGATE_REGISTER_DATA = (SKIN_CHANNEL + "\0floodgate:form").getBytes(Charsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the prebuilt brand as a byte array
|
* Get the prebuilt brand as a byte array
|
||||||
|
*
|
||||||
* @return the brand information of the Geyser client
|
* @return the brand information of the Geyser client
|
||||||
*/
|
*/
|
||||||
public static byte[] getGeyserBrandData() {
|
public static byte[] getGeyserBrandData() {
|
||||||
return BRAND_DATA;
|
return GEYSER_BRAND_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the prebuilt register data as a byte array
|
||||||
|
*
|
||||||
|
* @return the register data of the Floodgate channels
|
||||||
|
*/
|
||||||
|
public static byte[] getFloodgateRegisterData() {
|
||||||
|
return FLOODGATE_REGISTER_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the skin channel used in Floodgate
|
||||||
|
*/
|
||||||
|
public static String getSkinChannel() {
|
||||||
|
return SKIN_CHANNEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMessage(GeyserSession session, String channel, byte[] data) {
|
||||||
|
session.sendDownstreamPacket(new ClientPluginMessagePacket(channel, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] writeVarInt(int value) {
|
private static byte[] writeVarInt(int value) {
|
||||||
byte[] data = new byte[getVarIntLength(value)];
|
byte[] data = new byte[getVarIntLength(value)];
|
||||||
int index = 0;
|
int index = 0;
|
||||||
do {
|
do {
|
||||||
byte temp = (byte)(value & 0b01111111);
|
byte temp = (byte) (value & 0b01111111);
|
||||||
value >>>= 7;
|
value >>>= 7;
|
||||||
if (value != 0) {
|
if (value != 0) {
|
||||||
temp |= 0b10000000;
|
temp |= 0b10000000;
|
||||||
|
@ -27,79 +27,69 @@ package org.geysermc.connector.utils;
|
|||||||
|
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||||
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
|
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
|
||||||
import org.geysermc.common.window.CustomFormBuilder;
|
|
||||||
import org.geysermc.common.window.CustomFormWindow;
|
|
||||||
import org.geysermc.common.window.button.FormImage;
|
|
||||||
import org.geysermc.common.window.component.DropdownComponent;
|
|
||||||
import org.geysermc.common.window.component.InputComponent;
|
|
||||||
import org.geysermc.common.window.component.LabelComponent;
|
|
||||||
import org.geysermc.common.window.component.ToggleComponent;
|
|
||||||
import org.geysermc.common.window.response.CustomFormResponse;
|
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.connector.network.translators.world.WorldManager;
|
||||||
|
import org.geysermc.cumulus.CustomForm;
|
||||||
|
import org.geysermc.cumulus.component.DropdownComponent;
|
||||||
|
import org.geysermc.cumulus.response.CustomFormResponse;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class SettingsUtils {
|
public class SettingsUtils {
|
||||||
|
|
||||||
// Used in UpstreamPacketHandler.java
|
|
||||||
public static final int SETTINGS_FORM_ID = 1338;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a settings form for the given session and store it for later
|
* Build a settings form for the given session and store it for later
|
||||||
*
|
*
|
||||||
* @param session The session to build the form for
|
* @param session The session to build the form for
|
||||||
*/
|
*/
|
||||||
public static void buildForm(GeyserSession session) {
|
public static CustomForm buildForm(GeyserSession session) {
|
||||||
// Cache the language for cleaner access
|
// Cache the language for cleaner access
|
||||||
String language = session.getLocale();
|
String language = session.getLocale();
|
||||||
|
|
||||||
CustomFormBuilder builder = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.settings.title.main", language));
|
CustomForm.Builder builder = CustomForm.builder()
|
||||||
builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png"));
|
.translator(LanguageUtils::getPlayerLocaleString, language)
|
||||||
|
.title("geyser.settings.title.main")
|
||||||
|
.iconPath("textures/ui/settings_glyph_color_2x.png");
|
||||||
|
|
||||||
// Only show the client title if any of the client settings are available
|
// Only show the client title if any of the client settings are available
|
||||||
if (session.getPreferencesCache().isAllowShowCoordinates() || CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) {
|
if (session.getPreferencesCache().isAllowShowCoordinates() || CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) {
|
||||||
builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.client", language)));
|
builder.label("geyser.settings.title.client");
|
||||||
|
|
||||||
// Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config.
|
// Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config.
|
||||||
if (session.getPreferencesCache().isAllowShowCoordinates()) {
|
if (session.getPreferencesCache().isAllowShowCoordinates()) {
|
||||||
builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language), session.getPreferencesCache().isPrefersShowCoordinates()));
|
builder.toggle("geyser.settings.option.coordinates", session.getPreferencesCache().isPrefersShowCoordinates());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) {
|
if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) {
|
||||||
DropdownComponent cooldownDropdown = new DropdownComponent();
|
DropdownComponent.Builder cooldownDropdown = DropdownComponent.builder("options.attackIndicator");
|
||||||
cooldownDropdown.setText(LocaleUtils.getLocaleString("options.attackIndicator", language));
|
cooldownDropdown.option("options.attack.crosshair", session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.TITLE);
|
||||||
cooldownDropdown.setOptions(new ArrayList<>());
|
cooldownDropdown.option("options.attack.hotbar", session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.ACTIONBAR);
|
||||||
cooldownDropdown.addOption(LocaleUtils.getLocaleString("options.attack.crosshair", language), session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.TITLE);
|
cooldownDropdown.option("options.off", session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.DISABLED);
|
||||||
cooldownDropdown.addOption(LocaleUtils.getLocaleString("options.attack.hotbar", language), session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.ACTIONBAR);
|
builder.dropdown(cooldownDropdown);
|
||||||
cooldownDropdown.addOption(LocaleUtils.getLocaleString("options.off", language), session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.DISABLED);
|
|
||||||
builder.addComponent(cooldownDropdown);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
|
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
|
||||||
builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.server", language)));
|
builder.label("geyser.settings.title.server");
|
||||||
|
|
||||||
DropdownComponent gamemodeDropdown = new DropdownComponent();
|
DropdownComponent.Builder gamemodeDropdown = DropdownComponent.builder("%createWorldScreen.gameMode.personal");
|
||||||
gamemodeDropdown.setText("%createWorldScreen.gameMode.personal");
|
|
||||||
gamemodeDropdown.setOptions(new ArrayList<>());
|
|
||||||
for (GameMode gamemode : GameMode.values()) {
|
for (GameMode gamemode : GameMode.values()) {
|
||||||
gamemodeDropdown.addOption(LocaleUtils.getLocaleString("selectWorld.gameMode." + gamemode.name().toLowerCase(), language), session.getGameMode() == gamemode);
|
gamemodeDropdown.option("selectWorld.gameMode." + gamemode.name().toLowerCase(), session.getGameMode() == gamemode);
|
||||||
}
|
}
|
||||||
builder.addComponent(gamemodeDropdown);
|
builder.dropdown(gamemodeDropdown);
|
||||||
|
|
||||||
DropdownComponent difficultyDropdown = new DropdownComponent();
|
DropdownComponent.Builder difficultyDropdown = DropdownComponent.builder("%options.difficulty");
|
||||||
difficultyDropdown.setText("%options.difficulty");
|
|
||||||
difficultyDropdown.setOptions(new ArrayList<>());
|
|
||||||
for (Difficulty difficulty : Difficulty.values()) {
|
for (Difficulty difficulty : Difficulty.values()) {
|
||||||
difficultyDropdown.addOption("%options.difficulty." + difficulty.name().toLowerCase(), session.getWorldCache().getDifficulty() == difficulty);
|
difficultyDropdown.option("%options.difficulty." + difficulty.name().toLowerCase(), session.getWorldCache().getDifficulty() == difficulty);
|
||||||
}
|
}
|
||||||
builder.addComponent(difficultyDropdown);
|
builder.dropdown(difficultyDropdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) {
|
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) {
|
||||||
builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.game_rules", language)));
|
builder.label("geyser.settings.title.game_rules")
|
||||||
|
.translator(LocaleUtils::getLocaleString); // we need translate gamerules next
|
||||||
|
|
||||||
|
WorldManager worldManager = GeyserConnector.getInstance().getWorldManager();
|
||||||
for (GameRule gamerule : GameRule.values()) {
|
for (GameRule gamerule : GameRule.values()) {
|
||||||
if (gamerule.equals(GameRule.UNKNOWN)) {
|
if (gamerule.equals(GameRule.UNKNOWN)) {
|
||||||
continue;
|
continue;
|
||||||
@ -107,89 +97,69 @@ public class SettingsUtils {
|
|||||||
|
|
||||||
// Add the relevant form item based on the gamerule type
|
// Add the relevant form item based on the gamerule type
|
||||||
if (Boolean.class.equals(gamerule.getType())) {
|
if (Boolean.class.equals(gamerule.getType())) {
|
||||||
builder.addComponent(new ToggleComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), GeyserConnector.getInstance().getWorldManager().getGameRuleBool(session, gamerule)));
|
builder.toggle("gamerule." + gamerule.getJavaID(), worldManager.getGameRuleBool(session, gamerule));
|
||||||
} else if (Integer.class.equals(gamerule.getType())) {
|
} else if (Integer.class.equals(gamerule.getType())) {
|
||||||
builder.addComponent(new InputComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), "", String.valueOf(GeyserConnector.getInstance().getWorldManager().getGameRuleInt(session, gamerule))));
|
builder.input("gamerule." + gamerule.getJavaID(), "", String.valueOf(worldManager.getGameRuleInt(session, gamerule)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
session.setSettingsForm(builder.build());
|
builder.responseHandler((form, responseData) -> {
|
||||||
}
|
CustomFormResponse response = form.parseResponse(responseData);
|
||||||
|
if (response.isClosed() || response.isInvalid()) {
|
||||||
/**
|
return;
|
||||||
* Handle the settings form response
|
|
||||||
*
|
|
||||||
* @param session The session that sent the response
|
|
||||||
* @param response The response string to parse
|
|
||||||
* @return True if the form was parsed correctly, false if not
|
|
||||||
*/
|
|
||||||
public static boolean handleSettingsForm(GeyserSession session, String response) {
|
|
||||||
CustomFormWindow settingsForm = session.getSettingsForm();
|
|
||||||
settingsForm.setResponse(response);
|
|
||||||
|
|
||||||
CustomFormResponse settingsResponse = (CustomFormResponse) settingsForm.getResponse();
|
|
||||||
if (settingsResponse == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
if (session.getPreferencesCache().isAllowShowCoordinates() || CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) {
|
|
||||||
offset++; // Client settings title
|
|
||||||
|
|
||||||
// Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config.
|
|
||||||
if (session.getPreferencesCache().isAllowShowCoordinates()) {
|
|
||||||
session.getPreferencesCache().setPrefersShowCoordinates(settingsResponse.getToggleResponses().get(offset));
|
|
||||||
session.getPreferencesCache().updateShowCoordinates();
|
|
||||||
offset++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) {
|
if (session.getPreferencesCache().isAllowShowCoordinates() || CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) {
|
||||||
CooldownUtils.CooldownType cooldownType = CooldownUtils.CooldownType.VALUES[settingsResponse.getDropdownResponses().get(offset).getElementID()];
|
response.skip(); // Client settings title
|
||||||
session.getPreferencesCache().setCooldownPreference(cooldownType);
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
|
// Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config.
|
||||||
offset++; // Server settings title
|
if (session.getPreferencesCache().isAllowShowCoordinates()) {
|
||||||
|
session.getPreferencesCache().setPrefersShowCoordinates(response.next());
|
||||||
GameMode gameMode = GameMode.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()];
|
session.getPreferencesCache().updateShowCoordinates();
|
||||||
if (gameMode != null && gameMode != session.getGameMode()) {
|
response.skip();
|
||||||
session.getConnector().getWorldManager().setPlayerGameMode(session, gameMode);
|
|
||||||
}
|
|
||||||
offset++;
|
|
||||||
|
|
||||||
Difficulty difficulty = Difficulty.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()];
|
|
||||||
if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) {
|
|
||||||
session.getConnector().getWorldManager().setDifficulty(session, difficulty);
|
|
||||||
}
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) {
|
|
||||||
offset++; // Game rule title
|
|
||||||
|
|
||||||
for (GameRule gamerule : GameRule.values()) {
|
|
||||||
if (gamerule.equals(GameRule.UNKNOWN)) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Boolean.class.equals(gamerule.getType())) {
|
if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) {
|
||||||
boolean value = settingsResponse.getToggleResponses().get(offset);
|
CooldownUtils.CooldownType cooldownType = CooldownUtils.CooldownType.VALUES[(int) response.next()];
|
||||||
if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) {
|
session.getPreferencesCache().setCooldownPreference(cooldownType);
|
||||||
session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value);
|
response.skip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
|
||||||
|
GameMode gameMode = GameMode.values()[(int) response.next()];
|
||||||
|
if (gameMode != null && gameMode != session.getGameMode()) {
|
||||||
|
session.getConnector().getWorldManager().setPlayerGameMode(session, gameMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Difficulty difficulty = Difficulty.values()[(int) response.next()];
|
||||||
|
if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) {
|
||||||
|
session.getConnector().getWorldManager().setDifficulty(session, difficulty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) {
|
||||||
|
for (GameRule gamerule : GameRule.values()) {
|
||||||
|
if (gamerule.equals(GameRule.UNKNOWN)) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
} else if (Integer.class.equals(gamerule.getType())) {
|
|
||||||
int value = Integer.parseInt(settingsResponse.getInputResponses().get(offset));
|
if (Boolean.class.equals(gamerule.getType())) {
|
||||||
if (value != session.getConnector().getWorldManager().getGameRuleInt(session, gamerule)) {
|
boolean value = response.next();
|
||||||
session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value);
|
if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) {
|
||||||
|
session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value);
|
||||||
|
}
|
||||||
|
} else if (Integer.class.equals(gamerule.getType())) {
|
||||||
|
int value = Integer.parseInt(response.next());
|
||||||
|
if (value != session.getConnector().getWorldManager().getGameRuleInt(session, gamerule)) {
|
||||||
|
session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
offset++;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
return true;
|
return builder.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,197 +28,171 @@ package org.geysermc.connector.utils;
|
|||||||
import com.github.steveice10.mc.protocol.data.MagicValues;
|
import com.github.steveice10.mc.protocol.data.MagicValues;
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
|
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
|
||||||
import com.github.steveice10.mc.protocol.data.game.statistic.*;
|
import com.github.steveice10.mc.protocol.data.game.statistic.*;
|
||||||
import org.geysermc.common.window.SimpleFormWindow;
|
|
||||||
import org.geysermc.common.window.button.FormButton;
|
|
||||||
import org.geysermc.common.window.button.FormImage;
|
|
||||||
import org.geysermc.common.window.response.SimpleFormResponse;
|
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.item.ItemRegistry;
|
import org.geysermc.connector.network.translators.item.ItemRegistry;
|
||||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||||
|
import org.geysermc.cumulus.SimpleForm;
|
||||||
|
import org.geysermc.cumulus.response.SimpleFormResponse;
|
||||||
|
import org.geysermc.cumulus.util.FormImage;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class StatisticsUtils {
|
public class StatisticsUtils {
|
||||||
|
private static final Pattern CONTENT_PATTERN = Pattern.compile("^\\S+:", Pattern.MULTILINE);
|
||||||
// Used in UpstreamPacketHandler.java
|
|
||||||
public static final int STATISTICS_MENU_FORM_ID = 1339;
|
|
||||||
public static final int STATISTICS_LIST_FORM_ID = 1340;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a form for the given session with all statistic categories
|
* Build a form for the given session with all statistic categories
|
||||||
*
|
*
|
||||||
* @param session The session to build the form for
|
* @param session The session to build the form for
|
||||||
*/
|
*/
|
||||||
public static SimpleFormWindow buildMenuForm(GeyserSession session) {
|
public static void buildAndSendStatisticsMenu(GeyserSession session) {
|
||||||
// Cache the language for cleaner access
|
// Cache the language for cleaner access
|
||||||
String language = session.getClientData().getLanguageCode();
|
String language = session.getLocale();
|
||||||
|
|
||||||
SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.stats", language), "");
|
session.sendForm(
|
||||||
|
SimpleForm.builder()
|
||||||
|
.translator(StatisticsUtils::translate, language)
|
||||||
|
.title("gui.stats")
|
||||||
|
.button("stat.generalButton", FormImage.Type.PATH, "textures/ui/World")
|
||||||
|
.button("stat.itemsButton - stat_type.minecraft.mined", FormImage.Type.PATH, "textures/items/iron_pickaxe")
|
||||||
|
.button("stat.itemsButton - stat_type.minecraft.broken", FormImage.Type.PATH, "textures/item/record_11")
|
||||||
|
.button("stat.itemsButton - stat_type.minecraft.crafted", FormImage.Type.PATH, "textures/blocks/crafting_table_side")
|
||||||
|
.button("stat.itemsButton - stat_type.minecraft.used", FormImage.Type.PATH, "textures/ui/Wrenches1")
|
||||||
|
.button("stat.itemsButton - stat_type.minecraft.picked_up", FormImage.Type.PATH, "textures/blocks/chest_front")
|
||||||
|
.button("stat.itemsButton - stat_type.minecraft.dropped", FormImage.Type.PATH, "textures/ui/trash_default")
|
||||||
|
.button("stat.mobsButton - geyser.statistics.killed", FormImage.Type.PATH, "textures/items/diamon_sword")
|
||||||
|
.button("stat.mobsButton - geyser.statistics.killed_by", FormImage.Type.PATH, "textures/ui/wither_heart_flash")
|
||||||
|
.responseHandler((form, responseData) -> {
|
||||||
|
SimpleFormResponse response = form.parseResponse(responseData);
|
||||||
|
if (!response.isCorrect()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.generalButton", language), new FormImage(FormImage.FormImageType.PATH, "textures/ui/World")));
|
SimpleForm.Builder builder =
|
||||||
|
SimpleForm.builder()
|
||||||
|
.translator(StatisticsUtils::translate, language);
|
||||||
|
|
||||||
window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language), new FormImage(FormImage.FormImageType.PATH, "textures/items/iron_pickaxe")));
|
StringBuilder content = new StringBuilder();
|
||||||
window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language), new FormImage(FormImage.FormImageType.PATH, "textures/items/record_11")));
|
|
||||||
window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language), new FormImage(FormImage.FormImageType.PATH, "textures/blocks/crafting_table_side")));
|
|
||||||
window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language), new FormImage(FormImage.FormImageType.PATH, "textures/ui/Wrenches1")));
|
|
||||||
window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language), new FormImage(FormImage.FormImageType.PATH, "textures/blocks/chest_front")));
|
|
||||||
window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language), new FormImage(FormImage.FormImageType.PATH, "textures/ui/trash_default")));
|
|
||||||
|
|
||||||
window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language), new FormImage(FormImage.FormImageType.PATH, "textures/items/diamond_sword")));
|
switch (response.getClickedButtonId()) {
|
||||||
window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language), new FormImage(FormImage.FormImageType.PATH, "textures/ui/wither_heart_flash")));
|
case 0:
|
||||||
|
builder.title("stat.generalButton");
|
||||||
|
|
||||||
return window;
|
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
||||||
}
|
if (entry.getKey() instanceof GenericStatistic) {
|
||||||
|
String statName = ((GenericStatistic) entry.getKey()).name().toLowerCase();
|
||||||
|
content.append("stat.minecraft.").append(statName).append(": ").append(entry.getValue()).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
builder.title("stat.itemsButton - stat_type.minecraft.mined");
|
||||||
|
|
||||||
/**
|
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
||||||
* Handle the menu form response
|
if (entry.getKey() instanceof BreakBlockStatistic) {
|
||||||
*
|
String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId());
|
||||||
* @param session The session that sent the response
|
block = block.replace("minecraft:", "block.minecraft.");
|
||||||
* @param response The response string to parse
|
content.append(block).append(": ").append(entry.getValue()).append("\n");
|
||||||
* @return True if the form was parsed correctly, false if not
|
}
|
||||||
*/
|
}
|
||||||
public static boolean handleMenuForm(GeyserSession session, String response) {
|
break;
|
||||||
SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_MENU_FORM_ID);
|
case 2:
|
||||||
menuForm.setResponse(response);
|
builder.title("stat.itemsButton - stat_type.minecraft.broken");
|
||||||
SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse();
|
|
||||||
|
|
||||||
// Cache the language for cleaner access
|
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
||||||
String language = session.getClientData().getLanguageCode();
|
if (entry.getKey() instanceof BreakItemStatistic) {
|
||||||
|
String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
|
||||||
|
content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
builder.title("stat.itemsButton - stat_type.minecraft.crafted");
|
||||||
|
|
||||||
if (formResponse != null && formResponse.getClickedButton() != null) {
|
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
||||||
String title;
|
if (entry.getKey() instanceof CraftItemStatistic) {
|
||||||
StringBuilder content = new StringBuilder();
|
String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
|
||||||
|
content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
builder.title("stat.itemsButton - stat_type.minecraft.used");
|
||||||
|
|
||||||
switch (formResponse.getClickedButtonId()) {
|
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
||||||
case 0:
|
if (entry.getKey() instanceof UseItemStatistic) {
|
||||||
title = LocaleUtils.getLocaleString("stat.generalButton", language);
|
String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
|
||||||
|
content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
builder.title("stat.itemsButton - stat_type.minecraft.picked_up");
|
||||||
|
|
||||||
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
||||||
if (entry.getKey() instanceof GenericStatistic) {
|
if (entry.getKey() instanceof PickupItemStatistic) {
|
||||||
content.append(LocaleUtils.getLocaleString("stat.minecraft." + ((GenericStatistic) entry.getKey()).name().toLowerCase(), language) + ": " + entry.getValue() + "\n");
|
String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
|
||||||
}
|
content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n");
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case 1:
|
break;
|
||||||
title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language);
|
case 6:
|
||||||
|
builder.title("stat.itemsButton - stat_type.minecraft.dropped");
|
||||||
|
|
||||||
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
||||||
if (entry.getKey() instanceof BreakBlockStatistic) {
|
if (entry.getKey() instanceof DropItemStatistic) {
|
||||||
String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId());
|
String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
|
||||||
block = block.replace("minecraft:", "block.minecraft.");
|
content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n");
|
||||||
block = LocaleUtils.getLocaleString(block, language);
|
}
|
||||||
content.append(block + ": " + entry.getValue() + "\n");
|
}
|
||||||
}
|
break;
|
||||||
}
|
case 7:
|
||||||
break;
|
builder.title("stat.mobsButton - geyser.statistics.killed");
|
||||||
case 2:
|
|
||||||
title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language);
|
|
||||||
|
|
||||||
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
||||||
if (entry.getKey() instanceof BreakItemStatistic) {
|
if (entry.getKey() instanceof KillEntityStatistic) {
|
||||||
String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
|
String entityName = MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase();
|
||||||
content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n");
|
content.append("entity.minecraft.").append(entityName).append(": ").append(entry.getValue()).append("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 8:
|
||||||
title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language);
|
builder.title("stat.mobsButton - geyser.statistics.killed_by");
|
||||||
|
|
||||||
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
for (Map.Entry<Statistic, Integer> entry : session
|
||||||
if (entry.getKey() instanceof CraftItemStatistic) {
|
.getStatistics().entrySet()) {
|
||||||
String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
|
if (entry.getKey() instanceof KilledByEntityStatistic) {
|
||||||
content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n");
|
String entityName = MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase();
|
||||||
}
|
content.append("entity.minecraft.").append(entityName).append(": ").append(entry.getValue()).append("\n");
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case 4:
|
break;
|
||||||
title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language);
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
if (content.length() == 0) {
|
||||||
if (entry.getKey() instanceof UseItemStatistic) {
|
content = new StringBuilder("geyser.statistics.none");
|
||||||
String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
|
}
|
||||||
content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language);
|
|
||||||
|
|
||||||
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
session.sendForm(
|
||||||
if (entry.getKey() instanceof PickupItemStatistic) {
|
builder.content(content.toString())
|
||||||
String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
|
.button("gui.back", FormImage.Type.PATH, "textures/gui/newgui/undo")
|
||||||
content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n");
|
.responseHandler((form1, subFormResponseData) -> {
|
||||||
}
|
SimpleFormResponse response1 = form.parseResponse(subFormResponseData);
|
||||||
}
|
if (response1.isCorrect()) {
|
||||||
break;
|
buildAndSendStatisticsMenu(session);
|
||||||
case 6:
|
}
|
||||||
title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language);
|
}));
|
||||||
|
}));
|
||||||
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
|
||||||
if (entry.getKey() instanceof DropItemStatistic) {
|
|
||||||
String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
|
|
||||||
content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language);
|
|
||||||
|
|
||||||
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
|
||||||
if (entry.getKey() instanceof KillEntityStatistic) {
|
|
||||||
String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language);
|
|
||||||
content.append(mob + ": " + entry.getValue() + "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language);
|
|
||||||
|
|
||||||
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
|
|
||||||
if (entry.getKey() instanceof KilledByEntityStatistic) {
|
|
||||||
String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language);
|
|
||||||
content.append(mob + ": " + entry.getValue() + "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content.length() == 0) {
|
|
||||||
content = new StringBuilder(LanguageUtils.getPlayerLocaleString("geyser.statistics.none", language));
|
|
||||||
}
|
|
||||||
|
|
||||||
SimpleFormWindow window = new SimpleFormWindow(title, content.toString());
|
|
||||||
window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("gui.back", language), new FormImage(FormImage.FormImageType.PATH, "textures/gui/newgui/undo")));
|
|
||||||
session.sendForm(window, STATISTICS_LIST_FORM_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the list form response
|
|
||||||
*
|
|
||||||
* @param session The session that sent the response
|
|
||||||
* @param response The response string to parse
|
|
||||||
* @return True if the form was parsed correctly, false if not
|
|
||||||
*/
|
|
||||||
public static boolean handleListForm(GeyserSession session, String response) {
|
|
||||||
SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_LIST_FORM_ID);
|
|
||||||
listForm.setResponse(response);
|
|
||||||
|
|
||||||
if (!listForm.isClosed()) {
|
|
||||||
session.sendForm(buildMenuForm(session), STATISTICS_MENU_FORM_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the item translation key from the Java locale.
|
* Finds the item translation key from the Java locale.
|
||||||
*
|
*
|
||||||
* @param item the namespaced item to search for.
|
* @param item the namespaced item to search for.
|
||||||
* @param language the language to search in
|
* @param language the language to search in
|
||||||
* @return the full name of the item
|
* @return the full name of the item
|
||||||
*/
|
*/
|
||||||
@ -231,4 +205,31 @@ public class StatisticsUtils {
|
|||||||
}
|
}
|
||||||
return translatedItem;
|
return translatedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String translate(String keys, String locale) {
|
||||||
|
Matcher matcher = CONTENT_PATTERN.matcher(keys);
|
||||||
|
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
while (matcher.find()) {
|
||||||
|
String group = matcher.group();
|
||||||
|
matcher.appendReplacement(buffer, translateEntry(group.substring(0, group.length() - 1), locale) + ":");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer.length() != 0) {
|
||||||
|
return matcher.appendTail(buffer).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] keySplitted = keys.split(" - ");
|
||||||
|
for (int i = 0; i < keySplitted.length; i++) {
|
||||||
|
keySplitted[i] = translateEntry(keySplitted[i], locale);
|
||||||
|
}
|
||||||
|
return String.join(" - ", keySplitted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String translateEntry(String key, String locale) {
|
||||||
|
if (key.startsWith("geyser.")) {
|
||||||
|
return LanguageUtils.getPlayerLocaleString(key, locale);
|
||||||
|
}
|
||||||
|
return LocaleUtils.getLocaleString(key, locale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,9 +45,8 @@ public class WebUtils {
|
|||||||
* @return Body contents or error message if the request fails
|
* @return Body contents or error message if the request fails
|
||||||
*/
|
*/
|
||||||
public static String getBody(String reqURL) {
|
public static String getBody(String reqURL) {
|
||||||
URL url = null;
|
|
||||||
try {
|
try {
|
||||||
url = new URL(reqURL);
|
URL url = new URL(reqURL);
|
||||||
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
||||||
con.setRequestMethod("GET");
|
con.setRequestMethod("GET");
|
||||||
con.setRequestProperty("User-Agent", "Geyser-" + GeyserConnector.getInstance().getPlatformType().toString() + "/" + GeyserConnector.VERSION); // Otherwise Java 8 fails on checking updates
|
con.setRequestProperty("User-Agent", "Geyser-" + GeyserConnector.getInstance().getPlatformType().toString() + "/" + GeyserConnector.VERSION); // Otherwise Java 8 fails on checking updates
|
||||||
|
@ -58,9 +58,9 @@ remote:
|
|||||||
forward-hostname: false
|
forward-hostname: false
|
||||||
|
|
||||||
# Floodgate uses encryption to ensure use from authorised sources.
|
# Floodgate uses encryption to ensure use from authorised sources.
|
||||||
# This should point to the public key generated by Floodgate (Bungee or CraftBukkit)
|
# This should point to the public key generated by Floodgate (BungeeCord, Spigot or Velocity)
|
||||||
# You can ignore this when not using Floodgate.
|
# You can ignore this when not using Floodgate.
|
||||||
floodgate-key-file: public-key.pem
|
floodgate-key-file: key.pem
|
||||||
|
|
||||||
# The Xbox/Minecraft Bedrock username is the key for the Java server auth-info.
|
# The Xbox/Minecraft Bedrock username is the key for the Java server auth-info.
|
||||||
# This allows automatic configuration/login to the remote Java server.
|
# This allows automatic configuration/login to the remote Java server.
|
||||||
|
2
pom.xml
2
pom.xml
@ -5,7 +5,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>geyser-parent</artifactId>
|
<artifactId>geyser-parent</artifactId>
|
||||||
<version>1.2.1-SNAPSHOT</version>
|
<version>1.3.0-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>Geyser</name>
|
<name>Geyser</name>
|
||||||
<description>Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers.</description>
|
<description>Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers.</description>
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren