3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-11-03 14:50:19 +01:00

Merge branch 'feature/1.17' of https://github.com/GeyserMC/Geyser into feature/1.17

Dieser Commit ist enthalten in:
Camotoy 2021-06-05 19:24:40 -04:00
Commit 8c0d5413c7
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 7EEFB66FE798081F
84 geänderte Dateien mit 2747 neuen und 1868 gelöschten Zeilen

3
Jenkinsfile vendored
Datei anzeigen

@ -22,7 +22,10 @@ pipeline {
stage ('Deploy') { stage ('Deploy') {
when { when {
anyOf {
branch "master" branch "master"
branch "floodgate-2.0"
}
} }
steps { steps {

Datei anzeigen

@ -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>

Datei anzeigen

@ -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;

Datei anzeigen

@ -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");

Datei anzeigen

@ -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>

Datei anzeigen

@ -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>

Datei anzeigen

@ -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;

Datei anzeigen

@ -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");

Datei anzeigen

@ -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:

Datei anzeigen

@ -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>

Datei anzeigen

@ -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>

Datei anzeigen

@ -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>

Datei anzeigen

@ -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>

Datei anzeigen

@ -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"),

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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);
}

Datei anzeigen

@ -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);
}
}
}

Datei anzeigen

@ -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));
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}

Datei anzeigen

@ -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;
}

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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();
}
}
}
}

Datei anzeigen

@ -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);
}
} }

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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));
}
}

Datei anzeigen

@ -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;
} }

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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();
}
}

Datei anzeigen

@ -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; }
return null;
FormImageType(String name) {
this.name = name;
} }
@Override public ItemData read(JsonObject data) {
public String toString() { return readFunction.apply(data);
return name;
}
} }
} }

Datei anzeigen

@ -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;
} }
} }

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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 {
} }

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
} }
} }

Datei anzeigen

@ -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();
} }
} }

Datei anzeigen

@ -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];
} }

Datei anzeigen

@ -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)
);
}
}

Datei anzeigen

@ -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;
}
} }

Datei anzeigen

@ -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];
}
}

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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();
}
}

Datei anzeigen

@ -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];
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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>

Datei anzeigen

@ -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;

Datei anzeigen

@ -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.

Datei anzeigen

@ -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) {

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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;
} }

Datei anzeigen

@ -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;

Datei anzeigen

@ -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;
} }

Datei anzeigen

@ -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
} }

Datei anzeigen

@ -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(),
} catch (Exception e) { skinUploader.getVerifyCode(),
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); 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."
);
} }
addressSuffix = '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted; } catch (Exception 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' + 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() {

Datei anzeigen

@ -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);
}
} }

Datei anzeigen

@ -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;
} }
} }

Datei anzeigen

@ -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()) { builder.button(LanguageUtils.getPlayerLocaleString("gui.back", language));
window.getButtons().add(new FormButton("§6" + MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n"));
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 { } else {
window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n")); advancementIndex++;
}
}
} }
} }
} }
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language))); if (advancement != null) {
buildAndShowInfoForm(advancement);
} else {
buildAndShowMenuForm();
// Indicate that we have closed the current advancement tab
session.sendDownstreamPacket(new ClientAdvancementTabPacket());
}
});
return window; 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) {

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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.NX)) { if (session.getClientData().getDeviceOs().equals(DeviceOs.PS4)) {
// Ignore the weird DeviceOS, our order is wrong and will be fixed in Floodgate 2.0
pingId = packet.getTimestamp(); pingId = packet.getTimestamp();
} else { } else {
pingId = packet.getTimestamp() / 1000; 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);
} }
} }

Datei anzeigen

@ -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);
} }

Datei anzeigen

@ -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();
} }
} }

Datei anzeigen

@ -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);
} }

Datei anzeigen

@ -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);
}
}
}

Datei anzeigen

@ -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);
} }
} }
} }

Datei anzeigen

@ -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();
}
}

Datei anzeigen

@ -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();
} }

Datei anzeigen

@ -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;
} }
} }

Datei anzeigen

@ -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 &&

Datei anzeigen

@ -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;
@ -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;
} }
public static void showLoginDetailsWindow(GeyserSession session) { if (isPasswordAuthEnabled && response.getClickedButtonId() == 0) {
String userLanguage = session.getLocale(); session.setMicrosoftAccount(false);
CustomFormWindow window = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.title", userLanguage)) buildAndShowLoginDetailsWindow(session);
.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.desc", userLanguage))) return;
.addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.email", userLanguage), "account@geysermc.org", "")) }
.addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.pass", userLanguage), "123456", ""))
.build();
session.sendForm(window, AUTH_DETAILS_FORM_ID); 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 buildAndShowLoginDetailsWindow(GeyserSession session) {
session.sendForm(
CustomForm.builder()
.translator(LanguageUtils::getPlayerLocaleString, session.getLocale())
.title("geyser.auth.login.form.details.title")
.label("geyser.auth.login.form.details.desc")
.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.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")
.button2("%menu.disconnect")
.responseHandler((form, responseData) -> {
ModalFormResponse response = form.parseResponse(responseData);
if (!response.isCorrect()) {
buildAndShowMicrosoftAuthenticationWindow(session);
return;
} }
public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) {
WindowCache windowCache = session.getWindowCache();
if (!windowCache.getWindows().containsKey(formId))
return false;
if (formId == AUTH_MSA_DETAILS_FORM_ID || formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID || formId == AUTH_MSA_CODE_FORM_ID) {
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) { if (response.getClickedButtonId() == 1) {
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
} }
} else { })
showMicrosoftAuthenticationWindow(session); );
} }
}
}
}
return true;
}
} }

