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