Datei anzeigen

@ -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();
}
}

Datei anzeigen

@ -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;

Datei anzeigen

@ -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) { if (session.getPreferencesCache().isAllowShowCoordinates() || CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) {
offset++; // Client settings title response.skip(); // Client settings title
// 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()) {
session.getPreferencesCache().setPrefersShowCoordinates(settingsResponse.getToggleResponses().get(offset)); session.getPreferencesCache().setPrefersShowCoordinates(response.next());
session.getPreferencesCache().updateShowCoordinates(); session.getPreferencesCache().updateShowCoordinates();
offset++; response.skip();
} }
if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) { if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) {
CooldownUtils.CooldownType cooldownType = CooldownUtils.CooldownType.VALUES[settingsResponse.getDropdownResponses().get(offset).getElementID()]; CooldownUtils.CooldownType cooldownType = CooldownUtils.CooldownType.VALUES[(int) response.next()];
session.getPreferencesCache().setCooldownPreference(cooldownType); session.getPreferencesCache().setCooldownPreference(cooldownType);
offset++; response.skip();
} }
} }
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
offset++; // Server settings title GameMode gameMode = GameMode.values()[(int) response.next()];
GameMode gameMode = GameMode.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()];
if (gameMode != null && gameMode != session.getGameMode()) { if (gameMode != null && gameMode != session.getGameMode()) {
session.getConnector().getWorldManager().setPlayerGameMode(session, gameMode); session.getConnector().getWorldManager().setPlayerGameMode(session, gameMode);
} }
offset++;
Difficulty difficulty = Difficulty.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()]; Difficulty difficulty = Difficulty.values()[(int) response.next()];
if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) { if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) {
session.getConnector().getWorldManager().setDifficulty(session, difficulty); session.getConnector().getWorldManager().setDifficulty(session, difficulty);
} }
offset++;
} }
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) { if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) {
offset++; // Game rule title
for (GameRule gamerule : GameRule.values()) { for (GameRule gamerule : GameRule.values()) {
if (gamerule.equals(GameRule.UNKNOWN)) { if (gamerule.equals(GameRule.UNKNOWN)) {
continue; continue;
} }
if (Boolean.class.equals(gamerule.getType())) { if (Boolean.class.equals(gamerule.getType())) {
boolean value = settingsResponse.getToggleResponses().get(offset); boolean value = response.next();
if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) { if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) {
session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value);
} }
} else if (Integer.class.equals(gamerule.getType())) { } else if (Integer.class.equals(gamerule.getType())) {
int value = Integer.parseInt(settingsResponse.getInputResponses().get(offset)); int value = Integer.parseInt(response.next());
if (value != session.getConnector().getWorldManager().getGameRuleInt(session, gamerule)) { if (value != session.getConnector().getWorldManager().getGameRuleInt(session, gamerule)) {
session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value);
} }
} }
offset++;
} }
} }
});
return true; return builder.build();
} }
} }

Datei anzeigen

@ -28,191 +28,165 @@ 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()
window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.generalButton", language), new FormImage(FormImage.FormImageType.PATH, "textures/ui/World"))); .translator(StatisticsUtils::translate, language)
.title("gui.stats")
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"))); .button("stat.generalButton", FormImage.Type.PATH, "textures/ui/World")
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"))); .button("stat.itemsButton - stat_type.minecraft.mined", FormImage.Type.PATH, "textures/items/iron_pickaxe")
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"))); .button("stat.itemsButton - stat_type.minecraft.broken", FormImage.Type.PATH, "textures/item/record_11")
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"))); .button("stat.itemsButton - stat_type.minecraft.crafted", FormImage.Type.PATH, "textures/blocks/crafting_table_side")
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"))); .button("stat.itemsButton - stat_type.minecraft.used", FormImage.Type.PATH, "textures/ui/Wrenches1")
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"))); .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")
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"))); .button("stat.mobsButton - geyser.statistics.killed", FormImage.Type.PATH, "textures/items/diamon_sword")
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"))); .button("stat.mobsButton - geyser.statistics.killed_by", FormImage.Type.PATH, "textures/ui/wither_heart_flash")
.responseHandler((form, responseData) -> {
return window; SimpleFormResponse response = form.parseResponse(responseData);
if (!response.isCorrect()) {
return;
} }
/** SimpleForm.Builder builder =
* Handle the menu form response SimpleForm.builder()
* .translator(StatisticsUtils::translate, language);
* @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 handleMenuForm(GeyserSession session, String response) {
SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_MENU_FORM_ID);
menuForm.setResponse(response);
SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse();
// Cache the language for cleaner access
String language = session.getClientData().getLanguageCode();
if (formResponse != null && formResponse.getClickedButton() != null) {
String title;
StringBuilder content = new StringBuilder(); StringBuilder content = new StringBuilder();
switch (formResponse.getClickedButtonId()) { switch (response.getClickedButtonId()) {
case 0: case 0:
title = LocaleUtils.getLocaleString("stat.generalButton", language); builder.title("stat.generalButton");
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 GenericStatistic) {
content.append(LocaleUtils.getLocaleString("stat.minecraft." + ((GenericStatistic) entry.getKey()).name().toLowerCase(), language) + ": " + entry.getValue() + "\n"); String statName = ((GenericStatistic) entry.getKey()).name().toLowerCase();
content.append("stat.minecraft.").append(statName).append(": ").append(entry.getValue()).append("\n");
} }
} }
break; break;
case 1: case 1:
title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language); builder.title("stat.itemsButton - stat_type.minecraft.mined");
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 BreakBlockStatistic) {
String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId()); String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId());
block = block.replace("minecraft:", "block.minecraft."); block = block.replace("minecraft:", "block.minecraft.");
block = LocaleUtils.getLocaleString(block, language); content.append(block).append(": ").append(entry.getValue()).append("\n");
content.append(block + ": " + entry.getValue() + "\n");
} }
} }
break; break;
case 2: case 2:
title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language); builder.title("stat.itemsButton - stat_type.minecraft.broken");
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 BreakItemStatistic) {
String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n");
} }
} }
break; break;
case 3: case 3:
title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language); builder.title("stat.itemsButton - stat_type.minecraft.crafted");
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) { for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
if (entry.getKey() instanceof CraftItemStatistic) { if (entry.getKey() instanceof CraftItemStatistic) {
String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n");
} }
} }
break; break;
case 4: case 4:
title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language); builder.title("stat.itemsButton - stat_type.minecraft.used");
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) { for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
if (entry.getKey() instanceof UseItemStatistic) { if (entry.getKey() instanceof UseItemStatistic) {
String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n");
} }
} }
break; break;
case 5: case 5:
title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language); 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 PickupItemStatistic) { if (entry.getKey() instanceof PickupItemStatistic) {
String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n");
} }
} }
break; break;
case 6: case 6:
title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language); 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 DropItemStatistic) { if (entry.getKey() instanceof DropItemStatistic) {
String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier();
content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n");
} }
} }
break; break;
case 7: case 7:
title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language); builder.title("stat.mobsButton - geyser.statistics.killed");
for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) { for (Map.Entry<Statistic, Integer> entry : session.getStatistics().entrySet()) {
if (entry.getKey() instanceof KillEntityStatistic) { if (entry.getKey() instanceof KillEntityStatistic) {
String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); String entityName = MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase();
content.append(mob + ": " + entry.getValue() + "\n"); content.append("entity.minecraft.").append(entityName).append(": ").append(entry.getValue()).append("\n");
} }
} }
break; break;
case 8: case 8:
title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", 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
.getStatistics().entrySet()) {
if (entry.getKey() instanceof KilledByEntityStatistic) { if (entry.getKey() instanceof KilledByEntityStatistic) {
String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); String entityName = MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase();
content.append(mob + ": " + entry.getValue() + "\n"); content.append("entity.minecraft.").append(entityName).append(": ").append(entry.getValue()).append("\n");
} }
} }
break; break;
default: default:
return false; return;
} }
if (content.length() == 0) { if (content.length() == 0) {
content = new StringBuilder(LanguageUtils.getPlayerLocaleString("geyser.statistics.none", language)); content = new StringBuilder("geyser.statistics.none");
} }
SimpleFormWindow window = new SimpleFormWindow(title, content.toString()); session.sendForm(
window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("gui.back", language), new FormImage(FormImage.FormImageType.PATH, "textures/gui/newgui/undo"))); builder.content(content.toString())
session.sendForm(window, STATISTICS_LIST_FORM_ID); .button("gui.back", FormImage.Type.PATH, "textures/gui/newgui/undo")
.responseHandler((form1, subFormResponseData) -> {
SimpleFormResponse response1 = form.parseResponse(subFormResponseData);
if (response1.isCorrect()) {
buildAndSendStatisticsMenu(session);
} }
}));
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;
} }
/** /**
@ -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);
}
} }

Datei anzeigen

@ -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

Datei anzeigen

@ -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.

Datei anzeigen

@ -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>