diff --git a/Extensions/AdminOnConnect/pom.xml b/Extensions/AdminOnConnect/pom.xml index f7bf8a0..4343f99 100644 --- a/Extensions/AdminOnConnect/pom.xml +++ b/Extensions/AdminOnConnect/pom.xml @@ -13,7 +13,7 @@ G-Earth G-Earth-Parent - 1.1 + 1.3 ../../ @@ -106,7 +106,7 @@ G-Earth G-Earth - 1.0 + 1.3 diff --git a/Extensions/BlockReplacePackets/pom.xml b/Extensions/BlockReplacePackets/pom.xml index 813592d..55dc0ca 100644 --- a/Extensions/BlockReplacePackets/pom.xml +++ b/Extensions/BlockReplacePackets/pom.xml @@ -13,7 +13,7 @@ G-Earth G-Earth-Parent - 1.1 + 1.3 ../../ @@ -106,7 +106,7 @@ G-Earth G-Earth - 1.0 + 1.3 diff --git a/G-Earth/pom.xml b/G-Earth/pom.xml index adbc5be..80081dc 100644 --- a/G-Earth/pom.xml +++ b/G-Earth/pom.xml @@ -8,22 +8,21 @@ jar - 1.1 + 1.3 1.8 + 9.4.35.v20201120 G-Earth G-Earth-Parent - 1.1 + 1.3 - - org.apache.maven.plugins @@ -36,7 +35,7 @@ true true lib/ - gearth.J11Main + gearth.Main false @@ -58,7 +57,7 @@ ${project.build.directory}/bin - gearth.J11Main + gearth.Main @@ -86,6 +85,11 @@ + + + + + @@ -123,6 +127,7 @@ + @@ -152,13 +157,51 @@ com.github.tulskiy jkeymaster - 1.2 + 1.3 org.slf4j slf4j-jdk14 2.0.0-alpha0 + + + org.apache.maven + maven-artifact + 3.6.3 + + + + + javax.websocket + javax.websocket-api + 1.1 + + + + org.eclipse.jetty + jetty-server + ${jettyVersion} + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + ${jettyVersion} + + + org.eclipse.jetty + jetty-http + ${jettyVersion} + + + + + + G-Earth + G-Wasm + 1.0 + + diff --git a/G-Earth/src/main/java/gearth/J11Main.java b/G-Earth/src/main/java/gearth/J11Main.java deleted file mode 100644 index 7fc0e6c..0000000 --- a/G-Earth/src/main/java/gearth/J11Main.java +++ /dev/null @@ -1,7 +0,0 @@ -package gearth; - -public class J11Main { - public static void main(String[] args) { - Main.main(args); - } -} diff --git a/G-Earth/src/main/java/gearth/Main.java b/G-Earth/src/main/java/gearth/Main.java index ce3138f..745b1db 100644 --- a/G-Earth/src/main/java/gearth/Main.java +++ b/G-Earth/src/main/java/gearth/Main.java @@ -1,54 +1,53 @@ package gearth; import gearth.misc.AdminValidator; +import gearth.ui.GEarthController; import javafx.application.Application; import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; -import javafx.scene.control.*; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.layout.FlowPane; import javafx.scene.layout.Region; import javafx.scene.web.WebView; import javafx.stage.Stage; -import gearth.ui.GEarthController; import org.json.JSONObject; import org.jsoup.Jsoup; import java.io.IOException; +import java.util.HashSet; +import java.util.Set; // run as root issue Invalid MIT-MAGIC-COOKIE-1 key fix: https://stackoverflow.com/questions/48139447/invalid-mit-magic-cookie-1-key public class Main extends Application { public static Application main; - public static String version = "1.1"; + public static String version = "1.3"; private static String gitApi = "https://api.github.com/repos/sirjonasxx/G-Earth/releases/latest"; @Override public void start(Stage primaryStage) throws Exception{ main = this; - FXMLLoader loader = new FXMLLoader(getClass().getResource("/gearth/ui/G-Earth.fxml")); + FXMLLoader loader = new FXMLLoader(getClass().getResource("ui/G-Earth.fxml")); Parent root = loader.load(); GEarthController companion = loader.getController(); companion.setStage(primaryStage); - primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/gearth/G-EarthLogoSmaller.png"))); + primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("G-EarthLogoSmaller.png"))); - // TODO fix primaryStage.setResizable(false); -// primaryStage.setResizable(true); -// primaryStage.onShownProperty().addListener(e -> { -// Platform.runLater(() -> primaryStage.setResizable(false)); -// }); - //primaryStage.initStyle(StageStyle.UNDECORATED); primaryStage.setTitle("G-Earth " + version); primaryStage.setScene(new Scene(root, 650, 295)); primaryStage.show(); - primaryStage.getScene().getStylesheets().add(getClass().getResource("/gearth/ui/bootstrap3.css").toExternalForm()); + primaryStage.getScene().getStylesheets().add(getClass().getResource("ui/bootstrap3.css").toExternalForm()); primaryStage.setOnCloseRequest( event -> { companion.exit(); @@ -58,21 +57,17 @@ public class Main extends Application { System.exit(0); }); - new Thread(() -> { - if (!AdminValidator.isAdmin()) { - Platform.runLater(() -> { - Alert alert = new Alert(Alert.AlertType.ERROR, "G-Earth needs admin privileges in order to work properly, please restart G-Earth with admin permissions unless you know what you're doing", ButtonType.OK); - alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); - alert.setResizable(false); -// alert.setResizable(true); -// alert.onShownProperty().addListener(e -> { -// Platform.runLater(() -> alert.setResizable(false)); -// }); - alert.show(); - }); - - } - }).start(); +// new Thread(() -> { +// if (!AdminValidator.isAdmin()) { +// Platform.runLater(() -> { +// Alert alert = new Alert(Alert.AlertType.ERROR, "G-Earth needs admin privileges in order to work properly, please restart G-Earth with admin permissions unless you know what you're doing", ButtonType.OK); +// alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); +// alert.setResizable(false); +// alert.show(); +// }); +// +// } +// }).start(); new Thread(() -> { try { @@ -83,7 +78,7 @@ public class Main extends Application { if (!gitv.equals(version)) { Platform.runLater(() -> { String body = (String)object.get("body"); - boolean isForcedUpdate = body.contains(""); + boolean isForcedUpdate = body.contains("(!)"); Alert alert = new Alert(isForcedUpdate ? Alert.AlertType.ERROR : Alert.AlertType.INFORMATION, "G-Earth is outdated!", ButtonType.OK); @@ -103,11 +98,6 @@ public class Main extends Application { webView.setPrefSize(500, 200); alert.setResizable(false); -// alert.setResizable(true); -// alert.onShownProperty().addListener(e -> { -// Platform.runLater(() -> alert.setResizable(false)); -// }); - alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); alert.getDialogPane().setContent(fp); if (isForcedUpdate) { diff --git a/G-Earth/src/main/java/gearth/extensions/Extension.java b/G-Earth/src/main/java/gearth/extensions/Extension.java index 5078c47..89d668f 100644 --- a/G-Earth/src/main/java/gearth/extensions/Extension.java +++ b/G-Earth/src/main/java/gearth/extensions/Extension.java @@ -298,7 +298,13 @@ public abstract class Extension implements IExtension { public boolean requestFlags(FlagsCheckListener flagRequestCallback) { if (this.flagRequestCallback != null) return false; this.flagRequestCallback = flagRequestCallback; - return true; + try { + writeToStream(new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.REQUESTFLAGS).toBytes()); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } } diff --git a/G-Earth/src/main/java/gearth/extensions/ExtensionForm.java b/G-Earth/src/main/java/gearth/extensions/ExtensionForm.java index 983ba74..145733c 100644 --- a/G-Earth/src/main/java/gearth/extensions/ExtensionForm.java +++ b/G-Earth/src/main/java/gearth/extensions/ExtensionForm.java @@ -11,7 +11,7 @@ import gearth.protocol.HPacket; public abstract class ExtensionForm implements IExtension { volatile Extension extension; - volatile Stage primaryStage; + protected volatile Stage primaryStage; protected static void runExtensionForm(String[] args, Class extension) { ExtensionFormLauncher launcher = new ExtensionFormLauncher(); diff --git a/G-Earth/src/main/java/gearth/extensions/extra/harble/HashSupport.java b/G-Earth/src/main/java/gearth/extensions/extra/harble/HashSupport.java index 842fbc5..f507f44 100644 --- a/G-Earth/src/main/java/gearth/extensions/extra/harble/HashSupport.java +++ b/G-Earth/src/main/java/gearth/extensions/extra/harble/HashSupport.java @@ -6,6 +6,7 @@ import gearth.misc.harble_api.HarbleAPI; import gearth.protocol.HMessage; import gearth.protocol.HPacket; +import java.io.File; import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.HashMap; @@ -30,7 +31,7 @@ public class HashSupport { extension.onConnect((host, port, hotelversion, cachePath) -> { // synchronized (lock) { - harbleAPI = new HarbleAPI(hotelversion, cachePath); + harbleAPI = new HarbleAPI(new File(cachePath)); // } }); diff --git a/G-Earth/src/main/java/gearth/extensions/parsers/HEntityUpdate.java b/G-Earth/src/main/java/gearth/extensions/parsers/HEntityUpdate.java index 3aab58a..56d28f7 100644 --- a/G-Earth/src/main/java/gearth/extensions/parsers/HEntityUpdate.java +++ b/G-Earth/src/main/java/gearth/extensions/parsers/HEntityUpdate.java @@ -4,14 +4,14 @@ import gearth.protocol.HPacket; public class HEntityUpdate { private int index; - private boolean isController; + private boolean isController = false; private HPoint tile; - private HPoint movingTo; + private HPoint movingTo = null; - private HSign sign; - private HStance stance; - private HAction action; + private HSign sign = null; + private HStance stance = null; + private HAction action = null; private HDirection headFacing; private HDirection bodyFacing; diff --git a/G-Earth/src/main/java/gearth/misc/harble_api/HarbleAPI.java b/G-Earth/src/main/java/gearth/misc/harble_api/HarbleAPI.java index 77f75b7..8333483 100644 --- a/G-Earth/src/main/java/gearth/misc/harble_api/HarbleAPI.java +++ b/G-Earth/src/main/java/gearth/misc/harble_api/HarbleAPI.java @@ -71,7 +71,6 @@ public class HarbleAPI { private boolean success = false; private String fullPath = null; - private String revision = null; /** * cache file must be generated first within G-Earth, inb4 20 extensions requesting it at the same time @@ -92,22 +91,18 @@ public class HarbleAPI { if (Cacher.cacheFileExists(possibleCachedMessagesPath)) { JSONObject object = Cacher.getCacheContents(possibleCachedMessagesPath); success = true; - revision = hotelversion; fullPath = Cacher.getCacheDir() + File.separator + possibleCachedMessagesPath; parse(object); } } - public HarbleAPI(String hotelversion, String path_to_file) { - - File f = new File(path_to_file); + public HarbleAPI(File f) { if (f.exists() && !f.isDirectory()) { try { String contents = String.join("\n", Files.readAllLines(f.toPath())); JSONObject object = new JSONObject(contents); success = true; - revision = hotelversion; - fullPath = path_to_file; + fullPath = f.getAbsolutePath(); parse(object); } catch (IOException e) { @@ -118,15 +113,16 @@ public class HarbleAPI { private void addMessage(HMessage.Direction direction, JSONObject object) { String name; - try { - name = object.getString("Name"); - } catch (Exception e) { - name = null; - } - String hash = object.getString("Hash"); - Integer headerId = object.getInt("Id"); - String structure; + String hash; + try { name = object.getString("Name"); } + catch (Exception e) { name = null; } + try { hash = object.getString("Hash"); } + catch (Exception e) { hash = null; } + + int headerId = object.getInt("Id"); + + String structure; try { structure = object.getString("Structure"); } catch (Exception e) { @@ -218,10 +214,6 @@ public class HarbleAPI { return nameToMessage.get(name); } - public String getRevision() { - return revision; - } - public String getPath() { if (success) { return fullPath; diff --git a/G-Earth/src/main/java/gearth/misc/harble_api/HarbleAPIFetcher.java b/G-Earth/src/main/java/gearth/misc/harble_api/HarbleAPIFetcher.java index c99d942..08beed3 100644 --- a/G-Earth/src/main/java/gearth/misc/harble_api/HarbleAPIFetcher.java +++ b/G-Earth/src/main/java/gearth/misc/harble_api/HarbleAPIFetcher.java @@ -1,11 +1,14 @@ package gearth.misc.harble_api; +import gearth.Main; import gearth.misc.Cacher; import gearth.protocol.HMessage; import org.jsoup.Connection; import org.jsoup.Jsoup; +import java.io.File; import java.io.IOException; +import java.net.URISyntaxException; /** * Created by Jonas on 10/11/2018. @@ -36,6 +39,20 @@ public class HarbleAPIFetcher { public static HarbleAPI HARBLEAPI = null; public synchronized static void fetch(String hotelversion) { + // if unity + if (!(hotelversion.toLowerCase().contains("production") || hotelversion.toLowerCase().contains("release"))) { + try { + HARBLEAPI = new HarbleAPI( + new File(new File(Main.class.getProtectionDomain().getCodeSource().getLocation().toURI()) + .getParentFile(), "messages.json" + ) + ); + } catch (URISyntaxException e) { + HARBLEAPI = null; + } + return; + } + String cacheName = CACHE_PREFIX + hotelversion; if (Cacher.cacheFileExists(cacheName)) { diff --git a/G-Earth/src/main/java/gearth/misc/packetrepresentation/PacketStringUtils.java b/G-Earth/src/main/java/gearth/misc/packetrepresentation/PacketStringUtils.java index 7814fc3..adbafe8 100644 --- a/G-Earth/src/main/java/gearth/misc/packetrepresentation/PacketStringUtils.java +++ b/G-Earth/src/main/java/gearth/misc/packetrepresentation/PacketStringUtils.java @@ -39,12 +39,18 @@ public class PacketStringUtils { packet = replaceAll(packet, "\\{i:(-?[0-9]+)}", m -> toString(ByteBuffer.allocate(4).putInt(Integer.parseInt(m.group(1))).array())); + packet = replaceAll(packet, "\\{l:(-?[0-9]+)}", + m -> toString(ByteBuffer.allocate(8).putLong(Integer.parseInt(m.group(1))).array())); + packet = replaceAll(packet, "\\{d:(-?[0-9]*\\.[0-9]*)}", m -> toString(ByteBuffer.allocate(8).putDouble(Double.parseDouble(m.group(1))).array())); packet = replaceAll(packet, "\\{u:([0-9]+)}", m -> "[" + (Integer.parseInt(m.group(1))/256) + "][" + (Integer.parseInt(m.group(1)) % 256) + "]"); + packet = replaceAll(packet, "\\{h:([0-9]+)}", + m -> "[" + (Integer.parseInt(m.group(1))/256) + "][" + (Integer.parseInt(m.group(1)) % 256) + "]"); + packet = replaceAll(packet, "\\{b:([Ff]alse|[Tt]rue)}", m -> m.group(1).toLowerCase().equals("true") ? "[1]" : "[0]"); @@ -174,7 +180,7 @@ public class PacketStringUtils { packet.resetReadIndex(); StringBuilder builder = new StringBuilder(); - builder.append("{l}{u:").append(packet.headerId()).append("}"); + builder.append("{l}{h:").append(packet.headerId()).append("}"); buildExpressionFromGivenStructure(packet, struct, 0, builder); packet.setReadIndex(oldReadIndex); @@ -196,7 +202,7 @@ public class PacketStringUtils { } else if (c == 'i') builder.append("{i:").append(prevInt = p.readInteger()).append('}'); else if (c == 's') builder.append("{s:\"").append( - new String(p.readString().getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8) + p.readString(StandardCharsets.UTF_8) .replace("\\", "\\\\") // \ -> \\ .replace("\"", "\\\"") // " -> \" .replace("\r", "\\r") // CR -> \r @@ -204,6 +210,8 @@ public class PacketStringUtils { else if (c == 'd') builder.append("{d:").append(p.readDouble()).append('}'); else if (c == 'b') builder.append("{b:").append(p.readByte()).append('}'); else if (c == 'B') builder.append("{b:").append(p.readBoolean()).append('}'); + else if (c == 'l') builder.append("{l:").append(p.readLong()).append('}'); + else if (c == 'u') builder.append("{u:").append(p.readShort()).append('}'); else return; } } @@ -232,16 +240,16 @@ public class PacketStringUtils { } public static void main(String[] args) throws InvalidPacketException { - HPacket p1 = fromString("{l}{u:1129}{s:\"\\\\\\\\\"}{i:0}{i:0}"); + HPacket p1 = fromString("{l}{h:1129}{s:\"\\\\\\\\\"}{i:0}{i:0}"); System.out.println(p1.toExpression()); HPacket p1_2 = fromString(p1.toExpression()); System.out.println(p1_2.toExpression()); - HPacket p2 = fromString("{l}{u:4564}{i:3}{i:0}{s:\"hi\"}{i:0}{i:1}{s:\"how\"}{i:3}{b:1}{b:2}{b:3}{i:2}{s:\"r u\"}{i:1}{b:120}{i:2}{b:true}"); + HPacket p2 = fromString("{l}{h:4564}{i:3}{i:0}{s:\"hi\"}{i:0}{i:1}{s:\"how\"}{i:3}{b:1}{b:2}{b:3}{i:2}{s:\"r u\"}{i:1}{b:120}{i:2}{b:true}"); System.out.println(p2); System.out.println(structureEquals( - new HPacket("{l}{u:5}{s:\"asdas\"}"), + new HPacket("{l}{h:5}{s:\"asdas\"}"), "s" )); } diff --git a/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/StructurePredictor.java b/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/StructurePredictor.java index aa1f413..8216489 100644 --- a/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/StructurePredictor.java +++ b/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/StructurePredictor.java @@ -9,8 +9,8 @@ import java.util.List; public class StructurePredictor { - HPacket packet; - String structure; // null if not found/ didnt try to find + private HPacket packet; + private String structure; // null if not found/ didnt try to find public StructurePredictor(HPacket packet) { this.packet = packet; @@ -44,17 +44,12 @@ public class StructurePredictor { while (index < packet.getBytesLength()) { double currentLogScore = dynamic[index - 6].logScore; for (TypeChecker typeChecker : typeCheckers) { - if (typeChecker.canRead(index)) { - double score = typeChecker.score(index); - double newScore = currentLogScore + Math.log(score); - int nextIndex = typeChecker.nextIndex(index) - 6; - if (dynamic[nextIndex] == null || newScore > dynamic[nextIndex].logScore) { - dynamic[nextIndex] = new SubStructure( - index - 6, - typeChecker.getStructCode(), - newScore - ); - } + if (!typeChecker.canRead(index)) continue; + + double newScore = currentLogScore + Math.log(typeChecker.score(index)); + int nextIndex = typeChecker.nextIndex(index) - 6; + if (dynamic[nextIndex] == null || newScore > dynamic[nextIndex].logScore) { + dynamic[nextIndex] = new SubStructure(index - 6, typeChecker.getStructCode(), newScore); } } index++; @@ -80,39 +75,4 @@ public class StructurePredictor { public String getStructure() { return structure; } - - public static void main(String[] args) { - HPacket[] packets = new HPacket[] { - new HPacket("{l}{u:500}{i:20}"), - new HPacket(4002, "test"), - new HPacket(4002, "test", 0, true), - new HPacket(4002, "test", "testtsd", 54452, true, false), - new HPacket(4002, "test", 46564554, "testtsd", 54452, true, false), - new HPacket(4002, "test", 0, 46564554, "testtsd", 54452, true, false), - new HPacket(4002, -1, "test", 0, 46564554, "testtsd", -1, 54452, true, false), - new HPacket(4002, -1, "test", 0, 46564554, "testtsd", -1, 54452, false, true, ""), - new HPacket(4002, -1, "test", 0, 46564554, "testtsd", -1, 54452, false, true, ""), - new HPacket(4002, -1, "test", 0, 46564554, "testtsd", -1, 54452, false, true, 0), - new HPacket(4002, -1, (byte) 5, "test", 0, 46564554, "testtsd", -1, 54452, false, true, 0), - new HPacket(4002, "", 20, 0), - new HPacket(4002, 1), - new HPacket(4002, 0, 0), - new HPacket(4002, 0, 0, 42, false), - new HPacket(4002, 0, ""), - new HPacket("[0][0][0]F[0] [0][0][0] [0][0][0][5][0]\u0013ACH_FriendListSize5[0][0][0]U[0][0][0] [0][0][0][0][0][0][0][0][0][0][0]Z[0][0][6]social[0][0][0][0][0][13][0][0][0][2]"), - new HPacket("[0][0][0]?[0] [0][0][0][4][0][0][0][4][0][10]ACH_Login4[0][0][0]\u000F[0][0][0]\u001C[0][0][0][0][0][0][0][0][0][0][0][2][0][0][8]identity[0][0][0][0][0]\u0014[0][0][0][0]"), - new HPacket("[0][0][0][6]\u000Ew[0][0][0][0]"), - new HPacket("[0][0][0]'[3] [0][5]Login[0][6]socket[0]\u000Eclient.auth_ok[0][0][0][0][0][0]"), - new HPacket(4002, false, ""), - new HPacket("{l}{u:145}[0][0][0]ÿÿÿÿ[0]"), -// new HPacket("[0][0][1]p[13]1[0][0][0][12][0][10]MOUSE_ZOOM[0][0][1][0]\u0015HABBO_CLUB_OFFER_BETA[0][0][1][0]\u000EUSE_GUIDE_TOOL[0]&requirement.unfulfilled.helper_level_4[0][0]\u000FBUILDER_AT_WORK[0](requirement.unfulfilled.group_membership[0][0]\u000FCALL_ON_HELPERS[0][0][1][0]\u001FNAVIGATOR_ROOM_THUMBNAIL_CAMERA[0][0][1][0][7]CITIZEN[0][0][1][0]\u0012JUDGE_CHAT_REVIEWS[0]&requirement.unfulfilled.helper_level_6[0][0][5]TRADE[0][0][1][0][6]CAMERA[0][0][1][0]\u0014VOTE_IN_COMPETITIONS[0][0][1][0]\u0018NAVIGATOR_PHASE_TWO_2014[0][0][1]") - - }; - - for (HPacket packet : packets) { - StructurePredictor structurePredictor = new StructurePredictor(packet); - System.out.println(structurePredictor.getStructure()); - System.out.println(structurePredictor.getExpression()); - } - } } diff --git a/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/IntegerChecker.java b/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/IntegerChecker.java index 54c9941..d34c0a3 100644 --- a/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/IntegerChecker.java +++ b/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/IntegerChecker.java @@ -1,6 +1,7 @@ package gearth.misc.packetrepresentation.prediction.checkers; import gearth.protocol.HPacket; +import gearth.services.Constants; import java.nio.charset.StandardCharsets; @@ -33,7 +34,7 @@ public class IntegerChecker extends TypeChecker { } // 4 bytes that read [0][2]xy could be a string - if (ushortTest >= 2 && ushortTest <= 6 && StringChecker.canReadString(hPacket, index)) { + if (ushortTest == 2 && StringChecker.canReadString(hPacket, index)) { return (1 - StringChecker.scoreString(hPacket.readString(index))); } diff --git a/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/LongChecker.java b/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/LongChecker.java new file mode 100644 index 0000000..7cc3591 --- /dev/null +++ b/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/LongChecker.java @@ -0,0 +1,45 @@ +package gearth.misc.packetrepresentation.prediction.checkers; + +import gearth.protocol.HPacket; + +public class LongChecker extends TypeChecker { + + private IntegerChecker integerChecker; + + protected LongChecker(HPacket hPacket) { + super("l", hPacket); + integerChecker = new IntegerChecker(hPacket); + } + + @Override + public boolean canRead(int index) { + return index >= 6 && !(index + 8 > hPacket.getBytesLength()); + } + + @Override + public double score(int index) { + int split1 = hPacket.readInteger(index); + int split2 = hPacket.readInteger(index + 4); + + int zeros = 0; + for (int i = index + 4; i < index + 8; i++) { + zeros += hPacket.readByte(i) == 0 ? 1 : 0; + } + + if (split2 > 256 * 256 * 3 && split1 == 0 && zeros < 2) { + return integerChecker.score(index) * integerChecker.score(index + 4) + 0.0000000001; + } + + return 0; + } + + @Override + Long get(int index) { + return hPacket.readLong(index); + } + + @Override + int nextIndexSafe(int index) { + return index + 8; + } +} diff --git a/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/ShortChecker.java b/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/ShortChecker.java new file mode 100644 index 0000000..d125e3b --- /dev/null +++ b/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/ShortChecker.java @@ -0,0 +1,56 @@ +package gearth.misc.packetrepresentation.prediction.checkers; + +import gearth.protocol.HPacket; + +public class ShortChecker extends TypeChecker { + + private BooleanChecker booleanChecker; + private ByteChecker byteChecker; + + protected ShortChecker(HPacket hPacket) { + super("u", hPacket); + booleanChecker = new BooleanChecker(hPacket); + byteChecker = new ByteChecker(hPacket); + } + + @Override + public boolean canRead(int index) { + return index >= 6 && !(index + 2 > hPacket.getBytesLength()); + } + + @Override + public double score(int index) { + short val = hPacket.readShort(index); + + if (index == 6 && val == 0 && hPacket.length() == nextIndexSafe(index)) { + return 1; + } + + if (val <= 0) { + return 0; + } + + if (val < 1000) { + return 0.5; + } + + double leftMinScore; + double rightMinScore; + if (booleanChecker.canRead(index)) leftMinScore = booleanChecker.score(index); + else leftMinScore = byteChecker.score(index); + if (booleanChecker.canRead(index+1)) rightMinScore = booleanChecker.score(index+1); + else rightMinScore = byteChecker.score(index+1); + + return leftMinScore * rightMinScore + 0.00000001; + } + + @Override + Object get(int index) { + return hPacket.readShort(); + } + + @Override + int nextIndexSafe(int index) { + return index + 2; + } +} diff --git a/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/TypeCheckerProducer.java b/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/TypeCheckerProducer.java index 675699c..11255a7 100644 --- a/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/TypeCheckerProducer.java +++ b/G-Earth/src/main/java/gearth/misc/packetrepresentation/prediction/checkers/TypeCheckerProducer.java @@ -2,18 +2,27 @@ package gearth.misc.packetrepresentation.prediction.checkers; import gearth.protocol.HPacket; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import static gearth.services.Constants.UNITY_PACKETS; + public class TypeCheckerProducer { public static List getValidators(HPacket packet) { - return Arrays.asList( + List typeCheckers = new ArrayList<>(Arrays.asList( new BooleanChecker(packet), new ByteChecker(packet), new IntegerChecker(packet), - new StringChecker(packet) - ); + new StringChecker(packet))); + + if (UNITY_PACKETS) { + typeCheckers.add(new LongChecker(packet)); + typeCheckers.add(new ShortChecker(packet)); + } + + return typeCheckers; } } diff --git a/G-Earth/src/main/java/gearth/protocol/HConnection.java b/G-Earth/src/main/java/gearth/protocol/HConnection.java index 1a84a6d..97ed7d4 100644 --- a/G-Earth/src/main/java/gearth/protocol/HConnection.java +++ b/G-Earth/src/main/java/gearth/protocol/HConnection.java @@ -4,9 +4,10 @@ import gearth.misc.listenerpattern.Observable; import gearth.protocol.connection.HProxy; import gearth.protocol.connection.HState; import gearth.protocol.connection.proxy.ProxyProvider; +import gearth.protocol.connection.proxy.flash.FlashProxyProvider; import gearth.protocol.connection.proxy.ProxyProviderFactory; -import gearth.protocol.connection.proxy.unix.LinuxRawIpProxyProvider; -import gearth.protocol.connection.proxy.windows.WindowsRawIpProxyProvider; +import gearth.protocol.connection.proxy.flash.unix.LinuxRawIpFlashProxyProvider; +import gearth.protocol.connection.proxy.unity.UnityProxyProvider; import gearth.services.extensionhandler.ExtensionHandler; import java.io.IOException; @@ -60,6 +61,12 @@ public class HConnection { startMITM(); } + public void startUnity() { + HConnection selff = this; + proxyProvider = new UnityProxyProvider(proxy -> selff.proxy = proxy, selff::setState, this); + startMITM(); + } + private void startMITM() { try { if (proxyProvider != null) { @@ -166,7 +173,7 @@ public class HConnection { } public boolean isRawIpMode() { - return proxyProvider != null && proxyProvider instanceof LinuxRawIpProxyProvider; + return proxyProvider != null && proxyProvider instanceof LinuxRawIpFlashProxyProvider; // WindowsRawIpProxyProvider extends LinuxRawIpProxyProvider } diff --git a/G-Earth/src/main/java/gearth/protocol/HPacket.java b/G-Earth/src/main/java/gearth/protocol/HPacket.java index 1cde853..c1cfb94 100644 --- a/G-Earth/src/main/java/gearth/protocol/HPacket.java +++ b/G-Earth/src/main/java/gearth/protocol/HPacket.java @@ -8,6 +8,7 @@ import gearth.misc.packetrepresentation.PacketStringUtils; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.InvalidParameterException; import java.util.Arrays; @@ -36,7 +37,7 @@ public class HPacket implements StringifyAble { } public HPacket(int header) { packetInBytes = new byte[]{0,0,0,2,0,0}; - replaceUShort(4, header); + replaceShort(4, (short)header); isEdited = false; } public HPacket(int header, byte[] bytes) { @@ -126,7 +127,7 @@ public class HPacket implements StringifyAble { } public int headerId() { - return readUshort(4); + return readShort(4); } public int readInteger(){ @@ -180,34 +181,49 @@ public class HPacket implements StringifyAble { return java.nio.ByteBuffer.wrap(btarray).getLong(); } - public String readString() { - String r = readString(readIndex); - readIndex += (2 + r.length()); + + public String readString(Charset charset) { + String r = readString(readIndex, charset); + readIndex += (2 + readUshort(readIndex)); return r; } - public String readString(int index) { + public String readString(int index, Charset charset) { int length = readUshort(index); index+=2; - - return readString(index, length); + return readString(index, length, charset); } - private String readString(int index, int length) { + public String readString() { + return readString(StandardCharsets.ISO_8859_1); + } + public String readString(int index) { + return readString(index, StandardCharsets.ISO_8859_1); + } + + private String readString(int index, int length, Charset charset) { byte[] x = new byte[length]; for (int i = 0; i < x.length; i++) { x[i] = readByte(index); index++; } - return new String(x, StandardCharsets.ISO_8859_1); + return new String(x, charset); } - public String readLongString() { - String r = readLongString(readIndex); - readIndex += (4 + r.length()); + + public String readLongString(Charset charset) { + String r = readLongString(readIndex, charset); + readIndex += (4 + readInteger(readIndex)); return r; } - public String readLongString(int index) { + public String readLongString(int index, Charset charset) { int length = readInteger(index); index += 4; - return readString(index, length); + return readString(index, length, charset); + } + + public String readLongString() { + return readLongString(StandardCharsets.ISO_8859_1); + } + public String readLongString(int index) { + return readLongString(index, StandardCharsets.ISO_8859_1); } public boolean readBoolean() { @@ -231,6 +247,14 @@ public class HPacket implements StringifyAble { } return this; } + public HPacket replaceLong(int index, long l) { + isEdited = true; + ByteBuffer b = ByteBuffer.allocate(8).putLong(l); + for (int j = 0; j < 8; j++) { + packetInBytes[index + j] = b.array()[j]; + } + return this; + } public HPacket replaceDouble(int index, double d) { isEdited = true; ByteBuffer b = ByteBuffer.allocate(8).putDouble(d); @@ -267,10 +291,11 @@ public class HPacket implements StringifyAble { packetInBytes[index + 1] = b.array()[1]; return this; } - public HPacket replaceString(int index, String s) { + + public HPacket replaceString(int index, String s, Charset charset) { isEdited = true; - byte[] sbytes = s.getBytes(StandardCharsets.ISO_8859_1); - int mover = s.length() - readUshort(index); + byte[] sbytes = s.getBytes(charset); + int mover = sbytes.length - readUshort(index); if (mover != 0) { byte[] newPacket = Arrays.copyOf(packetInBytes, packetInBytes.length + mover); @@ -283,7 +308,7 @@ public class HPacket implements StringifyAble { } } else { - int i = index + 2 + s.length(); + int i = index + 2 + sbytes.length; while (i < newPacket.length) { newPacket[i] = packetInBytes[i - mover]; i++; @@ -294,13 +319,17 @@ public class HPacket implements StringifyAble { fixLength(); } - replaceUShort(index, s.length()); - for (int i = 0; i < s.length(); i++) { + replaceUShort(index, sbytes.length); + for (int i = 0; i < sbytes.length; i++) { packetInBytes[index + 2 + i] = sbytes[i]; } return this; } + public HPacket replaceString(int index, String s) { + return replaceString(index, s, StandardCharsets.ISO_8859_1); + } + private boolean canReadString(int index) { if (index < packetInBytes.length - 1) { int l = readUshort(index); @@ -394,6 +423,16 @@ public class HPacket implements StringifyAble { fixLength(); return this; } + public HPacket appendLong(long l) { + isEdited = true; + packetInBytes = Arrays.copyOf(packetInBytes, packetInBytes.length + 8); + ByteBuffer byteBuffer = ByteBuffer.allocate(8).putLong(l); + for (int j = 0; j < 8; j++) { + packetInBytes[packetInBytes.length - 8 + j] = byteBuffer.array()[j]; + } + fixLength(); + return this; + } public HPacket appendDouble(double d) { isEdited = true; packetInBytes = Arrays.copyOf(packetInBytes, packetInBytes.length + 8); @@ -445,18 +484,27 @@ public class HPacket implements StringifyAble { fixLength(); return this; } + public HPacket appendString(String s, Charset charset) { + isEdited = true; + appendUShort(s.getBytes(charset).length); + appendBytes(s.getBytes(charset)); + return this; + } public HPacket appendString(String s) { + return appendString(s, StandardCharsets.ISO_8859_1); + } + + public HPacket appendLongString(String s, Charset charset) { isEdited = true; - appendUShort(s.length()); - appendBytes(s.getBytes(StandardCharsets.ISO_8859_1)); + appendInt(s.getBytes(charset).length); + appendBytes(s.getBytes(charset)); return this; } + public HPacket appendLongString(String s) { - isEdited = true; - appendInt(s.length()); - appendBytes(s.getBytes(StandardCharsets.ISO_8859_1)); - return this; + return appendLongString(s, StandardCharsets.ISO_8859_1); } + public HPacket appendObject(Object o) throws InvalidParameterException { isEdited = true; @@ -472,6 +520,9 @@ public class HPacket implements StringifyAble { else if (o instanceof Boolean) { appendBoolean((Boolean) o); } + else if (o instanceof Long) { + appendLong((Long) o); + } else { throw new InvalidParameterException(); } @@ -479,20 +530,6 @@ public class HPacket implements StringifyAble { return this; } - - public HPacket removeFrom(int index) { - return removeRange(index, packetInBytes.length - index); - } - public HPacket removeRange(int index, int length) { - isEdited = true; - for (int i = index; i < packetInBytes.length - length; i++) { - packetInBytes[i] = packetInBytes[i + length]; - } - packetInBytes = Arrays.copyOf(packetInBytes, packetInBytes.length - length); - fixLength(); - return this; - } - public boolean isReplaced() { return isEdited; } @@ -507,25 +544,44 @@ public class HPacket implements StringifyAble { isEdited = edited; } - public String toExpression(HMessage.Direction direction) { - if (isCorrupted()) return ""; - + private String getHarbleStructure(HMessage.Direction direction) { HarbleAPI.HarbleMessage msg; if (HarbleAPIFetcher.HARBLEAPI != null && ((msg = HarbleAPIFetcher.HARBLEAPI.getHarbleMessageFromHeaderId(direction, headerId())) != null)) { - if (msg.getStructure() != null) { - return PacketStringUtils.toExpressionFromGivenStructure(this, msg.getStructure()); + if (msg.getStructure() != null && structureEquals(msg.getStructure())) { + return msg.getStructure(); } } - return toExpression(); + + return null; + } + + public String toExpression(HMessage.Direction direction) { + if (isCorrupted()) return ""; + + String structure = getHarbleStructure(direction); + if (structure != null) { + return PacketStringUtils.toExpressionFromGivenStructure(this, structure); + } + + return PacketStringUtils.predictedExpression(this); } /** * returns "" if not found or not sure enough - * dont hate on the coding quality in this function, its pretty effective. */ public String toExpression() { if (isCorrupted()) return ""; + + String structure1 = getHarbleStructure(HMessage.Direction.TOCLIENT); + String structure2 = getHarbleStructure(HMessage.Direction.TOSERVER); + if (structure1 != null && structure2 == null) { + return PacketStringUtils.toExpressionFromGivenStructure(this, structure1); + } + else if (structure1 == null && structure2 != null) { + return PacketStringUtils.toExpressionFromGivenStructure(this, structure2); + } + return PacketStringUtils.predictedExpression(this); } @@ -551,7 +607,7 @@ public class HPacket implements StringifyAble { } public static void main(String[] args) { - HPacket packet = new HPacket("{l}{u:4564}{i:3}{i:0}{s:\"hi\"}{i:0}{i:1}{s:\"how\"}{i:3}{b:1}{b:2}{b:3}{i:2}{s:\"r u\"}{i:1}{b:120}{i:2}{b:true}{d:1.4}"); + HPacket packet = new HPacket("{l}{h:4564}{i:3}{i:0}{s:\"hi\"}{i:0}{i:1}{s:\"how\"}{i:3}{b:1}{b:2}{b:3}{i:2}{s:\"r u\"}{i:1}{b:120}{i:2}{b:true}{d:1.4}"); String str = PacketStringUtils.toExpressionFromGivenStructure(packet, "i(isi(b))iBd"); diff --git a/G-Earth/src/main/java/gearth/protocol/connection/HClient.java b/G-Earth/src/main/java/gearth/protocol/connection/HClient.java new file mode 100644 index 0000000..54653c3 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/HClient.java @@ -0,0 +1,6 @@ +package gearth.protocol.connection; + +public enum HClient { + UNITY, + FLASH +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/HProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/HProxy.java index b49fd5e..e5f1147 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/HProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/HProxy.java @@ -1,11 +1,13 @@ package gearth.protocol.connection; -import gearth.protocol.packethandler.IncomingPacketHandler; -import gearth.protocol.packethandler.OutgoingPacketHandler; +import gearth.protocol.packethandler.PacketHandler; import java.net.ServerSocket; public class HProxy { + + private final HClient hClient; + private volatile String input_domain; //string representation of the domain to intercept private volatile String actual_domain; //dns resolved domain (ignoring hosts file) private volatile int actual_port; //port of the server @@ -15,13 +17,14 @@ public class HProxy { private volatile ServerSocket proxy_server = null; //listener for the client - private volatile IncomingPacketHandler inHandler = null; //connection with client (only initialized when verified habbo connection) - private volatile OutgoingPacketHandler outHandler = null; //connection with server (only initialized when verified habbo connection) + private volatile PacketHandler inHandler = null; //connection with client (only initialized when verified habbo connection) + private volatile PacketHandler outHandler = null; //connection with server (only initialized when verified habbo connection) private volatile String hotelVersion = ""; private volatile AsyncPacketSender asyncPacketSender = null; - public HProxy(String input_domain, String actual_domain, int actual_port, int intercept_port, String intercept_host) { + public HProxy(HClient hClient, String input_domain, String actual_domain, int actual_port, int intercept_port, String intercept_host) { + this.hClient = hClient; this.input_domain = input_domain; this.actual_domain = actual_domain; this.actual_port = actual_port; @@ -33,7 +36,7 @@ public class HProxy { this.proxy_server = socket; } - public void verifyProxy(IncomingPacketHandler incomingHandler, OutgoingPacketHandler outgoingHandler, String hotelVersion) { + public void verifyProxy(PacketHandler incomingHandler, PacketHandler outgoingHandler, String hotelVersion) { this.inHandler = incomingHandler; this.outHandler = outgoingHandler; this.hotelVersion = hotelVersion; @@ -64,11 +67,11 @@ public class HProxy { return intercept_host; } - public IncomingPacketHandler getInHandler() { + public PacketHandler getInHandler() { return inHandler; } - public OutgoingPacketHandler getOutHandler() { + public PacketHandler getOutHandler() { return outHandler; } @@ -79,4 +82,8 @@ public class HProxy { public AsyncPacketSender getAsyncPacketSender() { return asyncPacketSender; } + + public HClient gethClient() { + return hClient; + } } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/ProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/ProxyProvider.java index a1faa47..c3a831d 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/ProxyProvider.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/ProxyProvider.java @@ -1,129 +1,10 @@ package gearth.protocol.connection.proxy; -import gearth.protocol.HConnection; -import gearth.protocol.connection.HProxy; -import gearth.protocol.connection.HProxySetter; -import gearth.protocol.connection.HState; -import gearth.protocol.connection.HStateSetter; -import gearth.protocol.memory.Rc4Obtainer; -import gearth.protocol.packethandler.IncomingPacketHandler; -import gearth.protocol.packethandler.OutgoingPacketHandler; -import gearth.protocol.packethandler.PacketHandler; -import javafx.application.Platform; -import javafx.scene.control.Alert; -import javafx.scene.control.ButtonType; -import javafx.scene.layout.Region; - import java.io.IOException; -import java.net.Socket; -import java.util.Arrays; -import java.util.concurrent.Semaphore; -public abstract class ProxyProvider { +public interface ProxyProvider { - protected final HProxySetter proxySetter; - protected final HStateSetter stateSetter; - protected final HConnection hConnection; - - private Semaphore abortSemaphore = null; - - public ProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection){ - this.proxySetter = proxySetter; - this.stateSetter = stateSetter; - this.hConnection = hConnection; - } - - protected void startProxyThread(Socket client, Socket server, HProxy proxy) throws IOException, InterruptedException { - final boolean[] datastream = new boolean[1]; - server.setTcpNoDelay(true); - client.setTcpNoDelay(true); - - client.setSoTimeout(0); - server.setSoTimeout(0); - - if (HConnection.DEBUG) System.out.println(server.getLocalAddress().getHostAddress() + ": " + server.getLocalPort()); - Rc4Obtainer rc4Obtainer = new Rc4Obtainer(hConnection); - - OutgoingPacketHandler outgoingHandler = new OutgoingPacketHandler(server.getOutputStream(), hConnection.getTrafficObservables(), hConnection.getExtensionHandler()); - IncomingPacketHandler incomingHandler = new IncomingPacketHandler(client.getOutputStream(), hConnection.getTrafficObservables(), outgoingHandler, hConnection.getExtensionHandler()); - rc4Obtainer.setPacketHandlers(outgoingHandler, incomingHandler); - - Semaphore abort = new Semaphore(0); - - outgoingHandler.addOnDatastreamConfirmedListener(hotelVersion -> { - incomingHandler.setAsDataStream(); - proxy.verifyProxy(incomingHandler, outgoingHandler, hotelVersion); - proxySetter.setProxy(proxy); - datastream[0] = true; - abortSemaphore = abort; - onConnect(); - }); - - handleInputStream(client, outgoingHandler, abort); - handleInputStream(server, incomingHandler, abort); - - // abort can be acquired as soon as one of the sockets is closed - abort.acquire(); - try { - if (!server.isClosed()) server.close(); - if (!client.isClosed()) client.close(); - if (HConnection.DEBUG) System.out.println("STOP"); - if (datastream[0]) { - onConnectEnd(); - }; - } - catch (IOException e) { - e.printStackTrace(); - } - } - - private void handleInputStream(Socket socket, PacketHandler packetHandler, Semaphore abort) { - new Thread(() -> { - try { - int readLength; - byte[] buffer = new byte[10000]; - while (!socket.isClosed() && - (hConnection.getState() == HState.WAITING_FOR_CLIENT || hConnection.getState() == HState.CONNECTED) && - (readLength = socket.getInputStream().read(buffer)) != -1) { - packetHandler.act(Arrays.copyOf(buffer, readLength)); - } - } - catch (IOException ignore) { -// System.err.println(packetHandler instanceof IncomingPacketHandler ? "incoming" : "outgoing"); -// ignore.printStackTrace(); - } finally { - abort.release(); - } - }).start(); - } - - - public abstract void start() throws IOException; - public void abort() { - if (abortSemaphore != null) { - abortSemaphore.release(); - } - else { - stateSetter.setState(HState.NOT_CONNECTED); - } - } - - protected void onConnect() { - stateSetter.setState(HState.CONNECTED); - } - protected void onConnectEnd() { - proxySetter.setProxy(null); - abortSemaphore = null; - stateSetter.setState(HState.NOT_CONNECTED); - } - - protected void showInvalidConnectionError() { - Platform.runLater(() -> { - Alert alert = new Alert(Alert.AlertType.ERROR, "You entered invalid connection information, G-Earth could not connect", ButtonType.OK); - alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); - alert.setResizable(false); - alert.show(); - }); - } + void start() throws IOException; + void abort(); } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/ProxyProviderFactory.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/ProxyProviderFactory.java index 9cbe29e..59eecb4 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/ProxyProviderFactory.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/ProxyProviderFactory.java @@ -5,8 +5,10 @@ import gearth.misc.OSValidator; import gearth.protocol.HConnection; import gearth.protocol.connection.HProxySetter; import gearth.protocol.connection.HStateSetter; -import gearth.protocol.connection.proxy.unix.LinuxRawIpProxyProvider; -import gearth.protocol.connection.proxy.windows.WindowsRawIpProxyProvider; +import gearth.protocol.connection.proxy.flash.NormalFlashProxyProvider; +import gearth.protocol.connection.proxy.flash.FlashProxyProvider; +import gearth.protocol.connection.proxy.flash.unix.LinuxRawIpFlashProxyProvider; +import gearth.protocol.connection.proxy.flash.windows.WindowsRawIpFlashProxyProvider; import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; @@ -67,7 +69,7 @@ public class ProxyProviderFactory { // checks if host is a raw IP instead of a domain // TODO support ipv6 (not only here, also in IPmapper) - static boolean hostIsIpAddress(String host){ + public static boolean hostIsIpAddress(String host){ for (char c : host.toCharArray()) { if (c != '.' && (c < '0' || c > '9')) { return false; @@ -79,7 +81,6 @@ public class ProxyProviderFactory { public ProxyProvider provide() { return provide(autoDetectHosts); } - public ProxyProvider provide(String domain, int port) { List additionalCachedHotels = Cacher.getList(HOTELS_CACHE_KEY); if (additionalCachedHotels == null) { @@ -92,12 +93,12 @@ public class ProxyProviderFactory { if (hostIsIpAddress(domain)) { if (OSValidator.isWindows()) { - if (WindowsRawIpProxyProvider.isNoneConnected(domain) && + if (WindowsRawIpFlashProxyProvider.isNoneConnected(domain) && (!socksConfig.useSocks() || socksConfig.onlyUseIfNeeded()) ) { - return new WindowsRawIpProxyProvider(proxySetter, stateSetter, hConnection, domain, port, false); + return new WindowsRawIpFlashProxyProvider(proxySetter, stateSetter, hConnection, domain, port, false); } else if (socksConfig.useSocks()) { - return new WindowsRawIpProxyProvider(proxySetter, stateSetter, hConnection, domain, port, true); + return new WindowsRawIpFlashProxyProvider(proxySetter, stateSetter, hConnection, domain, port, true); } Platform.runLater(() -> { @@ -112,7 +113,7 @@ public class ProxyProviderFactory { return null; } else if (OSValidator.isUnix() || OSValidator.isMac()) { - return new LinuxRawIpProxyProvider(proxySetter, stateSetter, hConnection, domain, port, socksConfig.useSocks() && !socksConfig.onlyUseIfNeeded()); + return new LinuxRawIpFlashProxyProvider(proxySetter, stateSetter, hConnection, domain, port, socksConfig.useSocks() && !socksConfig.onlyUseIfNeeded()); } return null; @@ -123,9 +124,8 @@ public class ProxyProviderFactory { return provide(potentialHost); } } - private ProxyProvider provide(List potentialHosts) { - return new NormalProxyProvider(proxySetter, stateSetter, hConnection, potentialHosts, socksConfig.useSocks() && !socksConfig.onlyUseIfNeeded()); + return new NormalFlashProxyProvider(proxySetter, stateSetter, hConnection, potentialHosts, socksConfig.useSocks() && !socksConfig.onlyUseIfNeeded()); } public static void setSocksConfig(SocksConfiguration configuration) { diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/FlashProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/FlashProxyProvider.java new file mode 100644 index 0000000..5e61dd8 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/FlashProxyProvider.java @@ -0,0 +1,130 @@ +package gearth.protocol.connection.proxy.flash; + +import gearth.protocol.HConnection; +import gearth.protocol.connection.HProxy; +import gearth.protocol.connection.HProxySetter; +import gearth.protocol.connection.HState; +import gearth.protocol.connection.HStateSetter; +import gearth.protocol.connection.proxy.ProxyProvider; +import gearth.protocol.memory.Rc4Obtainer; +import gearth.protocol.packethandler.flash.IncomingFlashPacketHandler; +import gearth.protocol.packethandler.flash.OutgoingFlashPacketHandler; +import gearth.protocol.packethandler.flash.FlashPacketHandler; +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.layout.Region; + +import java.io.IOException; +import java.net.Socket; +import java.util.Arrays; +import java.util.concurrent.Semaphore; + +public abstract class FlashProxyProvider implements ProxyProvider { + + protected final HProxySetter proxySetter; + protected final HStateSetter stateSetter; + protected final HConnection hConnection; + + private Semaphore abortSemaphore = null; + + public FlashProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection){ + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.hConnection = hConnection; + } + + protected void startProxyThread(Socket client, Socket server, HProxy proxy) throws IOException, InterruptedException { + final boolean[] datastream = new boolean[1]; + server.setTcpNoDelay(true); + client.setTcpNoDelay(true); + + client.setSoTimeout(0); + server.setSoTimeout(0); + + if (HConnection.DEBUG) System.out.println(server.getLocalAddress().getHostAddress() + ": " + server.getLocalPort()); + Rc4Obtainer rc4Obtainer = new Rc4Obtainer(hConnection); + + OutgoingFlashPacketHandler outgoingHandler = new OutgoingFlashPacketHandler(server.getOutputStream(), hConnection.getTrafficObservables(), hConnection.getExtensionHandler()); + IncomingFlashPacketHandler incomingHandler = new IncomingFlashPacketHandler(client.getOutputStream(), hConnection.getTrafficObservables(), outgoingHandler, hConnection.getExtensionHandler()); + rc4Obtainer.setFlashPacketHandlers(outgoingHandler, incomingHandler); + + Semaphore abort = new Semaphore(0); + + outgoingHandler.addOnDatastreamConfirmedListener(hotelVersion -> { + incomingHandler.setAsDataStream(); + proxy.verifyProxy(incomingHandler, outgoingHandler, hotelVersion); + proxySetter.setProxy(proxy); + datastream[0] = true; + abortSemaphore = abort; + onConnect(); + }); + + handleInputStream(client, outgoingHandler, abort); + handleInputStream(server, incomingHandler, abort); + + // abort can be acquired as soon as one of the sockets is closed + abort.acquire(); + try { + if (!server.isClosed()) server.close(); + if (!client.isClosed()) client.close(); + if (HConnection.DEBUG) System.out.println("STOP"); + if (datastream[0]) { + onConnectEnd(); + }; + } + catch (IOException e) { + e.printStackTrace(); + } + } + + private void handleInputStream(Socket socket, FlashPacketHandler flashPacketHandler, Semaphore abort) { + new Thread(() -> { + try { + int readLength; + byte[] buffer = new byte[10000]; + while (!socket.isClosed() && + (hConnection.getState() == HState.WAITING_FOR_CLIENT || hConnection.getState() == HState.CONNECTED) && + (readLength = socket.getInputStream().read(buffer)) != -1) { + flashPacketHandler.act(Arrays.copyOf(buffer, readLength)); + } + } + catch (IOException ignore) { +// System.err.println(packetHandler instanceof IncomingPacketHandler ? "incoming" : "outgoing"); +// ignore.printStackTrace(); + } finally { + abort.release(); + } + }).start(); + } + + + public abstract void start() throws IOException; + public void abort() { + if (abortSemaphore != null) { + abortSemaphore.release(); + } + else { + stateSetter.setState(HState.NOT_CONNECTED); + } + } + + protected void onConnect() { + stateSetter.setState(HState.CONNECTED); + } + protected void onConnectEnd() { + proxySetter.setProxy(null); + abortSemaphore = null; + stateSetter.setState(HState.NOT_CONNECTED); + } + + protected void showInvalidConnectionError() { + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.ERROR, "You entered invalid connection information, G-Earth could not connect", ButtonType.OK); + alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + alert.setResizable(false); + alert.show(); + }); + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/NormalProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/NormalFlashProxyProvider.java similarity index 92% rename from G-Earth/src/main/java/gearth/protocol/connection/proxy/NormalProxyProvider.java rename to G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/NormalFlashProxyProvider.java index 1d9499d..5c68f08 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/NormalProxyProvider.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/NormalFlashProxyProvider.java @@ -1,21 +1,19 @@ -package gearth.protocol.connection.proxy; +package gearth.protocol.connection.proxy.flash; import gearth.misc.Cacher; import gearth.protocol.HConnection; -import gearth.protocol.connection.HProxy; -import gearth.protocol.connection.HProxySetter; -import gearth.protocol.connection.HState; -import gearth.protocol.connection.HStateSetter; +import gearth.protocol.connection.*; +import gearth.protocol.connection.proxy.ProxyProviderFactory; +import gearth.protocol.connection.proxy.SocksConfiguration; import gearth.protocol.hostreplacer.hostsfile.HostReplacer; import gearth.protocol.hostreplacer.hostsfile.HostReplacerFactory; import java.io.IOException; import java.net.*; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -public class NormalProxyProvider extends ProxyProvider { +public class NormalFlashProxyProvider extends FlashProxyProvider { private List potentialHosts; @@ -29,7 +27,7 @@ public class NormalProxyProvider extends ProxyProvider { private boolean useSocks; - public NormalProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection, List potentialHosts, boolean useSocks) { + public NormalFlashProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection, List potentialHosts, boolean useSocks) { super(proxySetter, stateSetter, hConnection); this.potentialHosts = potentialHosts; this.useSocks = useSocks; @@ -72,7 +70,7 @@ public class NormalProxyProvider extends ProxyProvider { int intercept_port = port; String intercept_host = "127.0." + (c / 254) + "." + (1 + c % 254); - potentialProxies.add(new HProxy(input_dom, actual_dom, port, intercept_port, intercept_host)); + potentialProxies.add(new HProxy(HClient.FLASH, input_dom, actual_dom, port, intercept_port, intercept_host)); c++; } } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/unix/LinuxRawIpProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/unix/LinuxRawIpFlashProxyProvider.java similarity index 88% rename from G-Earth/src/main/java/gearth/protocol/connection/proxy/unix/LinuxRawIpProxyProvider.java rename to G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/unix/LinuxRawIpFlashProxyProvider.java index 23ac486..3b31f78 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/unix/LinuxRawIpProxyProvider.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/unix/LinuxRawIpFlashProxyProvider.java @@ -1,26 +1,19 @@ -package gearth.protocol.connection.proxy.unix; +package gearth.protocol.connection.proxy.flash.unix; import gearth.protocol.HConnection; -import gearth.protocol.connection.HProxy; -import gearth.protocol.connection.HProxySetter; -import gearth.protocol.connection.HState; -import gearth.protocol.connection.HStateSetter; -import gearth.protocol.connection.proxy.ProxyProvider; +import gearth.protocol.connection.*; +import gearth.protocol.connection.proxy.flash.FlashProxyProvider; import gearth.protocol.connection.proxy.ProxyProviderFactory; import gearth.protocol.connection.proxy.SocksConfiguration; import gearth.protocol.hostreplacer.ipmapping.IpMapper; import gearth.protocol.hostreplacer.ipmapping.IpMapperFactory; -import javafx.application.Platform; -import javafx.scene.control.Alert; -import javafx.scene.control.ButtonType; -import javafx.scene.layout.Region; import java.io.IOException; import java.net.*; import java.util.LinkedList; import java.util.Queue; -public class LinuxRawIpProxyProvider extends ProxyProvider { +public class LinuxRawIpFlashProxyProvider extends FlashProxyProvider { private volatile String input_host; private volatile int input_port; @@ -30,7 +23,7 @@ public class LinuxRawIpProxyProvider extends ProxyProvider { private boolean useSocks; - public LinuxRawIpProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection, String input_host, int input_port, boolean useSocks) { + public LinuxRawIpFlashProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection, String input_host, int input_port, boolean useSocks) { super(proxySetter, stateSetter, hConnection); this.input_host = input_host; this.input_port = input_port; @@ -50,7 +43,7 @@ public class LinuxRawIpProxyProvider extends ProxyProvider { new Thread(() -> { try { stateSetter.setState(HState.PREPARING); - proxy = new HProxy(input_host, input_host, input_port, input_port, "0.0.0.0"); + proxy = new HProxy(HClient.FLASH, input_host, input_host, input_port, input_port, "0.0.0.0"); maybeRemoveMapping(); diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/windows/WindowsRawIpProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/windows/WindowsRawIpFlashProxyProvider.java similarity index 84% rename from G-Earth/src/main/java/gearth/protocol/connection/proxy/windows/WindowsRawIpProxyProvider.java rename to G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/windows/WindowsRawIpFlashProxyProvider.java index 9d28d5d..2f5b748 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/windows/WindowsRawIpProxyProvider.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/windows/WindowsRawIpFlashProxyProvider.java @@ -1,35 +1,23 @@ -package gearth.protocol.connection.proxy.windows; +package gearth.protocol.connection.proxy.flash.windows; import gearth.misc.Cacher; import gearth.protocol.HConnection; -import gearth.protocol.connection.HProxy; import gearth.protocol.connection.HProxySetter; import gearth.protocol.connection.HState; import gearth.protocol.connection.HStateSetter; -import gearth.protocol.connection.proxy.ProxyProvider; -import gearth.protocol.connection.proxy.unix.LinuxRawIpProxyProvider; -import gearth.protocol.hostreplacer.ipmapping.IpMapper; -import gearth.protocol.hostreplacer.ipmapping.IpMapperFactory; -import javafx.application.Platform; -import javafx.scene.control.Alert; -import javafx.scene.control.ButtonType; -import javafx.scene.layout.Region; +import gearth.protocol.connection.proxy.flash.unix.LinuxRawIpFlashProxyProvider; import org.json.JSONObject; -import java.io.IOException; import java.math.BigInteger; -import java.net.*; -import java.util.LinkedList; -import java.util.Queue; import java.util.UUID; // windows raw ip proxy provider extends the Linux one with the exception that it does not want to close // the IP redirect on connect -public class WindowsRawIpProxyProvider extends LinuxRawIpProxyProvider { +public class WindowsRawIpFlashProxyProvider extends LinuxRawIpFlashProxyProvider { private boolean hasMapped = false; - public WindowsRawIpProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection, String input_host, int input_port, boolean useSocks) { + public WindowsRawIpFlashProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection, String input_host, int input_port, boolean useSocks) { super(proxySetter, stateSetter, hConnection, input_host, input_port, useSocks); } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/PortRequester.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/PortRequester.java new file mode 100644 index 0000000..18aac14 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/PortRequester.java @@ -0,0 +1,26 @@ +package gearth.protocol.connection.proxy.unity; + +import javafx.beans.InvalidationListener; +import org.eclipse.jetty.websocket.jsr356.annotations.JsrParamIdText; + +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; + +// tells which port the packethandler is running on +// server gets closed afterwards +@ServerEndpoint(value = "/portrequest") +public class PortRequester { + + private final int packetHandlerPort; + + public PortRequester(int port) { + this.packetHandlerPort = port; + } + + @OnOpen + public void onOpen(Session session) throws IOException { + session.getBasicRemote().sendText("port " + packetHandlerPort); + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/PortRequesterConfig.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/PortRequesterConfig.java new file mode 100644 index 0000000..90fbd20 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/PortRequesterConfig.java @@ -0,0 +1,17 @@ +package gearth.protocol.connection.proxy.unity; + +import javax.websocket.server.ServerEndpointConfig; + +public class PortRequesterConfig extends ServerEndpointConfig.Configurator { + + private final int packetHandlerPort; + + public PortRequesterConfig(int packetHandlerPort) { + this.packetHandlerPort = packetHandlerPort; + } + + public T getEndpointInstance(Class clazz) throws InstantiationException { + return (T)new PortRequester(packetHandlerPort); + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/UnityCommunicator.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/UnityCommunicator.java new file mode 100644 index 0000000..985239b --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/UnityCommunicator.java @@ -0,0 +1,92 @@ +package gearth.protocol.connection.proxy.unity; + +import gearth.protocol.HConnection; +import gearth.protocol.HMessage; +import gearth.protocol.HPacket; +import gearth.protocol.connection.*; +import gearth.protocol.connection.proxy.ProxyProvider; +import gearth.protocol.packethandler.unity.UnityPacketHandler; + +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +@ServerEndpoint(value = "/packethandler") +public class UnityCommunicator { + + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection hConnection; + private final ProxyProvider proxyProvider; + + private HProxy hProxy = null; + private String allowedSession = null; + private String revision = null; + + public UnityCommunicator(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection, ProxyProvider proxyProvider) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.hConnection = hConnection; + this.proxyProvider = proxyProvider; + } + + + @OnOpen + public void onOpen(Session session) throws IOException { + session.setMaxBinaryMessageBufferSize(1024 * 1024 * 10); + } + + @OnMessage + public void onMessage(byte[] b, Session session) throws IOException { + if (allowedSession != null && !session.getId().equals(allowedSession)) { + return; + } + + if (revision == null) { + revision = new String(b, StandardCharsets.ISO_8859_1); + allowedSession = session.getId(); + return; + } + + + + byte[] packet = Arrays.copyOfRange(b, 1, b.length); + + if (hProxy == null && b[0] == 1) { + HPacket maybe = new HPacket(packet); + if (maybe.getBytesLength() > 6 && maybe.headerId() == 4000) { + hProxy = new HProxy(HClient.UNITY, "", "", -1, -1, ""); + hProxy.verifyProxy( + new UnityPacketHandler(hConnection.getExtensionHandler(), hConnection.getTrafficObservables(), session, HMessage.Direction.TOCLIENT), + new UnityPacketHandler(hConnection.getExtensionHandler(), hConnection.getTrafficObservables(), session, HMessage.Direction.TOSERVER), + revision + ); + proxySetter.setProxy(hProxy); + stateSetter.setState(HState.CONNECTED); + } + } + + + if (hProxy != null && b[0] == 0) { + hProxy.getInHandler().act(packet); + } + else if (hProxy != null && b[0] == 1) { + hProxy.getOutHandler().act(packet); + } + else { + proxyProvider.abort(); + } + } + + @OnClose + public void onClose(Session session) throws IOException { + proxyProvider.abort(); + } + + @OnError + public void onError(Session session, Throwable throwable) { + proxyProvider.abort(); + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/UnityCommunicatorConfig.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/UnityCommunicatorConfig.java new file mode 100644 index 0000000..f007fe6 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/UnityCommunicatorConfig.java @@ -0,0 +1,27 @@ +package gearth.protocol.connection.proxy.unity; + +import gearth.protocol.HConnection; +import gearth.protocol.connection.HProxySetter; +import gearth.protocol.connection.HStateSetter; +import gearth.protocol.connection.proxy.ProxyProvider; + +import javax.websocket.server.ServerEndpointConfig; + +public class UnityCommunicatorConfig extends ServerEndpointConfig.Configurator { + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection hConnection; + private final ProxyProvider proxyProvider; + + public UnityCommunicatorConfig(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection, ProxyProvider proxyProvider) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.hConnection = hConnection; + this.proxyProvider = proxyProvider; + } + + + public T getEndpointInstance(Class clazz) throws InstantiationException { + return (T)new UnityCommunicator(proxySetter, stateSetter, hConnection, proxyProvider); + } +} \ No newline at end of file diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/UnityProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/UnityProxyProvider.java new file mode 100644 index 0000000..58308db --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/unity/UnityProxyProvider.java @@ -0,0 +1,168 @@ +package gearth.protocol.connection.proxy.unity; + +import gearth.protocol.HConnection; +import gearth.protocol.StateChangeListener; +import gearth.protocol.connection.HProxySetter; +import gearth.protocol.connection.HState; +import gearth.protocol.connection.HStateSetter; +import gearth.protocol.connection.proxy.ProxyProvider; +import gearth.services.unity_tools.GUnityFileServer; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.HandlerList; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; + +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; +import java.io.IOException; + +import static gearth.services.unity_tools.GUnityFileServer.FILESERVER_PORT; + +public class UnityProxyProvider implements ProxyProvider { + + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection hConnection; + + private Server packetHandlerServer = null; + + public UnityProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.hConnection = hConnection; + } + + + @Override + public void start() throws IOException { + // https://happyhyppo.ro/2016/03/21/minimal-websockets-communication-with-javajetty-and-angularjs/ + + try { + int port = 9040; + boolean fail = true; + while (fail && port < 9100) { + try { + packetHandlerServer = new Server(port); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/ws"); + + HandlerList handlers = new HandlerList(); + handlers.setHandlers(new Handler[] { context }); + + ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context); + wscontainer.addEndpoint(ServerEndpointConfig.Builder + .create(UnityCommunicator.class, "/packethandler") // the endpoint url + .configurator(new UnityCommunicatorConfig(proxySetter, stateSetter, hConnection, this)) + .build()); + packetHandlerServer.setHandler(handlers); + packetHandlerServer.start(); + fail = false; + } + catch (Exception e) { + port++; + } + } + + if (fail) { + throw new Exception(); + } + + + startPortRequestServer(port); + startUnityFileServer(); + stateSetter.setState(HState.WAITING_FOR_CLIENT); + + } catch (Exception e) { + stateSetter.setState(HState.NOT_CONNECTED); + try { + packetHandlerServer.stop(); + } catch (Exception ex) { + ex.printStackTrace(); + } + e.printStackTrace(); + } + } + + @Override + public synchronized void abort() { + if (packetHandlerServer == null) { + return; + } + + final Server abortThis = packetHandlerServer; + stateSetter.setState(HState.ABORTING); + new Thread(() -> { + try { + abortThis.stop(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + stateSetter.setState(HState.NOT_CONNECTED); + } + }).start(); + packetHandlerServer = null; + } + + private void startUnityFileServer() throws Exception { + Server server = new Server(FILESERVER_PORT); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + + context.addServlet(new ServletHolder(new GUnityFileServer()), "/*"); + + HandlerList handlers = new HandlerList(); + handlers.setHandlers(new Handler[] { context }); + server.setHandler(handlers); + + server.start(); + StateChangeListener fileServerCloser = new StateChangeListener() { + @Override + public void stateChanged(HState oldState, HState newState) { + if (oldState == HState.WAITING_FOR_CLIENT || newState == HState.NOT_CONNECTED) { + hConnection.getStateObservable().removeListener(this); + try { + server.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }; + hConnection.getStateObservable().addListener(fileServerCloser); + } + + private void startPortRequestServer(int packetHandlerPort) throws Exception { + Server portRequestServer = new Server(9039); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/ws"); + + HandlerList handlers = new HandlerList(); + handlers.setHandlers(new Handler[] { context }); + portRequestServer.setHandler(handlers); + + ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context); + wscontainer.addEndpoint(ServerEndpointConfig.Builder + .create(PortRequester.class, "/portrequest") // the endpoint url + .configurator(new PortRequesterConfig(packetHandlerPort)) + .build()); + + portRequestServer.start(); + + StateChangeListener portRequesterCloser = new StateChangeListener() { + @Override + public void stateChanged(HState oldState, HState newState) { + if (oldState == HState.WAITING_FOR_CLIENT || newState == HState.NOT_CONNECTED) { + hConnection.getStateObservable().removeListener(this); + try { + portRequestServer.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }; + hConnection.getStateObservable().addListener(portRequesterCloser); + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/memory/Rc4Obtainer.java b/G-Earth/src/main/java/gearth/protocol/memory/Rc4Obtainer.java index d8fa9f1..69f8c3a 100644 --- a/G-Earth/src/main/java/gearth/protocol/memory/Rc4Obtainer.java +++ b/G-Earth/src/main/java/gearth/protocol/memory/Rc4Obtainer.java @@ -7,7 +7,9 @@ import gearth.protocol.HPacket; import gearth.protocol.crypto.RC4; import gearth.protocol.memory.habboclient.HabboClient; import gearth.protocol.memory.habboclient.HabboClientFactory; -import gearth.protocol.packethandler.*; +import gearth.protocol.packethandler.flash.BufferChangeListener; +import gearth.protocol.packethandler.flash.FlashPacketHandler; +import gearth.protocol.packethandler.PayloadBuffer; import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; @@ -25,17 +27,17 @@ public class Rc4Obtainer { public static final boolean DEBUG = false; private HabboClient client; - private List packetHandlers; + private List flashPacketHandlers; public Rc4Obtainer(HConnection hConnection) { client = HabboClientFactory.get(hConnection); } - public void setPacketHandlers(PacketHandler... packetHandlers) { - this.packetHandlers = Arrays.asList(packetHandlers); + public void setFlashPacketHandlers(FlashPacketHandler... flashPacketHandlers) { + this.flashPacketHandlers = Arrays.asList(flashPacketHandlers); - for (PacketHandler handler : packetHandlers) { + for (FlashPacketHandler handler : flashPacketHandlers) { BufferChangeListener bufferChangeListener = new BufferChangeListener() { @Override public void act() { @@ -53,10 +55,10 @@ public class Rc4Obtainer { - private void onSendFirstEncryptedMessage(PacketHandler packetHandler) { + private void onSendFirstEncryptedMessage(FlashPacketHandler flashPacketHandler) { if (!HConnection.DECRYPTPACKETS) return; - packetHandlers.forEach(PacketHandler::block); + flashPacketHandlers.forEach(FlashPacketHandler::block); new Thread(() -> { @@ -67,8 +69,8 @@ public class Rc4Obtainer { int i = 0; while (!worked && i < 4) { worked = (i % 2 == 0) ? - onSendFirstEncryptedMessage(packetHandler, client.getRC4cached()) : - onSendFirstEncryptedMessage(packetHandler, client.getRC4possibilities()); + onSendFirstEncryptedMessage(flashPacketHandler, client.getRC4cached()) : + onSendFirstEncryptedMessage(flashPacketHandler, client.getRC4possibilities()); i++; } @@ -107,16 +109,16 @@ public class Rc4Obtainer { System.out.println("Cracked RC4 in " + (endTime - startTime) + "ms"); } - packetHandlers.forEach(PacketHandler::unblock); + flashPacketHandlers.forEach(FlashPacketHandler::unblock); }).start(); } - private boolean onSendFirstEncryptedMessage(PacketHandler packetHandler, List potentialRC4tables) { + private boolean onSendFirstEncryptedMessage(FlashPacketHandler flashPacketHandler, List potentialRC4tables) { for (byte[] possible : potentialRC4tables) { - byte[] encBuffer = new byte[packetHandler.getEncryptedBuffer().size()]; + byte[] encBuffer = new byte[flashPacketHandler.getEncryptedBuffer().size()]; for (int i = 0; i < encBuffer.length; i++) { - encBuffer[i] = packetHandler.getEncryptedBuffer().get(i); + encBuffer[i] = flashPacketHandler.getEncryptedBuffer().get(i); } for (int i = 0; i < 256; i++) { @@ -124,7 +126,7 @@ public class Rc4Obtainer { byte[] keycpy = Arrays.copyOf(possible, possible.length); RC4 rc4Tryout = new RC4(keycpy, i, j); - if (packetHandler.getMessageSide() == HMessage.Direction.TOSERVER) rc4Tryout.undoRc4(encBuffer); + if (flashPacketHandler.getMessageSide() == HMessage.Direction.TOSERVER) rc4Tryout.undoRc4(encBuffer); if (rc4Tryout.couldBeFresh()) { byte[] encDataCopy = Arrays.copyOf(encBuffer, encBuffer.length); RC4 rc4TryCopy = rc4Tryout.deepCopy(); @@ -135,7 +137,7 @@ public class Rc4Obtainer { HPacket[] checker = payloadBuffer.pushAndReceive(decoded); if (payloadBuffer.peak().length == 0) { - packetHandler.setRc4(rc4Tryout); + flashPacketHandler.setRc4(rc4Tryout); return true; } diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/ByteArrayUtils.java b/G-Earth/src/main/java/gearth/protocol/packethandler/ByteArrayUtils.java new file mode 100644 index 0000000..22ea13b --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/ByteArrayUtils.java @@ -0,0 +1,12 @@ +package gearth.protocol.packethandler; + +public class ByteArrayUtils { + + public static byte[] combineByteArrays(byte[] arr1, byte[] arr2) { + byte[] combined = new byte[arr1.length + arr2.length]; + System.arraycopy(arr1,0,combined,0 ,arr1.length); + System.arraycopy(arr2,0,combined,arr1.length,arr2.length); + return combined; + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/PacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/PacketHandler.java index 6151c3b..01c5552 100644 --- a/G-Earth/src/main/java/gearth/protocol/packethandler/PacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/PacketHandler.java @@ -1,125 +1,32 @@ package gearth.protocol.packethandler; import gearth.misc.listenerpattern.Observable; -import gearth.protocol.HConnection; import gearth.protocol.HMessage; -import gearth.protocol.HPacket; import gearth.protocol.TrafficListener; -import gearth.protocol.crypto.RC4; import gearth.services.extensionhandler.ExtensionHandler; -import gearth.services.extensionhandler.OnHMessageHandled; import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; public abstract class PacketHandler { - protected static final boolean DEBUG = false; + protected final ExtensionHandler extensionHandler; + private final Object[] trafficObservables; //get notified on packet send + protected final PayloadBuffer payloadBuffer; + protected volatile int currentIndex = 0; + protected final Object sendLock = new Object(); - private volatile PayloadBuffer payloadBuffer = new PayloadBuffer(); - private volatile OutputStream out; - private volatile ExtensionHandler extensionHandler; - private volatile Object[] trafficObservables; //get notified on packet send - private volatile boolean isTempBlocked = false; - volatile boolean isDataStream = false; - private volatile int currentIndex = 0; - - private final Object manipulationLock = new Object(); - private final Object sendLock = new Object(); - - private RC4 decryptcipher = null; - private RC4 encryptcipher = null; - - private volatile List tempEncryptedBuffer = new ArrayList<>(); - volatile boolean isEncryptedStream = false; - - - PacketHandler(OutputStream outputStream, Object[] trafficObservables, ExtensionHandler extensionHandler) { - this.trafficObservables = trafficObservables; + protected PacketHandler(ExtensionHandler extensionHandler, Object[] trafficObservables) { this.extensionHandler = extensionHandler; - out = outputStream; - } - - public boolean isDataStream() {return isDataStream;} - public void setAsDataStream() { - isDataStream = true; - } - - public boolean isEncryptedStream() { - return isEncryptedStream; - } - - public void act(byte[] buffer) throws IOException { - if (!isDataStream) { - out.write(buffer); - return; - } - - bufferChangeObservable.fireEvent(); - - if (!isEncryptedStream) { - payloadBuffer.push(buffer); - } - else if (!HConnection.DECRYPTPACKETS) { - synchronized (sendLock) { - out.write(buffer); - } - } - else if (decryptcipher == null) { - for (int i = 0; i < buffer.length; i++) { - tempEncryptedBuffer.add(buffer[i]); - } - } - else { - byte[] tm = decryptcipher.rc4(buffer); - if (DEBUG) { - printForDebugging(tm); - } - payloadBuffer.push(tm); - } - - if (!isTempBlocked) { - flush(); - } + this.trafficObservables = trafficObservables; + this.payloadBuffer = new PayloadBuffer(); } - public void setRc4(RC4 rc4) { - this.decryptcipher = rc4.deepCopy(); - this.encryptcipher = rc4.deepCopy(); + public abstract void sendToStream(byte[] buffer); - byte[] encrbuffer = new byte[tempEncryptedBuffer.size()]; - for (int i = 0; i < tempEncryptedBuffer.size(); i++) { - encrbuffer[i] = tempEncryptedBuffer.get(i); - } + public abstract void act(byte[] buffer) throws IOException; - try { - act(encrbuffer); - } catch (IOException e) { - e.printStackTrace(); - } - tempEncryptedBuffer = null; - } - - public void block() { - isTempBlocked = true; - } - public void unblock() { - try { - flush(); - } catch (IOException e) { - e.printStackTrace(); - } - isTempBlocked = false; - } - - /** - * LISTENERS CAN EDIT THE MESSAGE BEFORE BEING SENT - * @param message - */ - private void notifyListeners(int i, HMessage message) { + protected void notifyListeners(int i, HMessage message) { ((Observable) trafficObservables[i]).fireEvent(trafficListener -> { message.getPacket().resetReadIndex(); trafficListener.onCapture(message); @@ -127,72 +34,4 @@ public abstract class PacketHandler { message.getPacket().resetReadIndex(); } - public void sendToStream(byte[] buffer) { - synchronized (sendLock) { - try { - out.write( - (!isEncryptedStream) - ? buffer - : encryptcipher.rc4(buffer) - ); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public void flush() throws IOException { - synchronized (manipulationLock) { - HPacket[] hpackets = payloadBuffer.receive(); - - for (HPacket hpacket : hpackets){ - HMessage hMessage = new HMessage(hpacket, getMessageSide(), currentIndex); - boolean isencrypted = isEncryptedStream; - - OnHMessageHandled afterExtensionIntercept = hMessage1 -> { - if (isDataStream) { - notifyListeners(2, hMessage1); - } - - if (!hMessage1.isBlocked()) { - synchronized (sendLock) { - out.write( - (!isencrypted) - ? hMessage1.getPacket().toBytes() - : encryptcipher.rc4(hMessage1.getPacket().toBytes()) - ); - } - } - }; - - if (isDataStream) { - notifyListeners(0, hMessage); - notifyListeners(1, hMessage); - extensionHandler.handle(hMessage, afterExtensionIntercept); - } - else { - afterExtensionIntercept.finished(hMessage); - } - - currentIndex++; - } - } - } - - public abstract HMessage.Direction getMessageSide(); - - public List getEncryptedBuffer() { - return tempEncryptedBuffer; - } - - protected abstract void printForDebugging(byte[] bytes); - - private Observable bufferChangeObservable = new Observable<>(BufferChangeListener::act); - public Observable getBufferChangeObservable() { - return bufferChangeObservable; - } - - public int getCurrentIndex() { - return currentIndex; - } } diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/PayloadBuffer.java b/G-Earth/src/main/java/gearth/protocol/packethandler/PayloadBuffer.java index ceb1d92..36c7045 100644 --- a/G-Earth/src/main/java/gearth/protocol/packethandler/PayloadBuffer.java +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/PayloadBuffer.java @@ -14,7 +14,7 @@ public class PayloadBuffer { return receive(); } public void push(byte[] tcpData) { - buffer = buffer.length == 0 ? tcpData.clone() : combineByteArrays(buffer, tcpData); + buffer = buffer.length == 0 ? tcpData.clone() : ByteArrayUtils.combineByteArrays(buffer, tcpData); } public HPacket[] receive() { if (buffer.length < 6) return new HPacket[0]; @@ -30,14 +30,6 @@ public class PayloadBuffer { } - private byte[] combineByteArrays(byte[] arr1, byte[] arr2) { - byte[] combined = new byte[arr1.length + arr2.length]; - System.arraycopy(arr1,0,combined,0 ,arr1.length); - System.arraycopy(arr2,0,combined,arr1.length,arr2.length); - return combined; - } - - public byte[] peak() { return buffer; } diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/BufferChangeListener.java b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/BufferChangeListener.java similarity index 57% rename from G-Earth/src/main/java/gearth/protocol/packethandler/BufferChangeListener.java rename to G-Earth/src/main/java/gearth/protocol/packethandler/flash/BufferChangeListener.java index d004cb8..e39d6e7 100644 --- a/G-Earth/src/main/java/gearth/protocol/packethandler/BufferChangeListener.java +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/BufferChangeListener.java @@ -1,4 +1,4 @@ -package gearth.protocol.packethandler; +package gearth.protocol.packethandler.flash; public interface BufferChangeListener { diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashPacketHandler.java new file mode 100644 index 0000000..b894343 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashPacketHandler.java @@ -0,0 +1,182 @@ +package gearth.protocol.packethandler.flash; + +import gearth.misc.listenerpattern.Observable; +import gearth.protocol.HConnection; +import gearth.protocol.HMessage; +import gearth.protocol.HPacket; +import gearth.protocol.TrafficListener; +import gearth.protocol.crypto.RC4; +import gearth.protocol.packethandler.PacketHandler; +import gearth.protocol.packethandler.PayloadBuffer; +import gearth.services.extensionhandler.ExtensionHandler; +import gearth.services.extensionhandler.OnHMessageHandled; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +public abstract class FlashPacketHandler extends PacketHandler { + + protected static final boolean DEBUG = false; + + private volatile OutputStream out; + private volatile boolean isTempBlocked = false; + volatile boolean isDataStream = false; + + private final Object manipulationLock = new Object(); + + private RC4 decryptcipher = null; + private RC4 encryptcipher = null; + + private volatile List tempEncryptedBuffer = new ArrayList<>(); + volatile boolean isEncryptedStream = false; + + + FlashPacketHandler(OutputStream outputStream, Object[] trafficObservables, ExtensionHandler extensionHandler) { + super(extensionHandler, trafficObservables); + out = outputStream; + } + + public boolean isDataStream() {return isDataStream;} + public void setAsDataStream() { + isDataStream = true; + } + + public boolean isEncryptedStream() { + return isEncryptedStream; + } + + public void act(byte[] buffer) throws IOException { + if (!isDataStream) { + out.write(buffer); + return; + } + + bufferChangeObservable.fireEvent(); + + if (!isEncryptedStream) { + payloadBuffer.push(buffer); + } + else if (!HConnection.DECRYPTPACKETS) { + synchronized (sendLock) { + out.write(buffer); + } + } + else if (decryptcipher == null) { + for (int i = 0; i < buffer.length; i++) { + tempEncryptedBuffer.add(buffer[i]); + } + } + else { + byte[] tm = decryptcipher.rc4(buffer); + if (DEBUG) { + printForDebugging(tm); + } + payloadBuffer.push(tm); + } + + if (!isTempBlocked) { + flush(); + } + } + + + public void setRc4(RC4 rc4) { + this.decryptcipher = rc4.deepCopy(); + this.encryptcipher = rc4.deepCopy(); + + byte[] encrbuffer = new byte[tempEncryptedBuffer.size()]; + for (int i = 0; i < tempEncryptedBuffer.size(); i++) { + encrbuffer[i] = tempEncryptedBuffer.get(i); + } + + try { + act(encrbuffer); + } catch (IOException e) { + e.printStackTrace(); + } + tempEncryptedBuffer = null; + } + + public void block() { + isTempBlocked = true; + } + public void unblock() { + try { + flush(); + } catch (IOException e) { + e.printStackTrace(); + } + isTempBlocked = false; + } + + public void sendToStream(byte[] buffer) { + synchronized (sendLock) { + try { + out.write( + (!isEncryptedStream) + ? buffer + : encryptcipher.rc4(buffer) + ); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public void flush() throws IOException { + synchronized (manipulationLock) { + HPacket[] hpackets = payloadBuffer.receive(); + + for (HPacket hpacket : hpackets){ + HMessage hMessage = new HMessage(hpacket, getMessageSide(), currentIndex); + boolean isencrypted = isEncryptedStream; + + OnHMessageHandled afterExtensionIntercept = hMessage1 -> { + if (isDataStream) { + notifyListeners(2, hMessage1); + } + + if (!hMessage1.isBlocked()) { + synchronized (sendLock) { + out.write( + (!isencrypted) + ? hMessage1.getPacket().toBytes() + : encryptcipher.rc4(hMessage1.getPacket().toBytes()) + ); + } + } + }; + + if (isDataStream) { + notifyListeners(0, hMessage); + notifyListeners(1, hMessage); + extensionHandler.handle(hMessage, afterExtensionIntercept); + } + else { + afterExtensionIntercept.finished(hMessage); + } + + currentIndex++; + } + } + } + + public abstract HMessage.Direction getMessageSide(); + + public List getEncryptedBuffer() { + return tempEncryptedBuffer; + } + + protected abstract void printForDebugging(byte[] bytes); + + private Observable bufferChangeObservable = new Observable<>(BufferChangeListener::act); + public Observable getBufferChangeObservable() { + return bufferChangeObservable; + } + + public int getCurrentIndex() { + return currentIndex; + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/IncomingPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/IncomingFlashPacketHandler.java similarity index 84% rename from G-Earth/src/main/java/gearth/protocol/packethandler/IncomingPacketHandler.java rename to G-Earth/src/main/java/gearth/protocol/packethandler/flash/IncomingFlashPacketHandler.java index f968241..a7819aa 100644 --- a/G-Earth/src/main/java/gearth/protocol/packethandler/IncomingPacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/IncomingFlashPacketHandler.java @@ -1,4 +1,4 @@ -package gearth.protocol.packethandler; +package gearth.protocol.packethandler.flash; import gearth.misc.listenerpattern.Observable; import gearth.protocol.HMessage; @@ -6,13 +6,11 @@ import gearth.protocol.HPacket; import gearth.protocol.TrafficListener; import gearth.services.extensionhandler.ExtensionHandler; -import java.io.IOException; import java.io.OutputStream; -import java.util.List; -public class IncomingPacketHandler extends PacketHandler { +public class IncomingFlashPacketHandler extends FlashPacketHandler { - public IncomingPacketHandler(OutputStream outputStream, Object[] trafficObservables, OutgoingPacketHandler outgoingHandler, ExtensionHandler extensionHandler) { + public IncomingFlashPacketHandler(OutputStream outputStream, Object[] trafficObservables, OutgoingFlashPacketHandler outgoingHandler, ExtensionHandler extensionHandler) { super(outputStream, trafficObservables, extensionHandler); TrafficListener listener = new TrafficListener() { diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/OnDatastreamConfirmedListener.java b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/OnDatastreamConfirmedListener.java similarity index 67% rename from G-Earth/src/main/java/gearth/protocol/packethandler/OnDatastreamConfirmedListener.java rename to G-Earth/src/main/java/gearth/protocol/packethandler/flash/OnDatastreamConfirmedListener.java index fd6dabc..4b09343 100644 --- a/G-Earth/src/main/java/gearth/protocol/packethandler/OnDatastreamConfirmedListener.java +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/OnDatastreamConfirmedListener.java @@ -1,4 +1,4 @@ -package gearth.protocol.packethandler; +package gearth.protocol.packethandler.flash; public interface OnDatastreamConfirmedListener { diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/OutgoingPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/OutgoingFlashPacketHandler.java similarity index 81% rename from G-Earth/src/main/java/gearth/protocol/packethandler/OutgoingPacketHandler.java rename to G-Earth/src/main/java/gearth/protocol/packethandler/flash/OutgoingFlashPacketHandler.java index 156eec6..26467f6 100644 --- a/G-Earth/src/main/java/gearth/protocol/packethandler/OutgoingPacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/OutgoingFlashPacketHandler.java @@ -1,4 +1,4 @@ -package gearth.protocol.packethandler; +package gearth.protocol.packethandler.flash; import gearth.misc.listenerpattern.Observable; import gearth.protocol.HMessage; @@ -7,13 +7,10 @@ import gearth.services.extensionhandler.ExtensionHandler; import java.io.IOException; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -public class OutgoingPacketHandler extends PacketHandler { +public class OutgoingFlashPacketHandler extends FlashPacketHandler { - public OutgoingPacketHandler(OutputStream outputStream, Object[] trafficObservables, ExtensionHandler extensionHandler) { + public OutgoingFlashPacketHandler(OutputStream outputStream, Object[] trafficObservables, ExtensionHandler extensionHandler) { super(outputStream, trafficObservables, extensionHandler); } diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/unity/UnityPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/unity/UnityPacketHandler.java new file mode 100644 index 0000000..5f3030d --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/unity/UnityPacketHandler.java @@ -0,0 +1,52 @@ +package gearth.protocol.packethandler.unity; + +import gearth.protocol.HMessage; +import gearth.protocol.HPacket; +import gearth.protocol.packethandler.ByteArrayUtils; +import gearth.protocol.packethandler.PacketHandler; +import gearth.protocol.packethandler.PayloadBuffer; +import gearth.services.extensionhandler.ExtensionHandler; +import gearth.services.extensionhandler.OnHMessageHandled; + +import javax.websocket.Session; +import java.io.IOException; +import java.nio.ByteBuffer; + +public class UnityPacketHandler extends PacketHandler { + + private final Session session; + private final HMessage.Direction direction; + + public UnityPacketHandler(ExtensionHandler extensionHandler, Object[] trafficObservables, Session session, HMessage.Direction direction) { + super(extensionHandler, trafficObservables); + this.session = session; + this.direction = direction; + } + + @Override + public void sendToStream(byte[] buffer) { + byte[] prefix = new byte[]{(direction == HMessage.Direction.TOCLIENT ? ((byte)0) : ((byte)1))}; + byte[] combined = ByteArrayUtils.combineByteArrays(prefix, buffer); + + session.getAsyncRemote().sendBinary(ByteBuffer.wrap(combined)); + } + + @Override + public void act(byte[] buffer) throws IOException { + HMessage hMessage = new HMessage(new HPacket(buffer), direction, currentIndex); + + OnHMessageHandled afterExtensionIntercept = hMessage1 -> { + notifyListeners(2, hMessage1); + + if (!hMessage1.isBlocked()) { + sendToStream(hMessage1.getPacket().toBytes()); + } + }; + + notifyListeners(0, hMessage); + notifyListeners(1, hMessage); + extensionHandler.handle(hMessage, afterExtensionIntercept); + + currentIndex++; + } +} diff --git a/G-Earth/src/main/java/gearth/services/Constants.java b/G-Earth/src/main/java/gearth/services/Constants.java new file mode 100644 index 0000000..6370c65 --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/Constants.java @@ -0,0 +1,7 @@ +package gearth.services; + +public class Constants { + + public static volatile boolean UNITY_PACKETS = false; + +} diff --git a/G-Earth/src/main/java/gearth/services/extensionhandler/ExtensionHandler.java b/G-Earth/src/main/java/gearth/services/extensionhandler/ExtensionHandler.java index 4296bb7..a7253ee 100644 --- a/G-Earth/src/main/java/gearth/services/extensionhandler/ExtensionHandler.java +++ b/G-Earth/src/main/java/gearth/services/extensionhandler/ExtensionHandler.java @@ -212,6 +212,27 @@ public class ExtensionHandler { protected void manipulatedPacket(HMessage hMessage) { onExtensionRespond(extension, hMessage); } + + @Override + protected void packetToStringRequest(HPacket packet) { + String s = ""; + String expression = ""; + try { + s = packet.toString(); + if (packet.length() < 3000) { + expression = packet.toExpression(); + } + } + finally { + extension.packetToStringResponse(s, expression); + } + } + + @Override + protected void stringToPacketRequest(String string) { + HPacket packet = new HPacket(string); + extension.stringToPacketResponse(packet); + } }; extension.getExtensionObservable().addListener(listener); diff --git a/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/ExtensionListener.java b/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/ExtensionListener.java index f7120ca..080c7c9 100644 --- a/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/ExtensionListener.java +++ b/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/ExtensionListener.java @@ -12,4 +12,7 @@ public abstract class ExtensionListener { protected void log(String text) {} protected void hasClosed() {} + protected void packetToStringRequest(HPacket packet) {} + protected void stringToPacketRequest(String string) {} + } diff --git a/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/GEarthExtension.java b/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/GEarthExtension.java index c38e428..61bfc32 100644 --- a/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/GEarthExtension.java +++ b/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/GEarthExtension.java @@ -8,6 +8,8 @@ import gearth.services.extensionhandler.extensions.listeners.OmRemoveClickListen import gearth.services.extensionhandler.extensions.listeners.OnClickListener; import gearth.services.extensionhandler.extensions.listeners.OnDeleteListener; +import java.util.function.Consumer; + public abstract class GEarthExtension { @@ -40,6 +42,8 @@ public abstract class GEarthExtension { public abstract void connectionEnd(); public abstract void init(); public abstract void close(); + public abstract void packetToStringResponse(String string, String expression); + public abstract void stringToPacketResponse(HPacket packet); // --------------------------------------------------------------- @@ -78,6 +82,19 @@ public abstract class GEarthExtension { protected void hasClosed() { extensionObservable.fireEvent(ExtensionListener::hasClosed); } + + protected void packetToStringRequest(HPacket packet) { + int orgIndex = packet.getReadIndex(); + extensionObservable.fireEvent(listener -> { + packet.setReadIndex(6); + listener.packetToStringRequest(packet); + }); + packet.setReadIndex(orgIndex); + } + protected void stringToPacketRequest(String string) { + extensionObservable.fireEvent(l -> l.stringToPacketRequest(string)); + } + // -------------------------------------------------------------------- diff --git a/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/network/NetworkExtension.java b/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/network/NetworkExtension.java index f40b790..209dc4f 100644 --- a/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/network/NetworkExtension.java +++ b/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/network/NetworkExtension.java @@ -8,6 +8,7 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.net.Socket; +import java.nio.charset.StandardCharsets; /** * Created by Jonas on 21/06/18. @@ -88,6 +89,14 @@ public class NetworkExtension extends GEarthExtension { else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONCONSOLELOG) { log(message.readString()); } + else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.PACKETTOSTRING_REQUEST) { + HPacket p = new HPacket(new byte[0]); + p.constructFromString(message.readLongString()); + packetToStringRequest(p); + } + else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.STRINGTOPACKET_REQUEST) { + stringToPacketRequest(message.readLongString(StandardCharsets.UTF_8)); + } } @@ -211,4 +220,19 @@ public class NetworkExtension extends GEarthExtension { connection.close(); } catch (IOException ignored) { } } + + @Override + public void packetToStringResponse(String string, String expression) { + HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.PACKETTOSTRING_RESPONSE); + packet.appendLongString(string); + packet.appendLongString(expression, StandardCharsets.UTF_8); + sendMessage(packet); + } + + @Override + public void stringToPacketResponse(HPacket packetFromString) { + HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.STRINGTOPACKET_RESPONSE); + packet.appendLongString(packetFromString.stringify()); + sendMessage(packet); + } } \ No newline at end of file diff --git a/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/network/NetworkExtensionInfo.java b/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/network/NetworkExtensionInfo.java index 17d9f2e..9ea10e1 100644 --- a/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/network/NetworkExtensionInfo.java +++ b/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/network/NetworkExtensionInfo.java @@ -87,6 +87,9 @@ public class NetworkExtensionInfo { public static final int CONNECTIONSTART = 5; public static final int CONNECTIONEND = 6; public static final int INIT = 7; + + public static final int PACKETTOSTRING_RESPONSE = 20; + public static final int STRINGTOPACKET_RESPONSE = 21; } @@ -95,6 +98,10 @@ public class NetworkExtensionInfo { public static final int MANIPULATEDPACKET = 2; public static final int REQUESTFLAGS = 3; public static final int SENDMESSAGE = 4; + + public static final int PACKETTOSTRING_REQUEST = 20; + public static final int STRINGTOPACKET_REQUEST = 21; + public static final int EXTENSIONCONSOLELOG = 98; } diff --git a/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/network/authentication/Authenticator.java b/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/network/authentication/Authenticator.java index 82c5adf..76c4df2 100644 --- a/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/network/authentication/Authenticator.java +++ b/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/network/authentication/Authenticator.java @@ -6,9 +6,7 @@ import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; +import java.util.*; /** * Created by Jonas on 16/10/18. @@ -16,15 +14,24 @@ import java.util.Random; public class Authenticator { private static Map cookies = new HashMap<>(); + private static Set perma_cookies = new HashSet<>(); public static String generateCookieForExtension(String filename) { String cookie = getRandomCookie(); cookies.put(filename, cookie); - + return cookie; + } + public static String generatePermanentCookie() { + String cookie = getRandomCookie(); + perma_cookies.add(cookie); return cookie; } public static boolean evaluate(NetworkExtension extension) { + if (extension.getCookie() != null && perma_cookies.contains(extension.getCookie())) { + return true; + } + if (extension.isInstalledExtension()) { return claimSession(extension.getFileName(), extension.getCookie()); } diff --git a/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/simple/ExampleExtension.java b/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/simple/ExampleExtension.java index 98752f2..e04b043 100644 --- a/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/simple/ExampleExtension.java +++ b/G-Earth/src/main/java/gearth/services/extensionhandler/extensions/implementations/simple/ExampleExtension.java @@ -1,6 +1,7 @@ package gearth.services.extensionhandler.extensions.implementations.simple; import gearth.protocol.HMessage; +import gearth.protocol.HPacket; import gearth.services.extensionhandler.extensions.GEarthExtension; public class ExampleExtension extends GEarthExtension { @@ -105,4 +106,18 @@ public class ExampleExtension extends GEarthExtension { // finish up and call "hasClosed()" hasClosed(); } + + + + + // ignore these + @Override + public void packetToStringResponse(String string, String expression) { + + } + + @Override + public void stringToPacketResponse(HPacket packet) { + + } } diff --git a/G-Earth/src/main/java/gearth/services/gpython/GPythonShell.java b/G-Earth/src/main/java/gearth/services/gpython/GPythonShell.java new file mode 100644 index 0000000..734e1f2 --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/gpython/GPythonShell.java @@ -0,0 +1,205 @@ +package gearth.services.gpython; + +import gearth.Main; +import gearth.ui.extra.ExtraController; +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Region; + +import java.io.*; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class GPythonShell { + + private final String extensionName; + private final int port; + private final String cookie; + + public GPythonShell(String extensionName, int port, String cookie) { + this.extensionName = extensionName; + this.port = port; + this.cookie = cookie; + } + + public void launch(OnQtConsoleLaunch onLaunch) { + new Thread(() -> { + try { + // launch the jupyter console + ProcessBuilder gConsoleBuilder = new ProcessBuilder("python", "-m", "jupyter", "console", "--simple-prompt"); + Process gConsole = gConsoleBuilder.start(); + + InputStreamReader in = new InputStreamReader(gConsole.getInputStream()); + BufferedWriter out = new BufferedWriter(new OutputStreamWriter(gConsole.getOutputStream())); + + readUntilExpectInput(in); + + // obtain jupyter kernel name + List sysargs = enterCommandAndAwait(out, in, "import sys; sys.argv"); + String kernelName = extractKernelName(sysargs); + + InputStream initScriptResource = getClass().getResourceAsStream("init_script.py"); + List initScript = new BufferedReader(new InputStreamReader(initScriptResource, + StandardCharsets.UTF_8)).lines().collect(Collectors.toList()); + + for (String line : initScript) { + line = line.replace("$G_PYTHON_SHELL_TITLE$", extensionName) + .replace("$G_EARTH_PORT$", "" + port) + .replace("$COOKIE$", cookie); + enterCommandAndAwait(out, in, line); + } + + ProcessBuilder qtConsoleBuilder = new ProcessBuilder("python", "-m", "jupyter", "qtconsole", + "--ConsoleWidget.include_other_output", "True", + "--ConsoleWidget.gui_completion", "'droplist'", +// "--ConsoleWidget.other_output_prefix", "'[G-Earth]'", +// "--JupyterWidget.in_prompt", "'>>>: '", +// "--JupyterWidget.out_prompt", "''", + "--KernelManager.autorestart", "False", + "--JupyterConsoleApp.confirm_exit", "False", + "--JupyterConsoleApp.existing", kernelName); + Process qtConsole = qtConsoleBuilder.start(); + + new Thread(() -> { + try { + qtConsole.waitFor(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + try { + enterCommandAndAwait(out, in, "ext.stop()"); + enterCommand(out, "exit()"); + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + + onLaunch.launched(false); + } + catch (Exception e) { + e.printStackTrace(); + showError(); + onLaunch.launched(true); + } + }).start(); + } + + private List readUntilExpectInput(InputStreamReader in) throws IOException { + List readings = new ArrayList<>(); + StringBuilder builder = new StringBuilder(); + + int c; + while ((c = in.read()) != -1) { + char character = (char)c; + if (character == '\n') { + String reading = builder.toString(); + if (reading.endsWith("\r")) { + reading = reading.substring(0, reading.length() - 1); + } + if (reading.matches("Out\\[[0-9+]]: .*")) { + reading = reading.replaceAll("^Out\\[[0-9+]]: ", ""); + if (reading.equals("")) { + builder = new StringBuilder(); + continue; + } + } + + readings.add(reading); + builder = new StringBuilder(); + } + else { + builder.append(character); + + if (builder.toString().matches("In \\[[0-9]+]: ") && !in.ready()) { + return readings; + } + } + } + return null; + } + private void enterCommand(BufferedWriter out, String command) throws IOException { + out.write(command); + out.newLine(); + out.flush(); + } + private List enterCommandAndAwait(BufferedWriter out, InputStreamReader in, String command) throws IOException { + enterCommand(out, command); + return readUntilExpectInput(in); + } + + private String extractKernelName(List inputArgs) { + // null if not found + + String joined = String.join("", inputArgs); + String paramsFull = joined.replaceAll("^[^\\[]*\\[", "").replaceAll("][^]]*$", ""); + List params = new ArrayList<>(); + + boolean backslashed = false; + int beginParameterIndex = -1; + for (int i = 0; i < paramsFull.length(); i++) { + char c = paramsFull.charAt(i); + if (c == '\'' && !backslashed) { + if (beginParameterIndex == -1) { + beginParameterIndex = i + 1; + } + else { + params.add(paramsFull.substring(beginParameterIndex, i)); + beginParameterIndex = -1; + } + } + + backslashed = c == '\\' && !backslashed; + } + + for (int i = 0; i < params.size() - 1; i++) { + if (params.get(i).equals("-f")) { + return "'" + params.get(i+1) + "'"; + } + } + return null; + } + + private void showError() { + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.ERROR, "G-Python error", ButtonType.OK); + alert.setTitle("G-Python error"); + + FlowPane fp = new FlowPane(); + Label lbl = new Label("Something went wrong launching the G-Python shell," + + System.lineSeparator() + "are you sure you followed the installation guide correctly?" + + System.lineSeparator() + System.lineSeparator() + "More information here:"); + Hyperlink link = new Hyperlink(ExtraController.INFO_URL_GPYTHON); + fp.getChildren().addAll(lbl, link); + link.setOnAction(event -> { + Main.main.getHostServices().showDocument(link.getText()); + event.consume(); + }); + + alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + alert.getDialogPane().setContent(fp); + alert.show(); + }); + } + + public String getExtensionName() { + return extensionName; + } + + public static void main(String[] args) { + GPythonShell shell = new GPythonShell("test", 9092, "cookie"); + shell.launch((b) -> { + System.out.println("launched"); + }); + } + +} diff --git a/G-Earth/src/main/java/gearth/services/gpython/GPythonVersionUtils.java b/G-Earth/src/main/java/gearth/services/gpython/GPythonVersionUtils.java new file mode 100644 index 0000000..9d2f3bf --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/gpython/GPythonVersionUtils.java @@ -0,0 +1,96 @@ +package gearth.services.gpython; + +import org.apache.maven.artifact.versioning.ComparableVersion; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +public class GPythonVersionUtils { + + private static final String MIN_GPYTHON_VERSION = "0.1"; + private static final String MIN_PYTHON_VERSION = "3.2"; + + // returns null if python not installed + public static String pythonVersion() { + List commandOutput = executeCommand("python", "--version"); + if (commandOutput.size() == 0) { + return null; + } + String maybeVersion = commandOutput.get(0); + if (!maybeVersion.contains("Python")) { + return null; + } + + return maybeVersion.split(" ")[1]; + } + + public static boolean validInstallation() { + // validates if user has all dependencies installed + String pythonVersion = pythonVersion(); + if (pythonVersion == null) { + return false; + } + + ComparableVersion version = new ComparableVersion(pythonVersion); + if (version.compareTo(new ComparableVersion(MIN_PYTHON_VERSION)) < 0) { + return false; + } + + List allPackages = executeCommand("python", "-m", "pip", "list"); + allPackages = allPackages.subList(2, allPackages.size()); + + String qtconsole = getPackageVersion(allPackages, "qtconsole"); + String pyqt5 = getPackageVersion(allPackages, "pyqt5"); + String jupyterConsole = getPackageVersion(allPackages, "jupyter-console"); + String gPython = getPackageVersion(allPackages, "g-python"); + + if (qtconsole == null || pyqt5 == null || jupyterConsole == null || gPython == null) { + return false; + } + + ComparableVersion gVersion = new ComparableVersion(gPython); + return gVersion.compareTo(new ComparableVersion(MIN_GPYTHON_VERSION)) >= 0; + } + + // null if not found + private static String getPackageVersion(List allPackages, String pkg) { + pkg = pkg.toLowerCase(); + + for (String maybePkg : allPackages) { + String[] split = maybePkg.split(" +"); + if (split[0].toLowerCase().equals(pkg)) { + return split[1]; + } + } + return null; + } + + private static List executeCommand(String... command) { + List output = new ArrayList<>(); + + try { + ProcessBuilder processBuilder = new ProcessBuilder(command); + Process process = processBuilder.start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + + String line; + while ((line = reader.readLine()) != null) { + output.add(line); + } + + process.waitFor(); + + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + return output; + } + +// public static void main(String[] args) { +// System.out.println(validInstallation()); +// } + +} diff --git a/G-Earth/src/main/java/gearth/services/gpython/OnQtConsoleLaunch.java b/G-Earth/src/main/java/gearth/services/gpython/OnQtConsoleLaunch.java new file mode 100644 index 0000000..8414672 --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/gpython/OnQtConsoleLaunch.java @@ -0,0 +1,7 @@ +package gearth.services.gpython; + +public interface OnQtConsoleLaunch { + + void launched(boolean failed); + +} diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/GUnityFileServer.java b/G-Earth/src/main/java/gearth/services/unity_tools/GUnityFileServer.java new file mode 100644 index 0000000..50cab54 --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/unity_tools/GUnityFileServer.java @@ -0,0 +1,150 @@ +package gearth.services.unity_tools; + +import gearth.Main; +import gearth.misc.Cacher; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Random; + +public class GUnityFileServer extends HttpServlet +{ + + public final static int FILESERVER_PORT = 9089; + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException + { + try { + response.addHeader("Access-Control-Allow-Origin", "*"); + String path = request.getPathInfo(); + + if (path.equals("/ping")) { + response.setStatus(200); + return; + } + + String url = request.getParameter("blabla"); + if (url == null || url.isEmpty()) { + response.setStatus(404); + return; + } + + response.addHeader("ETag", createETag()); + + String revision = url.split("/")[4]; + + if (path.equals("/prod")) getProd(revision, response); + else if (path.equals("/data")) getData(revision, response); + else if (path.equals("/wasm/code")) getWasmCode(revision, response); + else if (path.equals("/wasm/framework")) getWasmFramework(revision, response); + else if (path.equals("/unityloader")) getUnityLoader(revision, response); + else if (path.equals("/version")) getVersion(revision, response, url); + else if (path.equals("/logo")) getLogo(response); + else { + response.setStatus(404); + } + + response.setStatus(200); + } + catch (Exception e) { + e.printStackTrace(); + response.setStatus(500); + } + + } + + private static String getRandomHexString(int numchars){ + Random r = new Random(); + StringBuffer sb = new StringBuffer(); + while(sb.length() < numchars){ + sb.append(Integer.toHexString(r.nextInt())); + } + return sb.toString().substring(0, numchars); + } + + private String createETag() { + return "W/\"" + getRandomHexString(6) + "-" + getRandomHexString(13) + "\""; + } + + private String getDir(String revision) { + return Cacher.getCacheDir() + File.separator + "UNITY-" + revision + File.separator; + } + + + private void fileResponse(String file, HttpServletResponse response, String contentType) throws IOException { + ServletOutputStream out = response.getOutputStream(); + InputStream in = new FileInputStream(file); +// response.setContentType(contentType); + + byte[] bytes = new byte[4096]; + int bytesRead; + + while ((bytesRead = in.read(bytes)) != -1) { + out.write(bytes, 0, bytesRead); + } + + in.close(); + out.close(); + } + + + private void getProd(String revision, HttpServletResponse response) throws IOException { + UnityWebModifyer unitywebModifyer = new UnityWebModifyer(revision, getDir(revision)); + unitywebModifyer.modifyAllFiles(); + + fileResponse(getDir(revision) + UnityWebModifyer.UNITY_PROD, response, "application/json"); + } + + private void getData(String revision, HttpServletResponse response) throws IOException { + // application/vnd.unity + fileResponse(getDir(revision) + UnityWebModifyer.UNITY_DATA, response, "application/vnd.unity"); + } + + private void getWasmCode(String revision, HttpServletResponse response) throws IOException { + fileResponse(getDir(revision) + UnityWebModifyer.UNITY_CODE, response, "application/vnd.unity"); + } + + private void getWasmFramework(String revision, HttpServletResponse response) throws IOException { + fileResponse(getDir(revision) + UnityWebModifyer.UNITY_FRAMEWORK, response, "application/vnd.unity"); + } + + private void getUnityLoader(String revision, HttpServletResponse response) throws IOException { + UnityWebModifyer unitywebModifyer = new UnityWebModifyer(revision, getDir(revision)); + unitywebModifyer.modifyAllFiles(); + + fileResponse(getDir(revision) + UnityWebModifyer.UNITY_LOADER, response, "text/javascript"); + } + + private void getVersion(String revision, HttpServletResponse response, String url) throws IOException { + BufferedReader in = new BufferedReader(new InputStreamReader(new URL(url).openStream())); + + String version = in.readLine(); + String realVersion = version.split(" ")[0]; + + response.getOutputStream().print(realVersion + " - G-Earth by sirjonasxx"); + response.getOutputStream().close(); + } + + private void getLogo(HttpServletResponse response) throws IOException { + OutputStream out = response.getOutputStream(); + InputStream in = Main.class.getResourceAsStream("G-EarthLogo.png"); + + byte[] bytes = new byte[4096]; + int bytesRead; + + while ((bytesRead = in.read(bytes)) != -1) { + out.write(bytes, 0, bytesRead); + } + + in.close(); + out.close(); + } + +} + diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/UnityWebModifyer.java b/G-Earth/src/main/java/gearth/services/unity_tools/UnityWebModifyer.java new file mode 100644 index 0000000..1571860 --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/unity_tools/UnityWebModifyer.java @@ -0,0 +1,176 @@ +package gearth.services.unity_tools; + +import wasm.disassembly.InvalidOpCodeException; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class UnityWebModifyer { + + public final static String UNITY_PROD = "habbo2020-global-prod.json"; + public final static String UNITY_DATA = "habbo2020-global-prod.data.unityweb"; + public final static String UNITY_CODE = "habbo2020-global-prod.wasm.code.unityweb"; + public final static String UNITY_FRAMEWORK = "habbo2020-global-prod.wasm.framework.unityweb"; + public final static String UNITY_LOADER = "UnityLoader.js"; + + private final static String UNITYFILES_URL = "https://images.habbo.com/habbo-webgl-clients/{revision}/WebGL/habbo2020-global-prod/Build/"; + + private final String revision; + private final File saveFolder; + private final String currentUrl; + + + public UnityWebModifyer(String revision, String saveFolder) { + this.revision = revision; + this.currentUrl = UNITYFILES_URL.replace("{revision}", revision); + this.saveFolder = new File(saveFolder); + } + + public boolean modifyAllFiles() { + if (saveFolder.exists()) { + return true; + } + saveFolder.mkdirs(); + + try { + modifyProdFile(); + modifyDataFile(); + modifyCodeFile(); + modifyFrameworkFile(); + modifyUnityLoader(); + + } catch (Exception e) { + e.printStackTrace(); + saveFolder.delete(); + return false; + } + return true; + } + + // return urls for: data, code & framework file + private void modifyProdFile() throws IOException { + String prodUrl = currentUrl + UNITY_PROD; + + URLConnection connection = new URL(prodUrl).openConnection(); + InputStream is = connection.getInputStream(); + BufferedReader in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + + FileWriter fileWriter = new FileWriter(new File(saveFolder, UNITY_PROD)); + BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); + + String line; + while ((line = in.readLine()) != null) { + bufferedWriter.write(line); + bufferedWriter.newLine(); + } + + bufferedWriter.close(); + in.close(); + } + + private void downloadToFile(URL url, File file) throws IOException { + BufferedInputStream in = new BufferedInputStream(url.openStream()); + FileOutputStream fileOutputStream = new FileOutputStream(file); + byte[] dataBuffer = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead); + } + fileOutputStream.close(); + in.close(); + } + + private void modifyDataFile() throws IOException { + File dataFile = new File(saveFolder, UNITY_DATA); + URL dataUrl = new URL(currentUrl + UNITY_DATA); + downloadToFile(dataUrl, dataFile); + } + + private void modifyCodeFile() throws IOException, InvalidOpCodeException { + File codeFile = new File(saveFolder, UNITY_CODE); + URL codeUrl = new URL(currentUrl + UNITY_CODE); + downloadToFile(codeUrl, codeFile); + + new WasmCodePatcher(codeFile.getAbsolutePath()).patch(); + } + + + private String insertFrameworkCode(String fileContents, int index, String codeName) throws IOException { + BufferedReader code = new BufferedReader(new InputStreamReader(UnityWebModifyer.class.getResourceAsStream(codeName), StandardCharsets.UTF_8)); + + String firstPart = fileContents.substring(0, index); + String lastPart = fileContents.substring(index); + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(firstPart); + + stringBuilder.append("\n"); + String line; + while ((line = code.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + + stringBuilder.append(lastPart); + return stringBuilder.toString(); + } + + private void modifyFrameworkFile() throws IOException { + File frameworkFile = new File(saveFolder, UNITY_FRAMEWORK); + URL frameworkUrl = new URL(currentUrl + UNITY_FRAMEWORK); + downloadToFile(frameworkUrl, frameworkFile); + + + byte[] encoded = Files.readAllBytes(Paths.get(frameworkFile.getAbsolutePath())); + String contents = new String(encoded, StandardCharsets.UTF_8); + + contents = insertFrameworkCode(contents, 0, "js_code/unity_code.js"); + + String exportSearch = "Module.asmLibraryArg,buffer);Module[\"asm\"]=asm;"; + int exportIndex = contents.indexOf(exportSearch) + exportSearch.length(); + contents = insertFrameworkCode(contents, exportIndex, "js_code/unity_exports.js"); + + String importSearch = "if(!env[\"tableBase\"]){env[\"tableBase\"]=0}"; + int importIndex = contents.indexOf(importSearch) + importSearch.length(); + contents = insertFrameworkCode(contents, importIndex, "js_code/unity_imports.js"); + + contents = contents + .replace("var _free", "_free") + .replace("var _malloc", "_malloc") + .replace("{{RevisionName}}", revision); + + BufferedWriter writer = new BufferedWriter(new FileWriter(frameworkFile)); + writer.write(contents); + writer.close(); + } + + private void modifyUnityLoader() throws IOException { + File loaderFile = new File(saveFolder, UNITY_LOADER); + URL loaderUrl = new URL(currentUrl + UNITY_LOADER); + downloadToFile(loaderUrl, loaderFile); + + byte[] encoded = Files.readAllBytes(Paths.get(loaderFile.getAbsolutePath())); + String contents = new String(encoded, StandardCharsets.UTF_8); + + contents = contents.replace("o.result.responseHeaders[e]==a.getResponseHeader(e)", "false"); + contents = contents.replace("i.responseHeaders[e]=o.getResponseHeader(e)", + "const genRanHex = size => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');\n" + + " if (e === \"ETag\") {\n" + + " i.responseHeaders[e] = \"W/\\\"\" + genRanHex(6) + \"-\" + genRanHex(13) + \"\\\"\"\n" + + " }\n" + + " else {\n" + + " i.responseHeaders[e] = o.getResponseHeader(e)\n" + + " }"); + + BufferedWriter writer = new BufferedWriter(new FileWriter(loaderFile)); + writer.write(contents); + writer.close(); + } + + +} diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/WasmCodePatcher.java b/G-Earth/src/main/java/gearth/services/unity_tools/WasmCodePatcher.java new file mode 100644 index 0000000..ba2831b --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/unity_tools/WasmCodePatcher.java @@ -0,0 +1,30 @@ +package gearth.services.unity_tools; + +import gearth.services.unity_tools.codepatcher.IncomingPacketPatcher; +import gearth.services.unity_tools.codepatcher.OutgoingPacketPatcher; +import gearth.services.unity_tools.codepatcher.ReturnBytePatcher; +import gearth.services.unity_tools.codepatcher.SetKeyPatcher; +import wasm.disassembly.InvalidOpCodeException; +import wasm.disassembly.modules.Module; + +import java.io.IOException; +import java.util.Arrays; + +public class WasmCodePatcher { + + private String file; + + public WasmCodePatcher(String file) { + this.file = file; + } + + public void patch() throws IOException, InvalidOpCodeException { + Module module = new Module(file, Arrays.asList( + new SetKeyPatcher(), + new ReturnBytePatcher(), + new OutgoingPacketPatcher(), + new IncomingPacketPatcher() + )); + module.assembleToFile(file); + } +} diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/WasmCodePatcherOld.java b/G-Earth/src/main/java/gearth/services/unity_tools/WasmCodePatcherOld.java new file mode 100644 index 0000000..aaef51b --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/unity_tools/WasmCodePatcherOld.java @@ -0,0 +1,225 @@ +//package gearth.services.unity_tools; +// +//import wasm.disassembly.InvalidOpCodeException; +//import wasm.disassembly.instructions.Expression; +//import wasm.disassembly.instructions.Instr; +//import wasm.disassembly.instructions.InstrType; +//import wasm.disassembly.instructions.control.CallInstr; +//import wasm.disassembly.instructions.variable.LocalVariableInstr; +//import wasm.disassembly.modules.Module; +//import wasm.disassembly.modules.indices.FuncIdx; +//import wasm.disassembly.modules.indices.LocalIdx; +//import wasm.disassembly.modules.indices.TypeIdx; +//import wasm.disassembly.modules.sections.code.Func; +//import wasm.disassembly.modules.sections.export.Export; +//import wasm.disassembly.modules.sections.export.ExportDesc; +//import wasm.disassembly.modules.sections.imprt.Import; +//import wasm.disassembly.modules.sections.imprt.ImportDesc; +//import wasm.disassembly.types.FuncType; +//import wasm.disassembly.types.ResultType; +//import wasm.disassembly.types.ValType; +//import wasm.misc.Function; +// +//import java.io.*; +//import java.util.*; +// +//public class WasmCodePatcherOld { +// +// private String file; +// +// public WasmCodePatcherOld(String file) { +// this.file = file; +// } +// +// public void patch() throws IOException, InvalidOpCodeException { +// Module module = new Module(file); +// +// FuncIdx returnByteId = findReturnByteFunc(module); +// FuncIdx setkey = findSetKeyFunc(module); +// FuncIdx outgoingIdx = findOutFunc(module); +// FuncIdx incomingIdx = findInFunc(module); +// +// hook(module, setkey, "g_chacha_setkey"); +// copyEmptyHook(module, returnByteId, "_gearth_returnbyte_copy", "g_chacha_returnbyte"); +// copyEmptyHook(module, outgoingIdx, "_gearth_outgoing_copy", "g_outgoing_packet"); +// copyEmptyHook(module, incomingIdx, "_gearth_incoming_copy", "g_incoming_packet"); +// +// module.assembleToFile(file); +// } +// +// +// private FuncIdx findOutFunc(Module module) { +// TypeIdx expectedTypeIdx = module.getTypeSection().getTypeIdxForFuncType(new FuncType( +// new ResultType(Arrays.asList(ValType.I32, ValType.I32, ValType.I32)), +// new ResultType(Collections.emptyList()) +// )); +// +// outerloop: +// for (int i = 0; i < module.getCodeSection().getCodesEntries().size(); i++) { +// FuncIdx currentIdx = new FuncIdx(i + module.getImportSection().getTotalFuncImports(), module); +// +// Func func = module.getCodeSection().getByIdx(currentIdx); +// if (func.getLocalss().size() != 0) continue; +// if (!module.getFunctionSection().getByIdx(currentIdx).equals(expectedTypeIdx)) continue; +// +// List expression = func.getExpression().getInstructions(); +// +// if (expression.size() != 6) continue; +// +// if (expression.get(0).getInstrType() != InstrType.LOCAL_GET) continue; +// if (expression.get(1).getInstrType() != InstrType.LOCAL_GET) continue; +// if (expression.get(2).getInstrType() != InstrType.LOCAL_GET) continue; +// if (expression.get(3).getInstrType() != InstrType.I32_LOAD) continue; +// if (expression.get(4).getInstrType() != InstrType.I32_CONST) continue; +// if (expression.get(5).getInstrType() != InstrType.CALL) continue; +// +// return currentIdx; +// } +// +// return null; +// } +// private FuncIdx findSetKeyFunc(Module module) { +// FuncType expectedType = new FuncType( +// new ResultType(Arrays.asList(ValType.I32, ValType.I32, ValType.I32, ValType.I32)), +// new ResultType(Collections.emptyList()) +// ); +// +// List expectedExpr = Arrays.asList(InstrType.I32_CONST, InstrType.I32_LOAD8_S, +// InstrType.I32_EQZ, InstrType.IF, InstrType.BLOCK, InstrType.LOCAL_GET, InstrType.I32_CONST, +// InstrType.LOCAL_GET, InstrType.I32_LOAD, InstrType.I32_CONST, InstrType.I32_CONST, InstrType.I32_CONST, +// InstrType.CALL); +// +// outerloop: +// for (int i = 0; i < module.getCodeSection().getCodesEntries().size(); i++) { +// FuncIdx funcIdx = new FuncIdx(i + module.getImportSection().getTotalFuncImports(), module); +// +// Function function = new Function(module, funcIdx); +// if (!function.getFuncType().equals(expectedType)) continue; +// if (!(function.getLocalsFloored().size() == 1 && function.getLocalsFloored().get(0) == ValType.I32)) continue; +// if (function.getCode().getInstructions().size() != expectedExpr.size()) continue; +// +// for (int j = 0; j < function.getCode().getInstructions().size(); j++) { +// Instr instr = function.getCode().getInstructions().get(j); +// if (instr.getInstrType() != expectedExpr.get(j)) continue outerloop; +// } +// +// return funcIdx; +// } +// +// return null; +// } +// private FuncIdx findReturnByteFunc(Module module) { +// FuncType expectedType = new FuncType( +// new ResultType(Arrays.asList(ValType.I32, ValType.I32, ValType.I32)), +// new ResultType(Collections.singletonList(ValType.I32)) +// ); +// +// outerloop: +// for (int i = 0; i < module.getCodeSection().getCodesEntries().size(); i++) { +// FuncIdx funcIdx = new FuncIdx(i + module.getImportSection().getTotalFuncImports(), module); +// +// Function function = new Function(module, funcIdx); +// if (!function.getFuncType().equals(expectedType)) continue; +// if (function.getLocalsFloored().size() != 0) continue; +// if (function.getCode().getInstructions().size() != 30) continue; +// +// List expr = function.getCode().getInstructions(); +// if (expr.get(expr.size() - 1).getInstrType() != InstrType.I32_XOR) continue; +// +// return funcIdx; +// } +// +// return null; +// } +// private FuncIdx findInFunc(Module module) { +// FuncType expectedType = new FuncType( +// new ResultType(Arrays.asList(ValType.I32, ValType.I32, ValType.I32, ValType.I32, ValType.I32)), +// new ResultType(Collections.emptyList()) +// ); +// +// List expectedExpr = Arrays.asList(InstrType.I32_CONST, InstrType.I32_LOAD8_S, +// InstrType.I32_EQZ, InstrType.IF, InstrType.LOCAL_GET, InstrType.I32_LOAD, InstrType.LOCAL_TEE, +// InstrType.IF); +// +// outerloop: +// for (int i = 0; i < module.getCodeSection().getCodesEntries().size(); i++) { +// FuncIdx funcIdx = new FuncIdx(i + module.getImportSection().getTotalFuncImports(), module); +// +// Function function = new Function(module, funcIdx); +// if (!function.getFuncType().equals(expectedType)) continue; +// if (!(function.getLocalsFloored().size() == 1 && function.getLocalsFloored().get(0) == ValType.I32)) continue; +// if (function.getCode().getInstructions().size() != expectedExpr.size()) continue; +// +// for (int j = 0; j < function.getCode().getInstructions().size(); j++) { +// Instr instr = function.getCode().getInstructions().get(j); +// if (instr.getInstrType() != expectedExpr.get(j)) continue outerloop; +// } +// +// return funcIdx; +// } +// +// return null; +// } +// +// private void copyEmptyHook(Module module, FuncIdx orgFuncIdx, String exportName, String hookname) throws InvalidOpCodeException, IOException { +// // copies the method, empties the first one +// // export the copy +// // hooks to the emptied one +// +// Func func = module.getCodeSection().getByIdx(orgFuncIdx); +// FuncType funcType = module.getTypeSection().getByFuncIdx(orgFuncIdx); +// +// // copy the function +// Function copy = new Function(funcType, func.getLocalss(), func.getExpression()); +// FuncIdx copyIdx = copy.addToModule(module); +// +// module.getExportSection().getExports().add(new Export(exportName, new ExportDesc(copyIdx))); +// +// +// // clear & hook original function, let it return whatever JS returns +// Import imp = new Import( +// "env", +// hookname, +// new ImportDesc(module.getTypeSection().getTypeIdxForFuncType(new FuncType( +// funcType.getParameterType(), +// funcType.getResultType() +// ))) +// ); +// FuncIdx hookIdx = module.getImportSection().importFunction(imp); +// +// CallInstr call = new CallInstr(hookIdx); +// List newInstrs = new ArrayList<>(); +// for (int i = 0; i < funcType.getParameterType().typeList().size(); i++) { +// newInstrs.add(new LocalVariableInstr(InstrType.LOCAL_GET, new LocalIdx(i))); +// } +// newInstrs.add(call); +// func.setExpression(new Expression(newInstrs)); +// +// } +// +// private void hook(Module module, FuncIdx funcIdx, String jsFunctionName) throws InvalidOpCodeException, IOException { +// FuncType funcType = module.getTypeSection().getByFuncIdx(funcIdx); +// +// Import imp = new Import( +// "env", +// jsFunctionName, +// new ImportDesc(module.getTypeSection().getTypeIdxForFuncType(new FuncType( +// funcType.getParameterType(), +// new ResultType(Collections.emptyList()) +// ))) +// ); +// FuncIdx hookIdx = module.getImportSection().importFunction(imp); +// +// CallInstr call = new CallInstr(hookIdx); +// +// Func root = module.getCodeSection().getByIdx(funcIdx); +// List newInstrs = new ArrayList<>(); +// for (int i = 0; i < funcType.getParameterType().typeList().size(); i++) { +// newInstrs.add(new LocalVariableInstr(InstrType.LOCAL_GET, new LocalIdx(i))); +// } +// newInstrs.add(call); +// newInstrs.addAll(root.getExpression().getInstructions()); +// root.getExpression().setInstructions(newInstrs); +// } +// +//} diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/IncomingPacketPatcher.java b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/IncomingPacketPatcher.java new file mode 100644 index 0000000..48e269b --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/IncomingPacketPatcher.java @@ -0,0 +1,60 @@ +package gearth.services.unity_tools.codepatcher; + +import wasm.disassembly.instructions.Instr; +import wasm.disassembly.instructions.InstrType; +import wasm.disassembly.modules.sections.code.Func; +import wasm.disassembly.modules.sections.code.Locals; +import wasm.disassembly.types.FuncType; +import wasm.disassembly.types.ResultType; +import wasm.disassembly.types.ValType; +import wasm.misc.CodeCompare; +import wasm.misc.StreamReplacement; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class IncomingPacketPatcher implements StreamReplacement { + @Override + public FuncType getFuncType() { + return new FuncType(new ResultType(Arrays.asList(ValType.I32, ValType.I32, ValType.I32, ValType.I32, ValType.I32)), + new ResultType(Collections.emptyList()) + ); + } + + @Override + public ReplacementType getReplacementType() { + return ReplacementType.HOOKCOPYEXPORT; + } + + @Override + public String getImportName() { + return "g_incoming_packet"; + } + + @Override + public String getExportName() { + return "_gearth_incoming_copy"; + } + + @Override + public CodeCompare getCodeCompare() { + return code -> { + if (!(code.getLocalss().equals(Collections.singletonList(new Locals(1, ValType.I32))))) + return false; + + List expectedExpr = Arrays.asList(InstrType.I32_CONST, InstrType.I32_LOAD8_S, + InstrType.I32_EQZ, InstrType.IF, InstrType.LOCAL_GET, InstrType.I32_LOAD, InstrType.LOCAL_TEE, + InstrType.IF); + + if (code.getExpression().getInstructions().size() != expectedExpr.size()) return false; + + for (int j = 0; j < code.getExpression().getInstructions().size(); j++) { + Instr instr = code.getExpression().getInstructions().get(j); + if (instr.getInstrType() != expectedExpr.get(j)) return false; + } + + return true; + }; + } +} diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/OutgoingPacketPatcher.java b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/OutgoingPacketPatcher.java new file mode 100644 index 0000000..86921e3 --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/OutgoingPacketPatcher.java @@ -0,0 +1,53 @@ +package gearth.services.unity_tools.codepatcher; + +import wasm.disassembly.instructions.Instr; +import wasm.disassembly.instructions.InstrType; +import wasm.disassembly.modules.sections.code.Func; +import wasm.disassembly.types.FuncType; +import wasm.disassembly.types.ResultType; +import wasm.disassembly.types.ValType; +import wasm.misc.CodeCompare; +import wasm.misc.StreamReplacement; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class OutgoingPacketPatcher implements StreamReplacement { + @Override + public FuncType getFuncType() { + return new FuncType(new ResultType(Arrays.asList(ValType.I32, ValType.I32, ValType.I32)), + new ResultType(Collections.emptyList())); + } + + @Override + public ReplacementType getReplacementType() { + return ReplacementType.HOOKCOPYEXPORT; + } + + @Override + public String getImportName() { + return "g_outgoing_packet"; + } + + @Override + public String getExportName() { + return "_gearth_outgoing_copy"; + } + + @Override + public CodeCompare getCodeCompare() { + return code -> { + if (code.getLocalss().size() != 0) return false; + List expression = code.getExpression().getInstructions(); + if (expression.get(0).getInstrType() != InstrType.LOCAL_GET) return false; + if (expression.get(1).getInstrType() != InstrType.LOCAL_GET) return false; + if (expression.get(2).getInstrType() != InstrType.LOCAL_GET) return false; + if (expression.get(3).getInstrType() != InstrType.I32_LOAD) return false; + if (expression.get(4).getInstrType() != InstrType.I32_CONST) return false; + if (expression.get(5).getInstrType() != InstrType.CALL) return false; + + return true; + }; + } +} diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/ReturnBytePatcher.java b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/ReturnBytePatcher.java new file mode 100644 index 0000000..4d90c5c --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/ReturnBytePatcher.java @@ -0,0 +1,48 @@ +package gearth.services.unity_tools.codepatcher; + +import wasm.disassembly.instructions.Instr; +import wasm.disassembly.instructions.InstrType; +import wasm.disassembly.modules.sections.code.Func; +import wasm.disassembly.types.FuncType; +import wasm.disassembly.types.ResultType; +import wasm.disassembly.types.ValType; +import wasm.misc.CodeCompare; +import wasm.misc.StreamReplacement; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class ReturnBytePatcher implements StreamReplacement { + @Override + public FuncType getFuncType() { + return new FuncType(new ResultType(Arrays.asList(ValType.I32, ValType.I32, ValType.I32)), + new ResultType(Collections.singletonList(ValType.I32))); + } + + @Override + public ReplacementType getReplacementType() { + return ReplacementType.HOOKCOPYEXPORT; + } + + @Override + public String getImportName() { + return "g_chacha_returnbyte"; + } + + @Override + public String getExportName() { + return "_gearth_returnbyte_copy"; + } + + @Override + public CodeCompare getCodeCompare() { + return code -> { + if (code.getLocalss().size() != 0) return false; + if (code.getExpression().getInstructions().size() != 30) return false; + List expr = code.getExpression().getInstructions(); + if (expr.get(expr.size() - 1).getInstrType() != InstrType.I32_XOR) return false; + return true; + }; + } +} diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/SetKeyPatcher.java b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/SetKeyPatcher.java new file mode 100644 index 0000000..f952105 --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/SetKeyPatcher.java @@ -0,0 +1,60 @@ +package gearth.services.unity_tools.codepatcher; + +import wasm.disassembly.instructions.Instr; +import wasm.disassembly.instructions.InstrType; +import wasm.disassembly.modules.sections.code.Func; +import wasm.disassembly.modules.sections.code.Locals; +import wasm.disassembly.types.FuncType; +import wasm.disassembly.types.ResultType; +import wasm.disassembly.types.ValType; +import wasm.misc.CodeCompare; +import wasm.misc.StreamReplacement; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class SetKeyPatcher implements StreamReplacement { + + @Override + public FuncType getFuncType() { + return new FuncType(new ResultType(Arrays.asList(ValType.I32, ValType.I32, ValType.I32, ValType.I32)), + new ResultType(Collections.emptyList())); + } + + @Override + public ReplacementType getReplacementType() { + return ReplacementType.HOOK; + } + + @Override + public String getImportName() { + return "g_chacha_setkey"; + } + + @Override + public String getExportName() { + return null; + } + + @Override + public CodeCompare getCodeCompare() { + return code -> { + if (!(code.getLocalss().equals(Collections.singletonList(new Locals(1, ValType.I32))))) + return false; + List expectedExpr = Arrays.asList(InstrType.I32_CONST, InstrType.I32_LOAD8_S, + InstrType.I32_EQZ, InstrType.IF, InstrType.BLOCK, InstrType.LOCAL_GET, InstrType.I32_CONST, + InstrType.LOCAL_GET, InstrType.I32_LOAD, InstrType.I32_CONST, InstrType.I32_CONST, InstrType.I32_CONST, + InstrType.CALL); + + if (code.getExpression().getInstructions().size() != expectedExpr.size()) return false; + + for (int j = 0; j < code.getExpression().getInstructions().size(); j++) { + Instr instr = code.getExpression().getInstructions().get(j); + if (instr.getInstrType() != expectedExpr.get(j)) return false; + } + + return true; + }; + } +} diff --git a/G-Earth/src/main/java/gearth/ui/GEarthController.java b/G-Earth/src/main/java/gearth/ui/GEarthController.java index 3dbbf64..43a7f45 100644 --- a/G-Earth/src/main/java/gearth/ui/GEarthController.java +++ b/G-Earth/src/main/java/gearth/ui/GEarthController.java @@ -1,6 +1,8 @@ package gearth.ui; import gearth.ui.logger.loggerdisplays.PacketLoggerFactory; +import javafx.event.Event; +import javafx.event.EventHandler; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.layout.Pane; @@ -44,52 +46,35 @@ public class GEarthController { public void initialize() { tabs = new ArrayList<>(); + // must be ordered correctly tabs.add(connectionController); tabs.add(injectionController); tabs.add(loggerController); tabs.add(toolsController); tabs.add(schedulerController); + tabs.add(extensionsController); tabs.add(extraController); tabs.add(infoController); - tabs.add(extensionsController); synchronized (this) { trySetController(); } + List uiTabs = tabBar.getTabs(); + for (int i = 0; i < uiTabs.size(); i++) { + Tab tab = uiTabs.get(i); + int[] ii = {i}; + + tab.setOnSelectionChanged(event -> { + if (tab.isSelected()) { + tabs.get(ii[0]).onTabOpened(); + } + }); + } + if (PacketLoggerFactory.usesUIlogger()) { tabBar.getTabs().remove(tab_Logger); } - - //custom header bar -// final Point[] startpos = {null}; -// final Double[] xx = {0.0}; -// final Double[] yy = {0.0}; -// final Boolean[] isMoving = {false}; -// -// mover.addEventHandler(MouseEvent.MOUSE_PRESSED, -// event -> { -// startpos[0] = MouseInfo.getPointerInfo().getLocation(); -// xx[0] = stage.getX(); -// yy[0] = stage.getY(); -// isMoving[0] = true; -// }); -// -// mover.addEventHandler(MouseEvent.MOUSE_RELEASED, -// event -> { -// isMoving[0] = false; -// }); -// -// -// mover.setOnMouseDragged(event -> { -// if (isMoving[0]) { -// Point now = MouseInfo.getPointerInfo().getLocation(); -// double diffX = now.getX() - startpos[0].getX(); -// double diffY = now.getY() - startpos[0].getY(); -// stage.setX(xx[0] + diffX); -// stage.setY(yy[0] + diffY); -// } -// }); } public void setStage(Stage stage) { @@ -122,5 +107,4 @@ public class GEarthController { tabs.forEach(SubForm::exit); hConnection.abort(); } - } diff --git a/G-Earth/src/main/java/gearth/ui/SubForm.java b/G-Earth/src/main/java/gearth/ui/SubForm.java index b809300..9b0ba73 100644 --- a/G-Earth/src/main/java/gearth/ui/SubForm.java +++ b/G-Earth/src/main/java/gearth/ui/SubForm.java @@ -28,6 +28,10 @@ public class SubForm { } + protected void onTabOpened() { + + } + protected HConnection getHConnection() { return parentController.getHConnection(); } diff --git a/G-Earth/src/main/java/gearth/ui/buttons/BoxButton.java b/G-Earth/src/main/java/gearth/ui/buttons/BoxButton.java index d7f69a9..0afd90c 100644 --- a/G-Earth/src/main/java/gearth/ui/buttons/BoxButton.java +++ b/G-Earth/src/main/java/gearth/ui/buttons/BoxButton.java @@ -18,8 +18,8 @@ public class BoxButton extends StackPane { //paths zijn relatief aan deze classpath public BoxButton(String imageName, String imageOnHoverName) { - this.image = new Image(getClass().getResourceAsStream("/gearth/ui/buttons/files/" + imageName)); - this.imageOnHover = new Image(getClass().getResourceAsStream("/gearth/ui/buttons/files/" + imageOnHoverName)); + this.image = new Image(getClass().getResourceAsStream("files/" + imageName)); + this.imageOnHover = new Image(getClass().getResourceAsStream("files/" + imageOnHoverName)); this.imageView = new ImageView(); setCursor(Cursor.DEFAULT); diff --git a/G-Earth/src/main/java/gearth/ui/buttons/PauseResumeButton.java b/G-Earth/src/main/java/gearth/ui/buttons/PauseResumeButton.java index c107334..3df2cff 100644 --- a/G-Earth/src/main/java/gearth/ui/buttons/PauseResumeButton.java +++ b/G-Earth/src/main/java/gearth/ui/buttons/PauseResumeButton.java @@ -33,10 +33,10 @@ public class PauseResumeButton extends StackPane{ public PauseResumeButton(boolean isPaused) { this.isPaused[0] = isPaused; - this.imagePause = new Image(getClass().getResourceAsStream("/gearth/ui/buttons/files/ButtonPause.png")); - this.imagePauseOnHover = new Image(getClass().getResourceAsStream("/gearth/ui/buttons/files/ButtonPauseHover.png")); - this.imageResume = new Image(getClass().getResourceAsStream("/gearth/ui/buttons/files/ButtonResume.png")); - this.imageResumeOnHover = new Image(getClass().getResourceAsStream("/gearth/ui/buttons/files/ButtonResumeHover.png")); + this.imagePause = new Image(getClass().getResourceAsStream("files/ButtonPause.png")); + this.imagePauseOnHover = new Image(getClass().getResourceAsStream("files/ButtonPauseHover.png")); + this.imageResume = new Image(getClass().getResourceAsStream("files/ButtonResume.png")); + this.imageResumeOnHover = new Image(getClass().getResourceAsStream("files/ButtonResumeHover.png")); this.imageView = new ImageView(); setCursor(Cursor.DEFAULT); diff --git a/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java b/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java index 77ea986..2f9eae2 100644 --- a/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java +++ b/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java @@ -98,7 +98,9 @@ public class ConnectionController extends SubForm { txtfield_hotelversion.setText(getHConnection().getHotelVersion()); btnConnect.setDisable(getHConnection().getState() == HState.PREPARING || getHConnection().getState() == HState.ABORTING); - if (!cbx_autodetect.isSelected() && !btnConnect.isDisable()) { + + + if (!cbx_autodetect.isSelected() && !btnConnect.isDisable() && useFlash()) { try { int i = Integer.parseInt(inpPort.getEditor().getText()); btnConnect.setDisable(i < 0 || i >= 256 * 256); @@ -110,6 +112,13 @@ public class ConnectionController extends SubForm { inpHost.setDisable(getHConnection().getState() != HState.NOT_CONNECTED || cbx_autodetect.isSelected()); inpPort.setDisable(getHConnection().getState() != HState.NOT_CONNECTED || cbx_autodetect.isSelected()); + + cbx_autodetect.setDisable(!useFlash()); + outHost.setDisable(!useFlash()); + outPort.setDisable(!useFlash()); + + inpHost.setDisable(!useFlash() || getHConnection().getState() != HState.NOT_CONNECTED || cbx_autodetect.isSelected()); + inpPort.setDisable(!useFlash() || getHConnection().getState() != HState.NOT_CONNECTED || cbx_autodetect.isSelected()); } public void onParentSet(){ @@ -134,14 +143,15 @@ public class ConnectionController extends SubForm { if (newState == HState.CONNECTED) { lblState.setText("Connected"); - outHost.setText(getHConnection().getDomain()); - outPort.setText(getHConnection().getServerPort()+""); } if (newState == HState.WAITING_FOR_CLIENT) { lblState.setText("Waiting for connection"); } - if (newState == HState.CONNECTED) { + if (newState == HState.CONNECTED && useFlash()) { + outHost.setText(getHConnection().getDomain()); + outPort.setText(getHConnection().getServerPort()+""); + JSONObject connectionSettings = new JSONObject(); connectionSettings.put(AUTODETECT_CACHE, cbx_autodetect.isSelected()); connectionSettings.put(HOST_CACHE, inpHost.getEditor().getText()); @@ -158,12 +168,19 @@ public class ConnectionController extends SubForm { btnConnect.setDisable(true); new Thread(() -> { - if (cbx_autodetect.isSelected()) { - getHConnection().start(); + if (useFlash()) { + if (cbx_autodetect.isSelected()) { + getHConnection().start(); + } + else { + getHConnection().start(inpHost.getEditor().getText(), Integer.parseInt(inpPort.getEditor().getText())); + } } else { - getHConnection().start(inpHost.getEditor().getText(), Integer.parseInt(inpPort.getEditor().getText())); + getHConnection().startUnity(); } + + if (HConnection.DEBUG) System.out.println("connecting"); }).start(); @@ -178,5 +195,12 @@ public class ConnectionController extends SubForm { protected void onExit() { getHConnection().abort(); } - + + public void changeClientMode() { + updateInputUI(); + } + + private boolean useFlash() { + return parentController.extraController.rd_flash.isSelected(); + } } diff --git a/G-Earth/src/main/java/gearth/ui/extensions/ExtensionsController.java b/G-Earth/src/main/java/gearth/ui/extensions/ExtensionsController.java index 9d7f80b..813599f 100644 --- a/G-Earth/src/main/java/gearth/ui/extensions/ExtensionsController.java +++ b/G-Earth/src/main/java/gearth/ui/extensions/ExtensionsController.java @@ -1,25 +1,31 @@ package gearth.ui.extensions; +import gearth.Main; import gearth.services.extensionhandler.ExtensionConnectedListener; import gearth.services.extensionhandler.ExtensionHandler; import gearth.services.extensionhandler.extensions.ExtensionListener; import gearth.services.extensionhandler.extensions.GEarthExtension; import gearth.services.extensionhandler.extensions.implementations.network.NetworkExtensionsProducer; +import gearth.services.extensionhandler.extensions.implementations.network.authentication.Authenticator; import gearth.services.extensionhandler.extensions.implementations.network.executer.ExecutionInfo; import gearth.services.extensionhandler.extensions.implementations.network.executer.ExtensionRunner; import gearth.services.extensionhandler.extensions.implementations.network.executer.ExtensionRunnerFactory; +import gearth.services.gpython.GPythonShell; +import gearth.services.gpython.OnQtConsoleLaunch; import gearth.ui.SubForm; import gearth.ui.extensions.logger.ExtensionLogger; +import gearth.ui.extra.ExtraController; import javafx.application.Platform; import javafx.event.ActionEvent; -import javafx.scene.control.Button; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.TextField; +import javafx.scene.control.*; +import javafx.scene.layout.FlowPane; import javafx.scene.layout.GridPane; +import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import java.io.File; +import java.io.IOException; /** * Created by Jonas on 06/04/18. @@ -29,12 +35,12 @@ public class ExtensionsController extends SubForm { public Button btn_install; - public Button btn_remove; public TextField ext_port; public VBox extensioncontainer; public GridPane header_ext; public ScrollPane scroller; public Button btn_viewExtensionConsole; + public Button btn_gpython; private ExtensionRunner extensionRunner = null; private ExtensionHandler extensionHandler; @@ -98,6 +104,33 @@ public class ExtensionsController extends SubForm { else { extensionLogger.hide(); } + } + @Override + protected void onTabOpened() { + updateGPythonStatus(); + } + + public void updateGPythonStatus() { + if (!pythonShellLaunching) { + btn_gpython.setDisable(!parentController.extraController.useGPython()); + } + } + + + private volatile int gpytonShellCounter = 1; + private volatile boolean pythonShellLaunching = false; + public void gpythonBtnClicked(ActionEvent actionEvent) { + pythonShellLaunching = true; + Platform.runLater(() -> btn_gpython.setDisable(true)); + GPythonShell shell = new GPythonShell( + "Scripting shell " + gpytonShellCounter++, + networkExtensionsProducer.getPort(), + Authenticator.generatePermanentCookie() + ); + shell.launch((b) -> { + pythonShellLaunching = false; + Platform.runLater(this::updateGPythonStatus); + }); } } diff --git a/G-Earth/src/main/java/gearth/ui/extra/ExtraController.java b/G-Earth/src/main/java/gearth/ui/extra/ExtraController.java index ad8a8cd..d86dca2 100644 --- a/G-Earth/src/main/java/gearth/ui/extra/ExtraController.java +++ b/G-Earth/src/main/java/gearth/ui/extra/ExtraController.java @@ -1,14 +1,22 @@ package gearth.ui.extra; +import gearth.Main; import gearth.misc.Cacher; +import gearth.misc.packetrepresentation.prediction.checkers.TypeCheckerProducer; import gearth.protocol.HConnection; import gearth.protocol.connection.HState; import gearth.protocol.connection.proxy.ProxyProviderFactory; import gearth.protocol.connection.proxy.SocksConfiguration; +import gearth.services.Constants; +import gearth.services.gpython.GPythonVersionUtils; import gearth.ui.SubForm; import gearth.ui.info.InfoController; +import javafx.application.Platform; +import javafx.event.ActionEvent; import javafx.scene.control.*; +import javafx.scene.layout.FlowPane; import javafx.scene.layout.GridPane; +import javafx.scene.layout.Region; import org.json.JSONObject; /** @@ -16,12 +24,17 @@ import org.json.JSONObject; */ public class ExtraController extends SubForm implements SocksConfiguration { + public static final String INFO_URL_GPYTHON = "https://github.com/sirjonasxx/G-Earth/wiki/G-Python-qtConsole"; + public static final String NOTEPAD_CACHE_KEY = "notepad_text"; public static final String SOCKS_CACHE_KEY = "socks_config"; + public static final String GPYTHON_CACHE_KEY = "use_gpython"; + + public static final String USE_UNITY_CLIENT_CACHE_KEY = "use_unity"; public static final String SOCKS_IP = "ip"; public static final String SOCKS_PORT = "port"; - public static final String IGNORE_ONCE = "ignore_once"; +// public static final String IGNORE_ONCE = "ignore_once"; public TextArea txtarea_notepad; @@ -29,20 +42,32 @@ public class ExtraController extends SubForm implements SocksConfiguration { public CheckBox cbx_alwaysOnTop; public Hyperlink url_troubleshooting; + //TODO add setup link to g-earth wiki + public CheckBox cbx_gpython; + public CheckBox cbx_advanced; public GridPane grd_advanced; public CheckBox cbx_disableDecryption; public CheckBox cbx_debug; - public CheckBox cbx_useSocks; public GridPane grd_socksInfo; public TextField txt_socksPort; public TextField txt_socksIp; - public CheckBox cbx_socksUseIfNeeded; + + + public ToggleGroup tgl_clientMode; + public RadioButton rd_unity; + public RadioButton rd_flash; + public GridPane grd_clientSelection; public void initialize() { + Constants.UNITY_PACKETS = rd_unity.isSelected(); + tgl_clientMode.selectedToggleProperty().addListener(observable -> { + if (parentController != null) parentController.connectionController.changeClientMode(); + Constants.UNITY_PACKETS = rd_unity.isSelected(); + }); url_troubleshooting.setTooltip(new Tooltip("https://github.com/sirjonasxx/G-Earth/wiki/Troubleshooting")); InfoController.activateHyperlink(url_troubleshooting); @@ -56,7 +81,16 @@ public class ExtraController extends SubForm implements SocksConfiguration { JSONObject socksInitValue = Cacher.getCacheContents().getJSONObject(SOCKS_CACHE_KEY); txt_socksIp.setText(socksInitValue.getString(SOCKS_IP)); txt_socksPort.setText(socksInitValue.getString(SOCKS_PORT)); - cbx_socksUseIfNeeded.setSelected(socksInitValue.getBoolean(IGNORE_ONCE)); +// cbx_socksUseIfNeeded.setSelected(socksInitValue.getBoolean(IGNORE_ONCE)); + } + + if (Cacher.getCacheContents().has(GPYTHON_CACHE_KEY)) { + cbx_gpython.setSelected(Cacher.getCacheContents().getBoolean(GPYTHON_CACHE_KEY)); + } + + if (Cacher.getCacheContents().has(USE_UNITY_CLIENT_CACHE_KEY)) { + rd_unity.setSelected(Cacher.getCacheContents().getBoolean(USE_UNITY_CLIENT_CACHE_KEY)); + rd_flash.setSelected(!Cacher.getCacheContents().getBoolean(USE_UNITY_CLIENT_CACHE_KEY)); } cbx_debug.selectedProperty().addListener(observable -> HConnection.DEBUG = cbx_debug.isSelected()); @@ -85,6 +119,8 @@ public class ExtraController extends SubForm implements SocksConfiguration { @Override protected void onExit() { Cacher.put(NOTEPAD_CACHE_KEY, txtarea_notepad.getText()); + Cacher.put(GPYTHON_CACHE_KEY, cbx_gpython.isSelected()); + Cacher.put(USE_UNITY_CLIENT_CACHE_KEY, rd_unity.isSelected()); saveSocksConfig(); } @@ -92,11 +128,13 @@ public class ExtraController extends SubForm implements SocksConfiguration { JSONObject jsonObject = new JSONObject(); jsonObject.put(SOCKS_IP, txt_socksIp.getText()); jsonObject.put(SOCKS_PORT, txt_socksPort.getText()); - jsonObject.put(IGNORE_ONCE, cbx_socksUseIfNeeded.isSelected()); +// jsonObject.put(IGNORE_ONCE, cbx_socksUseIfNeeded.isSelected()); Cacher.put(SOCKS_CACHE_KEY, jsonObject); } private void updateAdvancedUI() { + grd_clientSelection.setDisable(getHConnection().getState() != HState.NOT_CONNECTED); + if (!cbx_advanced.isSelected()) { cbx_debug.setSelected(false); cbx_useSocks.setSelected(false); @@ -127,6 +165,54 @@ public class ExtraController extends SubForm implements SocksConfiguration { @Override public boolean onlyUseIfNeeded() { - return cbx_socksUseIfNeeded.isSelected(); +// return cbx_socksUseIfNeeded.isSelected(); + return false; + } + + public boolean useGPython() { + return cbx_gpython.isSelected(); + } + + public void gpythonCbxClick(ActionEvent actionEvent) { + if (cbx_gpython.isSelected()) { + new Thread(() -> { + Platform.runLater(() -> { + cbx_gpython.setSelected(false); + cbx_gpython.setDisable(true); + }); + if (!GPythonVersionUtils.validInstallation()) { + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.ERROR, "G-Python installation", ButtonType.OK); + alert.setTitle("G-Python installation"); + + FlowPane fp = new FlowPane(); + Label lbl = new Label("Before using G-Python, install the right packages using pip!" + + System.lineSeparator() + System.lineSeparator() + "More information here:"); + Hyperlink link = new Hyperlink(INFO_URL_GPYTHON); + fp.getChildren().addAll( lbl, link); + link.setOnAction(event -> { + Main.main.getHostServices().showDocument(link.getText()); + event.consume(); + }); + + alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + alert.getDialogPane().setContent(fp); + alert.show(); + + cbx_gpython.setDisable(false); + }); + } + else { + Platform.runLater(() -> { + cbx_gpython.setSelected(true); + cbx_gpython.setDisable(false); + parentController.extensionsController.updateGPythonStatus(); + }); + } + }).start(); + + + } + } } diff --git a/G-Earth/src/main/java/gearth/ui/info/InfoController.java b/G-Earth/src/main/java/gearth/ui/info/InfoController.java index 7d06c2b..59991b6 100644 --- a/G-Earth/src/main/java/gearth/ui/info/InfoController.java +++ b/G-Earth/src/main/java/gearth/ui/info/InfoController.java @@ -2,27 +2,27 @@ package gearth.ui.info; import gearth.Main; import javafx.event.ActionEvent; -import javafx.scene.control.Hyperlink; +import javafx.scene.control.*; import gearth.ui.SubForm; -import javafx.scene.control.Label; -import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Region; +import javafx.scene.web.WebView; /** * Created by Jonas on 06/04/18. */ public class InfoController extends SubForm { public ImageView img_logo; - public Hyperlink link_sng; + public Hyperlink link_ase; public Hyperlink link_darkbox; public Hyperlink link_d_harble; public Hyperlink link_g_gearth; public Hyperlink link_g_tanji; - public Hyperlink link_d_bonnie; - public Label version; + public Hyperlink link_d_gearth; - // this is a TEMPORARY info tab + public Label version; public static void activateHyperlink(Hyperlink link) { link.setOnAction((ActionEvent event) -> { @@ -38,16 +38,36 @@ public class InfoController extends SubForm { img_logo.setImage(new Image("/gearth/G-EarthLogo.png")); - link_sng.setTooltip(new Tooltip("https://www.sngforum.info")); + link_ase.setTooltip(new Tooltip("https://allseeingeye.to")); link_darkbox.setTooltip(new Tooltip("https://darkbox.nl")); + link_d_harble.setTooltip(new Tooltip("https://discord.gg/CzRuHvW")); + link_d_gearth.setTooltip(new Tooltip("https://discord.gg/AVkcF8y")); link_g_gearth.setTooltip(new Tooltip("https://github.com/sirjonasxx/G-Earth")); link_g_tanji.setTooltip(new Tooltip("https://github.com/ArachisH/Tanji")); - link_d_harble.setTooltip(new Tooltip("https://discord.gg/CzRuHvW")); + activateHyperlink(link_ase); + activateHyperlink(link_darkbox); activateHyperlink(link_d_harble); + activateHyperlink(link_d_gearth); activateHyperlink(link_g_gearth); activateHyperlink(link_g_tanji); - activateHyperlink(link_sng); - activateHyperlink(link_darkbox); + } + + public void donate(ActionEvent actionEvent) { + String pubkey = "1GEarthEV9Ua3RcixsKTcuc1PPZd9hqri3"; + + Alert alert = new Alert(Alert.AlertType.INFORMATION, "Donate Bitcoins", ButtonType.OK); + alert.setHeaderText("Donate Bitcoins"); + + WebView webView = new WebView(); + webView.getEngine().loadContent("Bitcoin public address:

" + + "" + + ""); + webView.setPrefSize(200, 100); + + alert.setResizable(false); + alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + alert.getDialogPane().setContent(webView); + alert.show(); } } diff --git a/G-Earth/src/main/java/gearth/ui/injection/InjectionController.java b/G-Earth/src/main/java/gearth/ui/injection/InjectionController.java index adb0726..7cc3e52 100644 --- a/G-Earth/src/main/java/gearth/ui/injection/InjectionController.java +++ b/G-Earth/src/main/java/gearth/ui/injection/InjectionController.java @@ -136,8 +136,8 @@ public class InjectionController extends SubForm { public static void main(String[] args) { - HPacket[] packets = parsePackets("{l}{u:3}{i:967585}{i:9589}{s:\"furni_inscriptionfuckfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss\"}{s:\"sirjonasxx-II\"}{s:\"\"}{i:188}{i:0}{i:0}{b:false}"); - System.out.println(new HPacket("{l}{u:2550}{s:\"ClientPerf\"\"ormance\\\"}\"}{s:\"23\"}{s:\"fps\"}{s:\"Avatars: 1, Objects: 0\"}{i:76970180}").toExpression()); + HPacket[] packets = parsePackets("{l}{h:3}{i:967585}{i:9589}{s:\"furni_inscriptionfuckfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss\"}{s:\"sirjonasxx-II\"}{s:\"\"}{i:188}{i:0}{i:0}{b:false}"); + System.out.println(new HPacket("{l}{h:2550}{s:\"ClientPerf\"\"ormance\\\"}\"}{s:\"23\"}{s:\"fps\"}{s:\"Avatars: 1, Objects: 0\"}{i:76970180}").toExpression()); System.out.println("hi"); } diff --git a/G-Earth/src/main/java/gearth/ui/logger/loggerdisplays/uilogger/UiLoggerController.java b/G-Earth/src/main/java/gearth/ui/logger/loggerdisplays/uilogger/UiLoggerController.java index 965bedd..3f88800 100644 --- a/G-Earth/src/main/java/gearth/ui/logger/loggerdisplays/uilogger/UiLoggerController.java +++ b/G-Earth/src/main/java/gearth/ui/logger/loggerdisplays/uilogger/UiLoggerController.java @@ -95,7 +95,7 @@ public class UiLoggerController implements Initializable { String expr = packet.toExpression(isIncoming ? HMessage.Direction.TOCLIENT : HMessage.Direction.TOSERVER); - lblHarbleAPI.setText("HarbleAPI: " + (HarbleAPIFetcher.HARBLEAPI == null ? "False" : "True")); + lblHarbleAPI.setText("Messages: " + (HarbleAPIFetcher.HARBLEAPI == null ? "False" : "True")); if ((viewMessageName || viewMessageHash) && HarbleAPIFetcher.HARBLEAPI != null) { HarbleAPI api = HarbleAPIFetcher.HARBLEAPI; HarbleAPI.HarbleMessage message = api.getHarbleMessageFromHeaderId( @@ -124,7 +124,7 @@ public class UiLoggerController implements Initializable { .append(packet.headerId()) .append(spanWithClass("]", isIncoming ? "incoming" : "outgoing")) .append(isIncoming ? " <- " : " -> ") - .append(skiphugepackets && packet.length() > 8000 ? + .append(skiphugepackets && packet.length() > 4000 ? divWithClass("", "skipped") : divWithClass(packet.toString(), isIncoming ? "incoming" : "outgoing")); diff --git a/G-Earth/src/main/resources/build/messages.json b/G-Earth/src/main/resources/build/messages.json new file mode 100644 index 0000000..455e59b --- /dev/null +++ b/G-Earth/src/main/resources/build/messages.json @@ -0,0 +1,1109 @@ +{"Incoming":[ + {"Id":1, "Name":"LegacyBannerPublicKey"}, + {"Id":2, "Name":"ClubAndSecurityLevels"}, + {"Id":3, "Name":"Ok"}, + {"Id":4, "Name":"Film"}, + {"Id":5, "Name":"UserObject"}, + {"Id":6, "Name":"WalletBalance"}, + {"Id":7, "Name":"ScrSendUserInfo"}, + {"Id":8, "Name":"IsFirstLoginOfDay"}, + {"Id":9, "Name":"ScrSendKickbackInfo"}, + {"Id":11, "Name":"Token"}, + {"Id":12, "Name":"MessengerInit"}, + {"Id":13, "Name":"FriendListUpdate"}, + {"Id":14, "Name":"FriendListFragment"}, + {"Id":18, "Name":"CloseConnection"}, + {"Id":19, "Name":"OpenConnectionConfirmation"}, + {"Id":20, "Name":"NoLoginPermission"}, + {"Id":21, "Name":"RoomExitReason"}, + {"Id":23, "Name":"DeleteFlatResult"}, + {"Id":24, "Name":"Chat"}, + {"Id":25, "Name":"Whisper"}, + {"Id":26, "Name":"Shout"}, + {"Id":27, "Name":"FloodControl"}, + {"Id":28, "Name":"UsersInRoom"}, + {"Id":29, "Name":"UserLoggedOut"}, + {"Id":31, "Name":"StackingHeightmap"}, + {"Id":32, "Name":"ActiveObjects"}, + {"Id":33, "Name":"Error"}, + {"Id":34, "Name":"Status"}, + {"Id":35, "Name":"UserBanned"}, + {"Id":36, "Name":"PetNameApproved"}, + {"Id":37, "Name":"PetStatusUpdate"}, + {"Id":38, "Name":"PetLevelUpdate"}, + {"Id":42, "Name":"YouAreController"}, + {"Id":43, "Name":"YouAreNotController"}, + {"Id":44, "Name":"NoSuchFlat"}, + {"Id":45, "Name":"Items"}, + {"Id":46, "Name":"FlatProperty"}, + {"Id":47, "Name":"YouAreOwner"}, + {"Id":48, "Name":"ItemData"}, + {"Id":49, "Name":"ServiceIsClosed"}, + {"Id":50, "Name":"Ping"}, + {"Id":51, "Name":"RegOk"}, + {"Id":58, "Name":"PickedUpPetFromRoom"}, + {"Id":59, "Name":"FlatCreated"}, + {"Id":60, "Name":"SubFlatCreated"}, + {"Id":61, "Name":"PickedUpAllItems"}, + {"Id":62, "Name":"DoorFlat"}, + {"Id":63, "Name":"DoorOtherEndDeleted"}, + {"Id":64, "Name":"DoorNotInstalled"}, + {"Id":65, "Name":"PurchaseError"}, + {"Id":66, "Name":"CatalogUrl"}, + {"Id":67, "Name":"PurchaseOk"}, + {"Id":68, "Name":"NotEnoughBalance"}, + {"Id":69, "Name":"RoomReady"}, + {"Id":70, "Name":"YouAreModerator"}, + {"Id":72, "Name":"PhTickets"}, + {"Id":73, "Name":"PhNoTickets"}, + {"Id":74, "Name":"JumpData"}, + {"Id":75, "Name":"JumpDataSaved"}, + {"Id":76, "Name":"UserNotFound"}, + {"Id":77, "Name":"JumpsForUser"}, + {"Id":78, "Name":"GotoJumpingPlace"}, + {"Id":82, "Name":"OwnerPresence"}, + {"Id":83, "Name":"AddItem"}, + {"Id":84, "Name":"RemoveItem"}, + {"Id":85, "Name":"UpdateItem"}, + {"Id":88, "Name":"StuffDataUpdate"}, + {"Id":89, "Name":"DoorOut"}, + {"Id":90, "Name":"DiceValue"}, + {"Id":91, "Name":"DoorbellRinging"}, + {"Id":92, "Name":"DoorIn"}, + {"Id":93, "Name":"ActiveObjectAdd"}, + {"Id":94, "Name":"ActiveObjectRemove"}, + {"Id":95, "Name":"ActiveObjectUpdate"}, + {"Id":98, "Name":"InventoryAddOrUpdateFurni"}, + {"Id":99, "Name":"InventoryRemoveFurni"}, + {"Id":101, "Name":"InventoryInvalidate"}, + {"Id":102, "Name":"TradeYouAreNotAllowed"}, + {"Id":103, "Name":"TradeOtherNotAllowed"}, + {"Id":104, "Name":"TradeOpen"}, + {"Id":105, "Name":"TradeOpenFail"}, + {"Id":106, "Name":"TradeNoTradeOpen"}, + {"Id":107, "Name":"TradeNoSuchItem"}, + {"Id":108, "Name":"TradeItems"}, + {"Id":109, "Name":"TradeAccept"}, + {"Id":110, "Name":"TradeClose"}, + {"Id":111, "Name":"TradeConfirmation"}, + {"Id":112, "Name":"TradeCompleted"}, + {"Id":113, "Name":"Trigger"}, + {"Id":114, "Name":"TeleportRelinkResponse"}, + {"Id":115, "Name":"FurniRentOrBuyoutOffer"}, + {"Id":117, "Name":"DonationSettings"}, + {"Id":118, "Name":"DonateResult"}, + {"Id":122, "Name":"PhLiftDoorOpen"}, + {"Id":123, "Name":"PhLiftDoorClose"}, + {"Id":124, "Name":"PhTicketsBuy"}, + {"Id":125, "Name":"PhJumpingPlaceOk"}, + {"Id":126, "Name":"CatalogIndex"}, + {"Id":127, "Name":"CatalogPage"}, + {"Id":128, "Name":"MemberInfo"}, + {"Id":129, "Name":"PresentOpen"}, + {"Id":130, "Name":"FlatPasswordOk"}, + {"Id":132, "Name":"NewBuddyRequest"}, + {"Id":134, "Name":"MessengerNewConsoleMessage"}, + {"Id":135, "Name":"MessengerRoomInvite"}, + {"Id":139, "Name":"SystemBroadcast"}, + {"Id":140, "Name":"InventoryPush"}, + {"Id":145, "Name":"PostItPlaced"}, + {"Id":150, "Name":"MemberRoom"}, + {"Id":151, "Name":"ServiceClosed"}, + {"Id":152, "Name":"RoomUsers"}, + {"Id":153, "Name":"NewUnit"}, + {"Id":154, "Name":"UnitRemove"}, + {"Id":155, "Name":"UnitUpdated"}, + {"Id":156, "Name":"SystemDown"}, + {"Id":157, "Name":"SystemOpened"}, + {"Id":158, "Name":"UserProfile"}, + {"Id":159, "Name":"UserMatch"}, + {"Id":160, "Name":"CloseVotingDialog"}, + {"Id":161, "Name":"ModeratorCaution"}, + {"Id":162, "Name":"UnitClosed"}, + {"Id":163, "Name":"ModeratorMessage"}, + {"Id":164, "Name":"AgeCheckResult"}, + {"Id":166, "Name":"TrackingRequest"}, + {"Id":167, "Name":"ReregistrationRequired"}, + {"Id":169, "Name":"AccountUpdateStatus"}, + {"Id":208, "Name":"RoomAd"}, + {"Id":209, "Name":"UserCreditTransactions"}, + {"Id":211, "Name":"UpdateOk"}, + {"Id":212, "Name":"VoucherRedeemOk"}, + {"Id":213, "Name":"VoucherRedeemError"}, + {"Id":219, "Name":"StackingHeightmapDiff"}, + {"Id":221, "Name":"UserFlatCategories"}, + {"Id":222, "Name":"EventFlatCategories"}, + {"Id":224, "Name":"CanNotConnect"}, + {"Id":228, "Name":"SelectedBadges"}, + {"Id":229, "Name":"AvailableBadges"}, + {"Id":230, "Name":"QueueMoveUpdate"}, + {"Id":243, "Name":"FullGameStatus"}, + {"Id":244, "Name":"GameStatus"}, + {"Id":254, "Name":"YouAreSpectator"}, + {"Id":255, "Name":"LevelEditorNotification"}, + {"Id":257, "Name":"SessionParams"}, + {"Id":258, "Name":"Interstitial"}, + {"Id":259, "Name":"RoomQueueStatus"}, + {"Id":260, "Name":"MessengerError"}, + {"Id":261, "Name":"InstantMessageError"}, + {"Id":262, "Name":"RoomInviteError"}, + {"Id":263, "Name":"BuddyList"}, + {"Id":265, "Name":"EmailChangeResult"}, + {"Id":266, "Name":"UpdateAvatar"}, + {"Id":267, "Name":"UserWardrobe"}, + {"Id":268, "Name":"AccountSafetyLockStatus"}, + {"Id":269, "Name":"EmailStatus"}, + {"Id":270, "Name":"HotLooks"}, + {"Id":273, "Name":"CallDeleted"}, + {"Id":274, "Name":"CallReply"}, + {"Id":275, "Name":"RegUpdateRequest"}, + {"Id":276, "Name":"PartnerStatus"}, + {"Id":277, "Name":"LegacyBannerHandshake"}, + {"Id":278, "Name":"DhInitHandshake"}, + {"Id":279, "Name":"DhCompleteHandshake"}, + {"Id":280, "Name":"CSubscriptionUserGifts"}, + {"Id":281, "Name":"AccountSafetyLockQuestions"}, + {"Id":283, "Name":"SpectatingEnded"}, + {"Id":286, "Name":"RoomForward"}, + {"Id":290, "Name":"AvailabilityStatus"}, + {"Id":291, "Name":"InfoHotelClosing"}, + {"Id":292, "Name":"InfoHotelClosed"}, + {"Id":293, "Name":"AvailabilityTime"}, + {"Id":294, "Name":"LoginFailedHotelClosed"}, + {"Id":296, "Name":"PurchaseNotAllowed"}, + {"Id":297, "Name":"FurniAliases"}, + {"Id":298, "Name":"SpectatorAmount"}, + {"Id":299, "Name":"ErrorReport"}, + {"Id":300, "Name":"SongInfo"}, + {"Id":301, "Name":"MachineSoundPackages"}, + {"Id":302, "Name":"UserSoundPackages"}, + {"Id":303, "Name":"SongId"}, + {"Id":308, "Name":"AccountPreferences"}, + {"Id":309, "Name":"HabboGroupBadge"}, + {"Id":310, "Name":"FavoriteMembershipUpdate"}, + {"Id":311, "Name":"HabboGroupDetails"}, + {"Id":312, "Name":"OneWayDoorStatusChange"}, + {"Id":313, "Name":"MessengerConsoleMessages"}, + {"Id":314, "Name":"BuddyRequests"}, + {"Id":315, "Name":"AcceptFriendResult"}, + {"Id":316, "Name":"PollOffer"}, + {"Id":317, "Name":"PollContents"}, + {"Id":318, "Name":"PollError"}, + {"Id":319, "Name":"PendingCallsForHelp"}, + {"Id":320, "Name":"PendingCallsForHelpDeleted"}, + {"Id":321, "Name":"CallForHelpResult"}, + {"Id":322, "Name":"SongList"}, + {"Id":323, "Name":"SoundMachinePlayList"}, + {"Id":324, "Name":"SongMissingPackages"}, + {"Id":325, "Name":"PlayListInvalid"}, + {"Id":326, "Name":"SongListFull"}, + {"Id":327, "Name":"NowPlaying"}, + {"Id":328, "Name":"CallForHelpDisabled"}, + {"Id":329, "Name":"HabboGroupDeactivated"}, + {"Id":331, "Name":"SongAdded"}, + {"Id":332, "Name":"InvalidSongName"}, + {"Id":333, "Name":"UserSongDiscs"}, + {"Id":334, "Name":"JukeboxDiscs"}, + {"Id":336, "Name":"SongLocked"}, + {"Id":337, "Name":"JukeboxPlayListFull"}, + {"Id":338, "Name":"InvalidSongData"}, + {"Id":339, "Name":"SongSaved"}, + {"Id":344, "Name":"MuteAllInRoomResponse"}, + {"Id":345, "Name":"RoomRating"}, + {"Id":347, "Name":"UpdateFigure"}, + {"Id":349, "Name":"FollowFriendFailed"}, + {"Id":354, "Name":"ClientLatencyPingResponse"}, + {"Id":355, "Name":"ShowMysteryBoxWait"}, + {"Id":356, "Name":"CancelMysteryBoxWait"}, + {"Id":357, "Name":"GotMysteryBoxPrize"}, + {"Id":358, "Name":"FriendFurniStartConfirmLock"}, + {"Id":359, "Name":"FriendFurniCancelLock"}, + {"Id":360, "Name":"FriendFurniLockConfirmedOther"}, + {"Id":361, "Name":"UserTypingStatusChange"}, + {"Id":362, "Name":"HighlightUser"}, + {"Id":363, "Name":"MiniMailArrived"}, + {"Id":364, "Name":"MiniMailUnreadCount"}, + {"Id":365, "Name":"RoomDimmerPresets"}, + {"Id":367, "Name":"RoomEventCanCreateEvent"}, + {"Id":368, "Name":"RoomEventEventTypes"}, + {"Id":369, "Name":"RoomEventEventsByEventType"}, + {"Id":370, "Name":"RoomEventEventInfo"}, + {"Id":371, "Name":"RoomAdAllowedRooms"}, + {"Id":372, "Name":"RoomAdListAds"}, + {"Id":373, "Name":"RoomAdUpdated"}, + {"Id":374, "Name":"RoomAdError"}, + {"Id":375, "Name":"RoomAdCancelled"}, + {"Id":419, "Name":"IgnoreUserResult"}, + {"Id":420, "Name":"RequestIgnoreList"}, + {"Id":435, "Name":"HabboSearchResult"}, + {"Id":436, "Name":"PossibleUserAchievements"}, + {"Id":437, "Name":"AchievementNotification"}, + {"Id":438, "Name":"ActivityPointNotification"}, + {"Id":439, "Name":"UniqueMachineId"}, + {"Id":440, "Name":"RespectNotification"}, + {"Id":441, "Name":"CatalogExpired"}, + {"Id":443, "Name":"AchievementScore"}, + {"Id":446, "Name":"ResolutionCompleted"}, + {"Id":450, "Name":"OfficialRooms"}, + {"Id":451, "Name":"GuestRoomSearchResult"}, + {"Id":452, "Name":"PopularRoomTagsResult"}, + {"Id":454, "Name":"GetGuestRoomResult"}, + {"Id":455, "Name":"NavigatorSettings"}, + {"Id":456, "Name":"RoomInfoUpdatedNotification"}, + {"Id":457, "Name":"RoomThumbnailUpdateResult"}, + {"Id":458, "Name":"Favourites"}, + {"Id":459, "Name":"FavouriteChanged"}, + {"Id":460, "Name":"AvatarEffects"}, + {"Id":461, "Name":"AvatarEffectAdded"}, + {"Id":462, "Name":"AvatarEffectActivated"}, + {"Id":463, "Name":"AvatarEffectExpired"}, + {"Id":464, "Name":"AvatarEffectSelected"}, + {"Id":465, "Name":"RoomSettingsData"}, + {"Id":466, "Name":"RoomSettingsError"}, + {"Id":467, "Name":"RoomSettingsSaved"}, + {"Id":468, "Name":"RoomSettingsSaveError"}, + {"Id":470, "Name":"FloorHeightmap"}, + {"Id":471, "Name":"RoomEntryInfo"}, + {"Id":472, "Name":"RoomVisualizationSettings"}, + {"Id":473, "Name":"MultipleStuffDataUpdate"}, + {"Id":474, "Name":"RoomChatSettings"}, + {"Id":475, "Name":"RoomFilterSettings"}, + {"Id":476, "Name":"EnforceRoomCategorySelect"}, + {"Id":477, "Name":"SpecialRoomEffect"}, + {"Id":478, "Name":"RoomTypes"}, + {"Id":480, "Name":"RoomDance"}, + {"Id":481, "Name":"RoomExpression"}, + {"Id":482, "Name":"RoomCarryObject"}, + {"Id":483, "Name":"RoomJoiningGame"}, + {"Id":484, "Name":"RoomNotJoiningGame"}, + {"Id":485, "Name":"RoomAvatarEffect"}, + {"Id":486, "Name":"RoomAvatarSleeping"}, + {"Id":488, "Name":"RoomUseObject"}, + {"Id":490, "Name":"JudgeGuiStatus"}, + {"Id":491, "Name":"StageOpenPerformerGui"}, + {"Id":492, "Name":"StageClosePerformerGui"}, + {"Id":493, "Name":"StageStartPlayingSong"}, + {"Id":494, "Name":"StageStopPlayingSong"}, + {"Id":500, "Name":"FlatCategoriesWithVisitorData"}, + {"Id":509, "Name":"TradingAllowed"}, + {"Id":510, "Name":"FlatControllerAdded"}, + {"Id":511, "Name":"FlatControllerRemoved"}, + {"Id":512, "Name":"CanCreateRoom"}, + {"Id":513, "Name":"ConvertedRoomId"}, + {"Id":515, "Name":"StripItemNotTradeable"}, + {"Id":517, "Name":"ClientInfoFeedEnabled"}, + {"Id":518, "Name":"FaqClientFaqs"}, + {"Id":519, "Name":"FaqCategories"}, + {"Id":520, "Name":"FaqText"}, + {"Id":521, "Name":"FaqSearchResults"}, + {"Id":522, "Name":"FaqCategory"}, + {"Id":530, "Name":"IssueInfo"}, + {"Id":531, "Name":"ModeratorInit"}, + {"Id":532, "Name":"IssuePickFailed"}, + {"Id":533, "Name":"ModeratorUserInfo"}, + {"Id":534, "Name":"CfhChatLog"}, + {"Id":535, "Name":"RoomChatLog"}, + {"Id":536, "Name":"UserChatLog"}, + {"Id":537, "Name":"RoomVisits"}, + {"Id":538, "Name":"ModeratorRoomInfo"}, + {"Id":539, "Name":"ModeratorActionResult"}, + {"Id":540, "Name":"IssueCloseNotification"}, + {"Id":541, "Name":"HelpRequestSessionAttached"}, + {"Id":542, "Name":"HelpRequestSessionStarted"}, + {"Id":543, "Name":"HelpRequestSessionEnded"}, + {"Id":544, "Name":"HelpRequestSessionDetached"}, + {"Id":545, "Name":"GuideTicketSessionError"}, + {"Id":546, "Name":"HelpRequestSessionMessage"}, + {"Id":547, "Name":"HelpRequestSessionRequesterRoom"}, + {"Id":548, "Name":"HelpRequestSessionRequesterInvitedToGuideRoom"}, + {"Id":549, "Name":"HelpRequestSessionTyping"}, + {"Id":550, "Name":"GuideOnDutyStatus"}, + {"Id":551, "Name":"ModeratorToolPreferences"}, + {"Id":552, "Name":"CfhTopicsInit"}, + {"Id":553, "Name":"CfhNextSanction"}, + {"Id":554, "Name":"CfhSanctionStatus"}, + {"Id":555, "Name":"UpdateRoomFloorPropertiesResponse"}, + {"Id":560, "Name":"QuizData"}, + {"Id":561, "Name":"QuizResult"}, + {"Id":565, "Name":"TalentTrack"}, + {"Id":566, "Name":"TalentTrackLevelUp"}, + {"Id":567, "Name":"TalentTrackLevel"}, + {"Id":570, "Name":"ChangeUserNameResult"}, + {"Id":571, "Name":"CheckUserNameResult"}, + {"Id":572, "Name":"UserNameChanged"}, + {"Id":573, "Name":"HotelMergeNameChange"}, + {"Id":596, "Name":"ChangePetNameResult"}, + {"Id":597, "Name":"GetPetConfigurationsResult"}, + {"Id":598, "Name":"PetSupplementedNotification"}, + {"Id":599, "Name":"PetBreeding"}, + {"Id":600, "Name":"PetInventory"}, + {"Id":601, "Name":"PetInfo"}, + {"Id":602, "Name":"PetLevelNotification"}, + {"Id":603, "Name":"PetAddedToInventory"}, + {"Id":604, "Name":"PetRemovedFromInventory"}, + {"Id":605, "Name":"PetCommands"}, + {"Id":606, "Name":"PetRespectNotification"}, + {"Id":607, "Name":"PetReceivedNotification"}, + {"Id":608, "Name":"PetPlacingFailed"}, + {"Id":609, "Name":"PetExperience"}, + {"Id":610, "Name":"MarketplaceMakeOfferResult"}, + {"Id":611, "Name":"MarketplaceCanMakeOfferResult"}, + {"Id":612, "Name":"MarketplaceConfiguration"}, + {"Id":613, "Name":"MarketplaceBuyOfferResult"}, + {"Id":614, "Name":"MarketplaceCancelOfferResult"}, + {"Id":615, "Name":"MarketplaceOpenOfferList"}, + {"Id":616, "Name":"MarketplaceOwnOfferList"}, + {"Id":617, "Name":"MarketplaceItemStats"}, + {"Id":620, "Name":"GiftWrappingConfiguration"}, + {"Id":621, "Name":"PetRespectFailed"}, + {"Id":622, "Name":"IsOfferGiftable"}, + {"Id":623, "Name":"SelectableClubGiftInfo"}, + {"Id":624, "Name":"ClubGiftSelected"}, + {"Id":625, "Name":"HabboClubOffers"}, + {"Id":626, "Name":"LoginWithPasswordAccountsDEPRECATED"}, + {"Id":627, "Name":"BadgePointLimits"}, + {"Id":628, "Name":"ActivityPoints"}, + {"Id":629, "Name":"BonusRareInfo"}, + {"Id":630, "Name":"HabboClubExtendOffer"}, + {"Id":631, "Name":"DirectClubBuyAllowed"}, + {"Id":632, "Name":"XmasCalendarDailyOffer"}, + {"Id":633, "Name":"BadgeReceived"}, + {"Id":634, "Name":"HabboSnowWarGameTokensOffer"}, + {"Id":635, "Name":"BundleDiscountRuleset"}, + {"Id":636, "Name":"LimitedEditionSoldOut"}, + {"Id":637, "Name":"ProductOffer"}, + {"Id":638, "Name":"CatalogPageWithEarliestExpiry"}, + {"Id":639, "Name":"LimitedFurniTimingInfo"}, + {"Id":640, "Name":"EarnedCommunityGoalPrizes"}, + {"Id":642, "Name":"CatalogPageExpirationData"}, + {"Id":643, "Name":"CompetitionEntrySubmitResult"}, + {"Id":644, "Name":"CompetitionVotingInfo"}, + {"Id":645, "Name":"SecondsUntil"}, + {"Id":646, "Name":"CommunityGoalHallOfFame"}, + {"Id":647, "Name":"IsUserPartOfCompetition"}, + {"Id":648, "Name":"IsBadgeRequestFulfilled"}, + {"Id":649, "Name":"CurrentTimingCode"}, + {"Id":650, "Name":"UserDefinedRoomEventsTrigger"}, + {"Id":651, "Name":"UserDefinedRoomEventsAction"}, + {"Id":652, "Name":"UserDefinedRoomEventsCondition"}, + {"Id":653, "Name":"UserDefinedRoomEventsOpen"}, + {"Id":654, "Name":"UserDefinedRoomEventsRewardResult"}, + {"Id":655, "Name":"UserDefinedRoomEventsValidationError"}, + {"Id":656, "Name":"UserDefinedRoomEventsSaveSuccess"}, + {"Id":670, "Name":"ConcurrentUsersGoalProgress"}, + {"Id":671, "Name":"PromoArticles"}, + {"Id":672, "Name":"CommunityGoalVoteAcknowledged"}, + {"Id":680, "Name":"Notification"}, + {"Id":685, "Name":"OfferRewardDelivered"}, + {"Id":700, "Name":"GameYouArePlayer"}, + {"Id":701, "Name":"GameNumberValue"}, + {"Id":750, "Name":"PerkAllowances"}, + {"Id":754, "Name":"ChangePassword"}, + {"Id":760, "Name":"CitizenshipVipPromoEnable"}, + {"Id":761, "Name":"CaptchaResponse"}, + {"Id":790, "Name":"CommunityGoalProgress"}, + {"Id":791, "Name":"EpicPopup"}, + {"Id":792, "Name":"CompetitionRoomsData"}, + {"Id":793, "Name":"NoOwnedRoomsAlert"}, + {"Id":800, "Name":"Quests"}, + {"Id":801, "Name":"QuestCompleted"}, + {"Id":802, "Name":"Quest"}, + {"Id":803, "Name":"QuestCancelled"}, + {"Id":804, "Name":"QuestDaily"}, + {"Id":805, "Name":"QuestsSeasonal"}, + {"Id":809, "Name":"HandItemReceived"}, + {"Id":810, "Name":"MotdResponse"}, + {"Id":811, "Name":"MuteTimeRemaining"}, + {"Id":812, "Name":"GetDeviceTokensResponse"}, + {"Id":813, "Name":"LoginTokensCleared"}, + {"Id":815, "Name":"RestoreClientFromMinimizedState"}, + {"Id":825, "Name":"OpenPetPackageRequested"}, + {"Id":826, "Name":"OpenPetPackageResult"}, + {"Id":827, "Name":"SellablePetPalettes"}, + {"Id":828, "Name":"PetBreedingResult"}, + {"Id":831, "Name":"FriendBarFindFriendsResult"}, + {"Id":832, "Name":"UnseenElements"}, + {"Id":833, "Name":"FriendBarEventNotification"}, + {"Id":898, "Name":"AccountProgressedInfo"}, + {"Id":899, "Name":"AccountProgressionInfo"}, + {"Id":901, "Name":"AvatarList"}, + {"Id":902, "Name":"NewAvatarInfo"}, + {"Id":903, "Name":"DeactivateAvatarInfo"}, + {"Id":904, "Name":"InitialRooms"}, + {"Id":905, "Name":"InitialRoomSelected"}, + {"Id":911, "Name":"RequestSpamWallPostItMessage"}, + {"Id":912, "Name":"RoomMessageNotification"}, + {"Id":913, "Name":"PossibleAchievement"}, + {"Id":914, "Name":"RelationshipStatusInfo"}, + {"Id":915, "Name":"ExtendedProfile"}, + {"Id":916, "Name":"HabboGroupJoinFailed"}, + {"Id":917, "Name":"GuildCreationInfo"}, + {"Id":918, "Name":"GuildEditInfo"}, + {"Id":919, "Name":"GuildEditorData"}, + {"Id":921, "Name":"GuildMembershipUpdated"}, + {"Id":922, "Name":"GuildMembershipRejected"}, + {"Id":923, "Name":"GuildEditFailed"}, + {"Id":924, "Name":"ExtendedProfileChanged"}, + {"Id":925, "Name":"GuildMemberMgmtFailed"}, + {"Id":926, "Name":"GuildMembers"}, + {"Id":927, "Name":"GuildMemberships"}, + {"Id":928, "Name":"GuildCreated"}, + {"Id":929, "Name":"GuildMemberFurniCountInHq"}, + {"Id":930, "Name":"FlatControllers"}, + {"Id":931, "Name":"GroupMembershipRequested"}, + {"Id":932, "Name":"GroupDetailsChanged"}, + {"Id":943, "Name":"UsersBannedFromRoom"}, + {"Id":944, "Name":"UserUnbannedFromRoom"}, + {"Id":945, "Name":"MuteAllInRoom"}, + {"Id":960, "Name":"UpdatePetFigure"}, + {"Id":970, "Name":"GuildFurniContextmenuInfo"}, + {"Id":980, "Name":"MaintenanceStatus"}, + {"Id":988, "Name":"AvailableResolutionAchievements"}, + {"Id":989, "Name":"ResolutionProgress"}, + {"Id":990, "Name":"ConfirmBreedingRequest"}, + {"Id":991, "Name":"GoToBreedingNestFailure"}, + {"Id":992, "Name":"ConfirmBreedingResult"}, + {"Id":993, "Name":"NestBreedingSuccess"}, + {"Id":1001, "Name":"CustomUserNotification"}, + {"Id":1002, "Name":"MysteryBoxKeys"}, + {"Id":1003, "Name":"BuildersClubMembershipStatus"}, + {"Id":1004, "Name":"BuildersClubFurniCount"}, + {"Id":1020, "Name":"Navigator2MetaData"}, + {"Id":1021, "Name":"Navigator2SearchResultBlocks"}, + {"Id":1022, "Name":"Navigator2LiftArea"}, + {"Id":1023, "Name":"Navigator2UserPreferences"}, + {"Id":1024, "Name":"Navigator2UserSavedSearches"}, + {"Id":1025, "Name":"Navigator2UserCollapsedCategories"}, + {"Id":1300, "Name":"CraftableProducts"}, + {"Id":1301, "Name":"CraftingRecipe"}, + {"Id":1302, "Name":"CraftingResult"}, + {"Id":1304, "Name":"CraftingRecipesAvailable"}, + {"Id":1310, "Name":"ProductOffers"}, + {"Id":1400, "Name":"CampaignCalendarData"}, + {"Id":1401, "Name":"CampaignCalendarDoorOpened"}, + {"Id":1469, "Name":"PickUpAllFurniAndResetHeightmap"}, + {"Id":1471, "Name":"StackingHelperHeightUpdate"}, + {"Id":1472, "Name":"RoomEntryTile"}, + {"Id":1473, "Name":"RoomOccupiedTiles"}, + {"Id":1480, "Name":"YoutubeDisplayVideo"}, + {"Id":1481, "Name":"YoutubePlayLists"}, + {"Id":1482, "Name":"YoutubeControlVideo"}, + {"Id":1490, "Name":"RentableSpaceInfo"}, + {"Id":1491, "Name":"RentableSpaceRentOk"}, + {"Id":1492, "Name":"RentableSpaceRentFailed"}, + {"Id":1500, "Name":"TryPhoneNumberResult"}, + {"Id":1501, "Name":"VerifyCodeResult"}, + {"Id":1502, "Name":"VerificationState"}, + {"Id":1520, "Name":"NuxNotComplete"}, + {"Id":1521, "Name":"NuxGiftOffer"}, + {"Id":1530, "Name":"TargetedOffer"}, + {"Id":1531, "Name":"TargetedOfferNotFound"}, + {"Id":1532, "Name":"TargetedOfferList"}, + {"Id":1540, "Name":"GuideReportingStatus"}, + {"Id":1542, "Name":"ChatReviewSessionOfferedToGuide"}, + {"Id":1543, "Name":"ChatReviewSessionStarted"}, + {"Id":1544, "Name":"ChatReviewSessionResults"}, + {"Id":1545, "Name":"ChatReviewTicketCreationResult"}, + {"Id":1546, "Name":"ChatReviewSessionDetached"}, + {"Id":1547, "Name":"ChatReviewSessionVotingStatus"}, + {"Id":1548, "Name":"ChatReviewTicketResolution"}, + {"Id":1552, "Name":"AccountSafetyLock"}, + {"Id":1560, "Name":"PointElement"}, + {"Id":1561, "Name":"InClientLink"}, + {"Id":1562, "Name":"NoobnessLevel"}, + {"Id":1570, "Name":"SubmitGdprRequestResult"}, + {"Id":1571, "Name":"CancelGdprRequestResult"}, + {"Id":1572, "Name":"GetGdprRequestResult"}, + {"Id":1600, "Name":"ForumStats"}, + {"Id":1601, "Name":"ForumThreads"}, + {"Id":1602, "Name":"ForumThreadMessages"}, + {"Id":1604, "Name":"ForumThread"}, + {"Id":1605, "Name":"ForumMessage"}, + {"Id":1606, "Name":"ForumsList"}, + {"Id":1607, "Name":"UnreadForumsCount"}, + {"Id":1610, "Name":"PostForumThreadOk"}, + {"Id":1611, "Name":"PostForumMessageOk"}, + {"Id":1700, "Name":"UserClassifications"}, + {"Id":1701, "Name":"CameraToken"}, + {"Id":1702, "Name":"PhotoPurchaseSuccess"}, + {"Id":1703, "Name":"CameraPictureUrl"}, + {"Id":1704, "Name":"CameraPhotoPrice"}, + {"Id":1705, "Name":"PhotoPublishStatus"}, + {"Id":1706, "Name":"PhotoCompetitionEntryStatus"}, + {"Id":1720, "Name":"RoomThumbnailStatus"}, + {"Id":1750, "Name":"Question"}, + {"Id":1751, "Name":"QuestionAnswered"}, + {"Id":1752, "Name":"QuestionFinished"}, + {"Id":2550, "Name":"Reputation"}, + {"Id":2998, "Name":"InventoryFurniByRoomResult"}, + {"Id":3028, "Name":"BotInventory"}, + {"Id":3029, "Name":"BotAddedToInventory"}, + {"Id":3030, "Name":"BotRemovedFromInventory"}, + {"Id":3031, "Name":"BotReceived"}, + {"Id":3032, "Name":"BotError"}, + {"Id":3033, "Name":"BotCommandConfigurationData"}, + {"Id":3034, "Name":"BotUpdateSkillList"}, + {"Id":3035, "Name":"BotForceOpenContextMenu"}, + {"Id":3036, "Name":"FigureSetIds"}, + {"Id":3048, "Name":"MarketplaceCancelAllOffersResult"}, + {"Id":3049, "Name":"SendPetToHolidayResult"}, + {"Id":3240, "Name":"AgreementTypesResult"}, + {"Id":3242, "Name":"IdentityAgreements"}, + {"Id":3266, "Name":"GuildMembershipRequests"}, + {"Id":3299, "Name":"FlatFavouriteCount"}, + {"Id":3303, "Name":"StartCreateGuildResponse"}, + {"Id":3304, "Name":"CommitCreateGuildResponse"}, + {"Id":3600, "Name":"MeltdownWatchVerify"}, + {"Id":3700, "Name":"EarningStatus"}, + {"Id":3701, "Name":"ClaimEarningResult"}, + {"Id":3702, "Name":"VaultStatus"}, + {"Id":3800, "Name":"UpdateAccountPreferencesResult"}, + {"Id":3850, "Name":"LinkIdentificationMethodResult"}, + {"Id":3990, "Name":"BanInfo"}, + {"Id":3998, "Name":"AntiSpamTriggered"}, + {"Id":3999, "Name":"ServerDebugPong"}, + {"Id":4000, "Name":"DisconnectionReason"}, + {"Id":4001, "Name":"ProxyId"} +],"Outgoing":[ + {"Id":7, "Name":"InfoRetrieve"}, + {"Id":8, "Name":"GetCredits"}, + {"Id":12, "Name":"MessengerInit"}, + {"Id":15, "Name":"FriendListUpdate"}, + {"Id":19, "Name":"AddYourFavouriteRoom"}, + {"Id":20, "Name":"DeleteYourFavouriteRoom"}, + {"Id":23, "Name":"DeleteFlat"}, + {"Id":26, "Name":"SubscriptionGetUserInfo"}, + {"Id":27, "Name":"SubscriptionGetKickbackInfo"}, + {"Id":29, "Name":"CreateNewFlat"}, + {"Id":33, "Name":"SendMessage"}, + {"Id":34, "Name":"SendRoomInvite"}, + {"Id":37, "Name":"AcceptFriend"}, + {"Id":38, "Name":"DeclineFriend"}, + {"Id":39, "Name":"RequestFriend"}, + {"Id":40, "Name":"RemoveFriend"}, + {"Id":41, "Name":"HabboSearch"}, + {"Id":42, "Name":"ApprovePetName"}, + {"Id":44, "Name":"UpdateAvatar"}, + {"Id":45, "Name":"CustomizeAvatarWithFurni"}, + {"Id":52, "Name":"Chat"}, + {"Id":53, "Name":"Quit"}, + {"Id":55, "Name":"Shout"}, + {"Id":56, "Name":"Whisper"}, + {"Id":58, "Name":"PickUpPetFromRoom"}, + {"Id":59, "Name":"GoToFlat"}, + {"Id":61, "Name":"PickUpAllItemsFromRoom"}, + {"Id":66, "Name":"FlatPropertyByItem"}, + {"Id":67, "Name":"PickItemUpFromRoom"}, + {"Id":68, "Name":"TradeUnaccept"}, + {"Id":69, "Name":"TradeAccept"}, + {"Id":70, "Name":"TradeClose"}, + {"Id":71, "Name":"TradeOpen"}, + {"Id":72, "Name":"TradeAddItem"}, + {"Id":73, "Name":"MoveRoomItem"}, + {"Id":74, "Name":"SetStuffData"}, + {"Id":75, "Name":"Move"}, + {"Id":76, "Name":"ThrowDice"}, + {"Id":77, "Name":"DiceOff"}, + {"Id":78, "Name":"PresentOpen"}, + {"Id":79, "Name":"LookTo"}, + {"Id":80, "Name":"PassHandItem"}, + {"Id":81, "Name":"DropHandItem"}, + {"Id":82, "Name":"PassHandItemToPet"}, + {"Id":83, "Name":"GetItemData"}, + {"Id":84, "Name":"SetStickyData"}, + {"Id":85, "Name":"RemoveItem"}, + {"Id":86, "Name":"Posture"}, + {"Id":87, "Name":"GiveSupplementToPet"}, + {"Id":89, "Name":"TradeAddItems"}, + {"Id":90, "Name":"PlaceStuffFromStripDEPRECATED"}, + {"Id":91, "Name":"MoveItemDEPRECATED"}, + {"Id":92, "Name":"AmbassadorAlert"}, + {"Id":93, "Name":"Dance"}, + {"Id":94, "Name":"Expression"}, + {"Id":95, "Name":"KickUser"}, + {"Id":96, "Name":"AssignRights"}, + {"Id":97, "Name":"RemoveRights"}, + {"Id":99, "Name":"RemoveOwnRights"}, + {"Id":100, "Name":"PurchaseFromCatalog"}, + {"Id":101, "Name":"GetCatalogIndex"}, + {"Id":102, "Name":"GetCatalogPage"}, + {"Id":104, "Name":"ShowSign"}, + {"Id":105, "Name":"PlaceRoomItem"}, + {"Id":106, "Name":"PlaceWallItem"}, + {"Id":108, "Name":"MoveWallItem"}, + {"Id":114, "Name":"RelinkTeleports"}, + {"Id":115, "Name":"Goaway"}, + {"Id":117, "Name":"GetDonationSettings"}, + {"Id":118, "Name":"Donate"}, + {"Id":120, "Name":"GetRentOrBuyoutOffer"}, + {"Id":121, "Name":"ExtendRentOrBuyoutFurniInRoom"}, + {"Id":122, "Name":"ExtendRentOrBuyoutFurniInInventory"}, + {"Id":129, "Name":"RedeemVoucherCode"}, + {"Id":130, "Name":"RedeemVoucherCodeWithHc"}, + {"Id":151, "Name":"GetUserFlatCategories"}, + {"Id":152, "Name":"GetEventFlatCats"}, + {"Id":155, "Name":"RemoveAllRights"}, + {"Id":157, "Name":"GetAvailableBadges"}, + {"Id":158, "Name":"SetSelectedBadges"}, + {"Id":159, "Name":"GetSelectedBadges"}, + {"Id":181, "Name":"InterstitialShown"}, + {"Id":182, "Name":"GetInterstitial"}, + {"Id":183, "Name":"ConvertFurniToCredits"}, + {"Id":196, "Name":"Pong"}, + {"Id":197, "Name":"ClientSuspended"}, + {"Id":198, "Name":"ClientResumed"}, + {"Id":200, "Name":"ModerationAction"}, + {"Id":207, "Name":"InitDhHandshake"}, + {"Id":208, "Name":"CompleteDhHandshake"}, + {"Id":211, "Name":"RoomQueueChange"}, + {"Id":212, "Name":"GetOpeningHours"}, + {"Id":214, "Name":"SetWallItemAnimationState"}, + {"Id":215, "Name":"GetFurniAliases"}, + {"Id":216, "Name":"GetSpectatorAmount"}, + {"Id":221, "Name":"GetSongInfo"}, + {"Id":222, "Name":"GetSongId"}, + {"Id":227, "Name":"SetChatPreferences"}, + {"Id":228, "Name":"GetAccountPreferences"}, + {"Id":229, "Name":"SetSoundSettings"}, + {"Id":230, "Name":"GetHabboGroupBadges"}, + {"Id":231, "Name":"GetHabboGroupDetails"}, + {"Id":232, "Name":"EnterOneWayDoor"}, + {"Id":233, "Name":"GetFriendRequests"}, + {"Id":234, "Name":"StartPoll"}, + {"Id":235, "Name":"RejectPoll"}, + {"Id":236, "Name":"PollAnswer"}, + {"Id":237, "Name":"GetPendingCallsForHelp"}, + {"Id":238, "Name":"DeletePendingCallsForHelp"}, + {"Id":240, "Name":"SetRoomInvitePreferences"}, + {"Id":241, "Name":"SetRoomCameraPreferences"}, + {"Id":242, "Name":"SetNewNavigatorPreferences"}, + {"Id":243, "Name":"SetUiFlags"}, + {"Id":244, "Name":"SetChatStylePreference"}, + {"Id":245, "Name":"GetSoundMachinePlayList"}, + {"Id":247, "Name":"SpinWheelOfFortune"}, + {"Id":249, "Name":"GetNowPlaying"}, + {"Id":255, "Name":"AddJukeboxDisc"}, + {"Id":256, "Name":"RemoveJukeboxDisc"}, + {"Id":258, "Name":"GetJukeboxDiscs"}, + {"Id":259, "Name":"GetUserSongDiscs"}, + {"Id":260, "Name":"RemoveAllJukeboxDiscs"}, + {"Id":261, "Name":"RateFlat"}, + {"Id":262, "Name":"FollowFriend"}, + {"Id":264, "Name":"VisitUser"}, + {"Id":314, "Name":"SetFurniRandomState"}, + {"Id":315, "Name":"ClientLatencyPingRequest"}, + {"Id":316, "Name":"ClientLatencyPingReport"}, + {"Id":317, "Name":"UserStartTyping"}, + {"Id":318, "Name":"UserCancelTyping"}, + {"Id":319, "Name":"IgnoreUser"}, + {"Id":320, "Name":"IgnoreAvatarId"}, + {"Id":321, "Name":"GetIgnoreList"}, + {"Id":322, "Name":"UnignoreUser"}, + {"Id":330, "Name":"RoomBanWithDuration"}, + {"Id":331, "Name":"RoomMuteUser"}, + {"Id":332, "Name":"RoomMuteUnmuteAll"}, + {"Id":333, "Name":"RoomUnmuteUser"}, + {"Id":341, "Name":"RoomDimmerEditPresets"}, + {"Id":342, "Name":"RoomDimmerSavePreset"}, + {"Id":343, "Name":"RoomDimmerChangeState"}, + {"Id":344, "Name":"RoomMuteAll"}, + {"Id":347, "Name":"RoomAdCancelAd"}, + {"Id":348, "Name":"RoomAdEditAd"}, + {"Id":349, "Name":"PurchaseRoomAd"}, + {"Id":352, "Name":"RoomAdGetRooms"}, + {"Id":353, "Name":"RoomAdListAds"}, + {"Id":354, "Name":"RoomAdEventTabViewed"}, + {"Id":356, "Name":"RoomAdEventTabAdClicked"}, + {"Id":357, "Name":"RoomAdPurchaseInitiated"}, + {"Id":370, "Name":"GetUserAchievements"}, + {"Id":371, "Name":"RespectUser"}, + {"Id":372, "Name":"UseAvatarEffect"}, + {"Id":373, "Name":"ActivateAvatarEffect"}, + {"Id":374, "Name":"PurchaseAndActivateAvatarEffect"}, + {"Id":375, "Name":"GetWardrobe"}, + {"Id":376, "Name":"SaveWardrobeOutfit"}, + {"Id":377, "Name":"MysteryBoxWaitingCanceled"}, + {"Id":378, "Name":"ResetResolutionAchievement"}, + {"Id":379, "Name":"GetUserAchievementsForAResolution"}, + {"Id":380, "Name":"GetOfficialRooms"}, + {"Id":381, "Name":"FriendFurnitureLockConfirm"}, + {"Id":382, "Name":"GetPopularRoomTags"}, + {"Id":383, "Name":"GetCategoriesWithVisitorCount"}, + {"Id":384, "Name":"UpdateNavigatorSettings"}, + {"Id":385, "Name":"GetGuestRoom"}, + {"Id":386, "Name":"UpdateRoomThumbnail"}, + {"Id":387, "Name":"CanCreateRoom"}, + {"Id":388, "Name":"ConvertGlobalRoomId"}, + {"Id":390, "Name":"GetRoomEntryData"}, + {"Id":391, "Name":"FlatOpc"}, + {"Id":392, "Name":"UseStuff"}, + {"Id":393, "Name":"UseWallItem"}, + {"Id":400, "Name":"GetRoomSettings"}, + {"Id":401, "Name":"SaveRoomSettings"}, + {"Id":402, "Name":"TradeConfirmAccept"}, + {"Id":403, "Name":"TradeConfirmDecline"}, + {"Id":404, "Name":"GetInventory"}, + {"Id":405, "Name":"TradeRemoveItem"}, + {"Id":406, "Name":"GetInventoryPeer"}, + {"Id":407, "Name":"UpdateRoomFilter"}, + {"Id":408, "Name":"GetRoomFilter"}, + {"Id":409, "Name":"UpdateRoomCategoryAndTrade"}, + {"Id":410, "Name":"StageStartPerformance"}, + {"Id":411, "Name":"StageVotePerformance"}, + {"Id":415, "Name":"LoginWithTicket"}, + {"Id":416, "Name":"GetClientFaqs"}, + {"Id":417, "Name":"GetFaqCategories"}, + {"Id":418, "Name":"GetFaqText"}, + {"Id":419, "Name":"SearchFaqs"}, + {"Id":420, "Name":"GetFaqCategory"}, + {"Id":421, "Name":"LogFlashPerformance"}, + {"Id":422, "Name":"LogLagWarning"}, + {"Id":423, "Name":"LogAirPerformance"}, + {"Id":430, "Name":"PopularRoomsSearch"}, + {"Id":431, "Name":"RoomsWithHighestScoreSearch"}, + {"Id":432, "Name":"MyFriendsRoomsSearch"}, + {"Id":433, "Name":"RoomsWhereMyFriendsAreSearch"}, + {"Id":434, "Name":"MyRoomsSearch"}, + {"Id":435, "Name":"MyFavouriteRoomsSearch"}, + {"Id":436, "Name":"MyRoomHistorySearch"}, + {"Id":437, "Name":"RoomTextSearch"}, + {"Id":438, "Name":"RoomTagSearch"}, + {"Id":440, "Name":"MyFrequentlyVisitedRoomsSearch"}, + {"Id":442, "Name":"GuildBaseSearch"}, + {"Id":443, "Name":"RoomAdSearch"}, + {"Id":444, "Name":"MyRoomRightsSearch"}, + {"Id":445, "Name":"MyGuildBasesSearch"}, + {"Id":446, "Name":"MyRecommendedRoomsSearch"}, + {"Id":449, "Name":"CloseIssueDefaultAction"}, + {"Id":450, "Name":"PickIssues"}, + {"Id":451, "Name":"ReleaseIssues"}, + {"Id":452, "Name":"CloseIssues"}, + {"Id":453, "Name":"CreateIssue"}, + {"Id":454, "Name":"GetModeratorUserInfo"}, + {"Id":455, "Name":"GetUserChatLog"}, + {"Id":456, "Name":"GetRoomChatLog"}, + {"Id":457, "Name":"GetCfhChatLog"}, + {"Id":458, "Name":"GetRoomVisits"}, + {"Id":459, "Name":"GetModeratorRoomInfo"}, + {"Id":460, "Name":"ModerateRoom"}, + {"Id":461, "Name":"ModAlert"}, + {"Id":462, "Name":"ModMessage"}, + {"Id":463, "Name":"ModKick"}, + {"Id":464, "Name":"ModBan"}, + {"Id":465, "Name":"ModMute"}, + {"Id":466, "Name":"ModToolPreferences"}, + {"Id":467, "Name":"ModToolNextSanction"}, + {"Id":468, "Name":"ModTradingLock"}, + {"Id":469, "Name":"CreateImIssue"}, + {"Id":470, "Name":"ChangeAvatarNameInRoom"}, + {"Id":471, "Name":"CheckAvatarName"}, + {"Id":472, "Name":"PurchaseFromCatalogAsGift"}, + {"Id":473, "Name":"GetGiftWrappingConfiguration"}, + {"Id":474, "Name":"GetSelectableClubGiftInfo"}, + {"Id":475, "Name":"SelectClubGift"}, + {"Id":476, "Name":"ChangeAvatarName"}, + {"Id":477, "Name":"DefaultSanction"}, + {"Id":478, "Name":"GetRoomTypes"}, + {"Id":480, "Name":"SetClothingChangeFurnitureData"}, + {"Id":482, "Name":"LogToEventLog"}, + {"Id":483, "Name":"ToggleRoomStaffPick"}, + {"Id":484, "Name":"ChangeAvatarMotto"}, + {"Id":485, "Name":"ForwardToARandomPromotedRoom"}, + {"Id":486, "Name":"ForwardToSomeRoom"}, + {"Id":490, "Name":"FriendBarHelperFindFriends"}, + {"Id":491, "Name":"SetRelationshipStatus"}, + {"Id":492, "Name":"GetRelationshipStatusInfo"}, + {"Id":500, "Name":"GetEventStream"}, + {"Id":501, "Name":"SetEventStreamPublishingAllowed"}, + {"Id":502, "Name":"StreamLike"}, + {"Id":503, "Name":"StreamStatus"}, + {"Id":504, "Name":"GetEventStreamForAccount"}, + {"Id":505, "Name":"StreamComment"}, + {"Id":506, "Name":"GetStreamNotificationCount"}, + {"Id":507, "Name":"GetStreamNotifications"}, + {"Id":512, "Name":"Disconnect"}, + {"Id":520, "Name":"GetCommunityGoalProgress"}, + {"Id":521, "Name":"SubmitRoomToCompetition"}, + {"Id":522, "Name":"RoomCompetitionInit"}, + {"Id":523, "Name":"VoteForRoom"}, + {"Id":524, "Name":"GetSecondsUntil"}, + {"Id":525, "Name":"ForwardToRandomCompetitionRoom"}, + {"Id":526, "Name":"CompetitionRoomsSearch"}, + {"Id":527, "Name":"GetCommunityGoalHallOfFame"}, + {"Id":528, "Name":"ForwardToACompetitionRoom"}, + {"Id":529, "Name":"IsUserPartOfCompetition"}, + {"Id":530, "Name":"GetCurrentTimingCode"}, + {"Id":531, "Name":"ForwardToASubmittableRoom"}, + {"Id":532, "Name":"GetConcurrentUsersGoalProgress"}, + {"Id":533, "Name":"RequestConcurrentUsersGoalReward"}, + {"Id":541, "Name":"HelpRequestSessionCreate"}, + {"Id":542, "Name":"HelpRequestSessionGuideDecides"}, + {"Id":543, "Name":"HelpRequestSessionRequesterCancels"}, + {"Id":544, "Name":"HelpRequestSessionResolved"}, + {"Id":545, "Name":"HelpRequestSessionFeedback"}, + {"Id":546, "Name":"GuideOnDutyUpdate"}, + {"Id":547, "Name":"HelpRequestSessionMessage"}, + {"Id":548, "Name":"HelpRequestSessionGetRequesterRoom"}, + {"Id":550, "Name":"HelpRequestSessionReported"}, + {"Id":551, "Name":"HelpRequestSessionInviteRequester"}, + {"Id":552, "Name":"HelpRequestSessionTyping"}, + {"Id":560, "Name":"QuizGetQuestions"}, + {"Id":561, "Name":"QuizPostAnswers"}, + {"Id":565, "Name":"GetTalentTrack"}, + {"Id":566, "Name":"GetTalentTrackLevel"}, + {"Id":567, "Name":"GuideAdvertisementRead"}, + {"Id":596, "Name":"ChangePetName"}, + {"Id":597, "Name":"GetPetConfigurations"}, + {"Id":754, "Name":"ChangePassword"}, + {"Id":755, "Name":"LoginWithPassword"}, + {"Id":756, "Name":"LoginWithPasswordDEPRECATED"}, + {"Id":757, "Name":"LoginWithFacebookToken"}, + {"Id":758, "Name":"LoginWithToken"}, + {"Id":759, "Name":"CreateAccount"}, + {"Id":761, "Name":"CaptchaRequest"}, + {"Id":811, "Name":"ClearDeviceLoginToken"}, + {"Id":812, "Name":"GetDeviceLoginTokens"}, + {"Id":813, "Name":"UniqueMachineId"}, + {"Id":815, "Name":"ClientStatistics"}, + {"Id":899, "Name":"GetAccountProgressionInfo"}, + {"Id":900, "Name":"GetHotlooks"}, + {"Id":901, "Name":"GetAvatarList"}, + {"Id":902, "Name":"CreateNewAvatar"}, + {"Id":903, "Name":"DeactivateAvatar"}, + {"Id":904, "Name":"GetInitialRooms"}, + {"Id":905, "Name":"SelectInitialRoom"}, + {"Id":1000, "Name":"GetCfhStatus"}, + {"Id":1010, "Name":"RoomNetworkForward"}, + {"Id":1020, "Name":"Navigator2Init"}, + {"Id":1021, "Name":"Navigator2Search"}, + {"Id":1023, "Name":"Navigator2AddSavedSearch"}, + {"Id":1024, "Name":"Navigator2DeleteSavedSearch"}, + {"Id":1025, "Name":"Navigator2AddCollapsedCategory"}, + {"Id":1026, "Name":"Navigator2RemoveCollapsedCategory"}, + {"Id":1027, "Name":"Navigator2SetSearchCodeViewMode"}, + {"Id":1170, "Name":"VersionCheck"}, + {"Id":1300, "Name":"GetCraftableProducts"}, + {"Id":1301, "Name":"GetCraftingRecipe"}, + {"Id":1302, "Name":"Craft"}, + {"Id":1303, "Name":"CraftSecret"}, + {"Id":1304, "Name":"GetCraftingRecipesAvailable"}, + {"Id":1310, "Name":"GetProductOffers"}, + {"Id":1401, "Name":"OpenCampaignCalendarDoor"}, + {"Id":1402, "Name":"StaffOpenCampaignCalendarDoor"}, + {"Id":1469, "Name":"PickUpAllFurniAndResetHeightmap"}, + {"Id":1470, "Name":"UpdateRoomFloorProperties"}, + {"Id":1471, "Name":"StackingHelperSetCaretHeight"}, + {"Id":1473, "Name":"GetRoomEntryTile"}, + {"Id":1474, "Name":"GetRoomOccupiedTiles"}, + {"Id":1480, "Name":"YoutubeDisplayGetStatus"}, + {"Id":1481, "Name":"YoutubeDisplaySetPlaylist"}, + {"Id":1482, "Name":"YoutubeDisplayControlPlayback"}, + {"Id":1490, "Name":"RentableSpaceGetInfo"}, + {"Id":1491, "Name":"RentableSpaceRentSpace"}, + {"Id":1492, "Name":"RentableSpaceExtendRent"}, + {"Id":1493, "Name":"RentableSpaceCancel"}, + {"Id":1494, "Name":"BuildersClubPlaceRoomItem"}, + {"Id":1495, "Name":"BuildersClubPlaceWallItem"}, + {"Id":1496, "Name":"BuildersClubQueryFurniCount"}, + {"Id":1500, "Name":"TryPhoneNumber"}, + {"Id":1501, "Name":"VerifyCode"}, + {"Id":1502, "Name":"SetPhoneNumberCollectionStatus"}, + {"Id":1503, "Name":"GiveGift"}, + {"Id":1504, "Name":"RestartPhoneNumberCollection"}, + {"Id":1505, "Name":"GiveStarGems"}, + {"Id":1522, "Name":"NuxGetGifts"}, + {"Id":1523, "Name":"ScriptProceed"}, + {"Id":1530, "Name":"GetNextTargetedOffer"}, + {"Id":1531, "Name":"PurchaseTargetedOffer"}, + {"Id":1532, "Name":"SetTargetedOfferState"}, + {"Id":1533, "Name":"ShopTargetedOfferViewed"}, + {"Id":1534, "Name":"GetTargetedOffer"}, + {"Id":1535, "Name":"GetTargetedOfferList"}, + {"Id":1540, "Name":"GetGuideReportingStatus"}, + {"Id":1541, "Name":"ChatReviewSessionCreate"}, + {"Id":1542, "Name":"ChatReviewSessionGuideDecidesOnOffer"}, + {"Id":1544, "Name":"ChatReviewSessionGuideVote"}, + {"Id":1546, "Name":"ChatReviewSessionGuideDetached"}, + {"Id":1550, "Name":"AccountSafetylockGetQuestions"}, + {"Id":1551, "Name":"AccountSafetylockUnlock"}, + {"Id":1552, "Name":"AccountSafetyLock"}, + {"Id":1570, "Name":"SubmitGdprRequest"}, + {"Id":1571, "Name":"CancelGdprRequest"}, + {"Id":1572, "Name":"GetGdprRequest"}, + {"Id":1600, "Name":"GetForumStats"}, + {"Id":1601, "Name":"GetForumThreads"}, + {"Id":1602, "Name":"GetForumThreadMessages"}, + {"Id":1603, "Name":"UpdateForumReadMarkers"}, + {"Id":1604, "Name":"GetForumThread"}, + {"Id":1605, "Name":"GetForumsList"}, + {"Id":1606, "Name":"UpdateForumSettings"}, + {"Id":1607, "Name":"GetUnreadForumsCount"}, + {"Id":1610, "Name":"PostForumMessage"}, + {"Id":1612, "Name":"ModerateForumThread"}, + {"Id":1613, "Name":"ModerateForumMessage"}, + {"Id":1614, "Name":"ReportForumThread"}, + {"Id":1615, "Name":"ReportForumMessage"}, + {"Id":1616, "Name":"UpdateForumThread"}, + {"Id":1700, "Name":"GetRoomUsersClassification"}, + {"Id":1701, "Name":"GetPeerUsersClassification"}, + {"Id":1800, "Name":"ReportSelfie"}, + {"Id":1801, "Name":"ReportPhoto"}, + {"Id":1900, "Name":"UserFeedback"}, + {"Id":2550, "Name":"GetReputation"}, + {"Id":2998, "Name":"GetFurniByRoomInventory"}, + {"Id":2999, "Name":"GetInventoryForDebugging"}, + {"Id":3000, "Name":"GetPetInventory"}, + {"Id":3001, "Name":"GetNewPetInfo"}, + {"Id":3002, "Name":"PlacePetToFlat"}, + {"Id":3003, "Name":"RemovePetFromFlat"}, + {"Id":3004, "Name":"GetAvailablePetCommands"}, + {"Id":3005, "Name":"RespectPet"}, + {"Id":3006, "Name":"OpenPetPackage"}, + {"Id":3007, "Name":"GetSellablePetPalettes"}, + {"Id":3008, "Name":"CustomizePetWithFurni"}, + {"Id":3009, "Name":"RemoveSaddle"}, + {"Id":3010, "Name":"MarketplaceMakeOffer"}, + {"Id":3011, "Name":"MarketplaceGetConfiguration"}, + {"Id":3012, "Name":"MarketplaceCanMakeOffer"}, + {"Id":3013, "Name":"MarketplaceBuyTokens"}, + {"Id":3014, "Name":"MarketplaceBuyOffer"}, + {"Id":3015, "Name":"MarketplaceCancelOffer"}, + {"Id":3016, "Name":"MarketplaceRedeemOfferCredits"}, + {"Id":3017, "Name":"MountPet"}, + {"Id":3018, "Name":"MarketplaceSearchOffers"}, + {"Id":3019, "Name":"MarketplaceListOwnOffers"}, + {"Id":3020, "Name":"MarketplaceGetItemStats"}, + {"Id":3021, "Name":"TogglePetRidingAccessRights"}, + {"Id":3022, "Name":"PetSelected"}, + {"Id":3023, "Name":"MovePetInFlat"}, + {"Id":3024, "Name":"BreedPets"}, + {"Id":3025, "Name":"HarvestPet"}, + {"Id":3026, "Name":"TogglePetBreedingRights"}, + {"Id":3027, "Name":"PlaceBotToFlat"}, + {"Id":3028, "Name":"GetBotInventory"}, + {"Id":3029, "Name":"CommandBot"}, + {"Id":3030, "Name":"GetIsOfferGiftable"}, + {"Id":3031, "Name":"GetHabboClubOffers"}, + {"Id":3032, "Name":"GetBadgePointLimits"}, + {"Id":3033, "Name":"ChargeStuff"}, + {"Id":3034, "Name":"MarkCatalogNewAdditionsPageOpened"}, + {"Id":3035, "Name":"GetHabboVipMembershipExtendOffer"}, + {"Id":3036, "Name":"PurchaseDiscountedVipMembershipExtension"}, + {"Id":3037, "Name":"PurchaseDiscountedBasicMembershipExtension"}, + {"Id":3038, "Name":"GetHabboBasicMembershipExtendOffer"}, + {"Id":3039, "Name":"GetSnowWarGameTokensOffer"}, + {"Id":3040, "Name":"PurchaseSnowWarGameTokens"}, + {"Id":3041, "Name":"GetBundleDiscountRuleset"}, + {"Id":3042, "Name":"GetProductOffer"}, + {"Id":3043, "Name":"RequestBadge"}, + {"Id":3044, "Name":"GetIsBadgeRequestFulfilled"}, + {"Id":3045, "Name":"ConfirmPetBreeding"}, + {"Id":3046, "Name":"CancelPetBreeding"}, + {"Id":3047, "Name":"CompostPlant"}, + {"Id":3048, "Name":"MarketplaceCancelAllOffers"}, + {"Id":3049, "Name":"SendPetToHoliday"}, + {"Id":3050, "Name":"UserDefinedRoomEventsUpdateTrigger"}, + {"Id":3051, "Name":"UserDefinedRoomEventsUpdateAction"}, + {"Id":3052, "Name":"UserDefinedRoomEventsUpdateCondition"}, + {"Id":3053, "Name":"UserDefinedRoomEventsOpen"}, + {"Id":3054, "Name":"UserDefinedRoomEventsApplySnapshot"}, + {"Id":3060, "Name":"RemoveBotFromFlat"}, + {"Id":3101, "Name":"GetQuests"}, + {"Id":3102, "Name":"AcceptQuest"}, + {"Id":3106, "Name":"RejectQuest"}, + {"Id":3107, "Name":"OpenQuestTracker"}, + {"Id":3108, "Name":"StartCampaign"}, + {"Id":3109, "Name":"GetDailyQuest"}, + {"Id":3110, "Name":"GetMessageOfTheDay"}, + {"Id":3111, "Name":"ResetUnseenCounter"}, + {"Id":3112, "Name":"ActivateQuest"}, + {"Id":3113, "Name":"CancelQuest"}, + {"Id":3114, "Name":"GetSeasonalQuests"}, + {"Id":3210, "Name":"FriendRequestQuestComplete"}, + {"Id":3240, "Name":"GetIdentityAgreementTypes"}, + {"Id":3241, "Name":"SaveAgreements"}, + {"Id":3242, "Name":"GetIdentityAgreements"}, + {"Id":3250, "Name":"GetEmailStatus"}, + {"Id":3251, "Name":"ChangeEmail"}, + {"Id":3254, "Name":"PlacePostIt"}, + {"Id":3255, "Name":"AddSpamWallPostIt"}, + {"Id":3256, "Name":"SetMannequinFigure"}, + {"Id":3257, "Name":"JoinHabboGroup"}, + {"Id":3258, "Name":"SelectFavouriteHabboGroup"}, + {"Id":3259, "Name":"DeselectFavouriteHabboGroup"}, + {"Id":3260, "Name":"GetExtendedProfile"}, + {"Id":3261, "Name":"GetGuildCreationInfo"}, + {"Id":3262, "Name":"CreateGuild"}, + {"Id":3263, "Name":"GetGuildEditInfo"}, + {"Id":3264, "Name":"UpdateGuildIdentity"}, + {"Id":3265, "Name":"GetGuildEditorData"}, + {"Id":3266, "Name":"GetGuildMembershipRequests"}, + {"Id":3267, "Name":"ApproveMembershipRequest"}, + {"Id":3268, "Name":"RejectMembershipRequest"}, + {"Id":3269, "Name":"ApproveAllMembershipRequests"}, + {"Id":3270, "Name":"RemoveAdminRightsFromMember"}, + {"Id":3271, "Name":"AddAdminRightsToMember"}, + {"Id":3272, "Name":"KickMember"}, + {"Id":3273, "Name":"UpdateGuildSettings"}, + {"Id":3274, "Name":"GetFlatControllers"}, + {"Id":3275, "Name":"UpdateGuildBadge"}, + {"Id":3276, "Name":"UpdateGuildColors"}, + {"Id":3277, "Name":"GetGuildMembers"}, + {"Id":3278, "Name":"GetGuildMemberships"}, + {"Id":3279, "Name":"GuildMemberHqFurniCount"}, + {"Id":3280, "Name":"SetMannequinName"}, + {"Id":3281, "Name":"GetGuildFurniContextMenuInfo"}, + {"Id":3282, "Name":"GetDirectClubBuyAllowed"}, + {"Id":3283, "Name":"GetExtendedProfileByUsername"}, + {"Id":3284, "Name":"GetSeasonalCalendarDailyOffer"}, + {"Id":3285, "Name":"SetRoomSessionTags"}, + {"Id":3286, "Name":"GetCatalogPageWithEarliestExpiry"}, + {"Id":3287, "Name":"GetLimitedFurniTimingInfo"}, + {"Id":3288, "Name":"GetCommunityGoalEarnedPrizes"}, + {"Id":3289, "Name":"RedeemCommunityGoalPrize"}, + {"Id":3290, "Name":"DeactivateGuild"}, + {"Id":3291, "Name":"GetCatalogPageExpiration"}, + {"Id":3292, "Name":"SetRoomBackgroundColorData"}, + {"Id":3293, "Name":"GetBannedUsers"}, + {"Id":3294, "Name":"RoomUnbanUser"}, + {"Id":3295, "Name":"GetPromoArticles"}, + {"Id":3296, "Name":"GetBonusRareInfo"}, + {"Id":3297, "Name":"OpenMysteryTrophy"}, + {"Id":3298, "Name":"CommunityGoalVote"}, + {"Id":3299, "Name":"GetFlatFavouriteCount"}, + {"Id":3302, "Name":"GetExternalImageFurniData"}, + {"Id":3303, "Name":"StartCreateGuild"}, + {"Id":3304, "Name":"CommitCreateGuild"}, + {"Id":3330, "Name":"GetBotCommandConfigurationData"}, + {"Id":3420, "Name":"RemoveUnseenElements"}, + {"Id":3421, "Name":"RemoveUnseenElement"}, + {"Id":3501, "Name":"RequestCameraToken"}, + {"Id":3502, "Name":"RenderRoom"}, + {"Id":3503, "Name":"PurchasePhoto"}, + {"Id":3504, "Name":"PublishPhoto"}, + {"Id":3505, "Name":"InitCamera"}, + {"Id":3506, "Name":"CompetitionPhoto"}, + {"Id":3520, "Name":"RenderAndSaveRoomThumbnailPhoto"}, + {"Id":3600, "Name":"MeltdownWatchVerify"}, + {"Id":3700, "Name":"EarningStatus"}, + {"Id":3701, "Name":"ClaimEarning"}, + {"Id":3702, "Name":"VaultStatus"}, + {"Id":3703, "Name":"WithdrawVault"}, + {"Id":3800, "Name":"UpdateAccountPreferences"}, + {"Id":3850, "Name":"UnlinkIdentificationMethod"}, + {"Id":3851, "Name":"LinkIdentificationMethod"}, + {"Id":3900, "Name":"ClientDebugPingPeerSlow"}, + {"Id":3901, "Name":"ClientDebugPingPeerFast"}, + {"Id":3902, "Name":"ClientDebugPingRoomSlow"}, + {"Id":3903, "Name":"ClientDebugPingRoomFast"}, + {"Id":3908, "Name":"ClientDebugPingMessengerSlow"}, + {"Id":3909, "Name":"ClientDebugPingMessengerFast"}, + {"Id":3910, "Name":"ClientDebugPingNavigatorSlow"}, + {"Id":3911, "Name":"ClientDebugPingNavigatorFast"}, + {"Id":3912, "Name":"ClientDebugPingRoomDirectorySlow"}, + {"Id":3913, "Name":"ClientDebugPingRoomDirectoryFast"}, + {"Id":3914, "Name":"ClientDebugPingProxySlow"}, + {"Id":3915, "Name":"ClientDebugPingProxyFast"}, + {"Id":4000, "Name":"Hello"}, + {"Id":4001, "Name":"GetProxyId"} +]} \ No newline at end of file diff --git a/G-Earth/src/main/resources/build/windows/32bit/G-Earth.exe b/G-Earth/src/main/resources/build/windows/32bit/G-Earth.exe index 7a025e6..2ea409e 100644 Binary files a/G-Earth/src/main/resources/build/windows/32bit/G-Earth.exe and b/G-Earth/src/main/resources/build/windows/32bit/G-Earth.exe differ diff --git a/G-Earth/src/main/resources/build/windows/64bit/G-Earth.exe b/G-Earth/src/main/resources/build/windows/64bit/G-Earth.exe index 7a025e6..e0538bb 100644 Binary files a/G-Earth/src/main/resources/build/windows/64bit/G-Earth.exe and b/G-Earth/src/main/resources/build/windows/64bit/G-Earth.exe differ diff --git a/G-Earth/src/main/resources/gearth/services/gpython/init_script.py b/G-Earth/src/main/resources/gearth/services/gpython/init_script.py new file mode 100644 index 0000000..c0a750c --- /dev/null +++ b/G-Earth/src/main/resources/gearth/services/gpython/init_script.py @@ -0,0 +1,42 @@ +from time import sleep + +from g_python.gextension import Extension +from g_python.hmessage import Direction, HMessage +from g_python.hpacket import HPacket +from g_python import hparsers +from g_python import htools + +extension_info = {"title": "$G_PYTHON_SHELL_TITLE$", "description": "G-Python scripting console", "version": "1.0", "author": ""} + +ext = Extension(extension_info, ["--port", "$G_EARTH_PORT$", "--auth-token", "$COOKIE$"], {"can_leave": False}) +ext.start() + + +def is_closed(): return ext.is_closed() + + +def send_to_client(packet): return ext.send_to_client(packet) + + +def send_to_server(packet): return ext.send_to_server(packet) + + +def on_event(event_name: str, func): return ext.on_event(event_name, func) + + +def intercept(direction: Direction, callback, id=-1): return ext.intercept(direction, callback, id) + + +def start(): return ext.start() + + +def stop(): return ext.stop() + + +def write_to_console(text, color='black', mention_title=True): return ext.write_to_console(text, color, mention_title) + + +def request_flags(): return ext.request_flags() + + +HPacket.default_extension = ext diff --git a/G-Earth/src/main/resources/gearth/services/unity_tools/js_code/unity_code.js b/G-Earth/src/main/resources/gearth/services/unity_tools/js_code/unity_code.js new file mode 100644 index 0000000..b7a8e6e --- /dev/null +++ b/G-Earth/src/main/resources/gearth/services/unity_tools/js_code/unity_code.js @@ -0,0 +1,214 @@ +let gearth_isconnected = false; + +let revision = "{{RevisionName}}"; +let g_ws; + + +let chachas = []; +let chachaClass = -1; + +let out_send_param1 = -1; +let out_send_param3 = -1; +let out_packet_objid = new Uint8Array(4); + +let in_recv_param1 = -1; +let in_packet_prefix = new Uint8Array(16); + +let _gearth_returnbyte_copy; +let _gearth_outgoing_copy; +let _gearth_incoming_copy; + +let _malloc; +let _free; + + +var packetBuff = {"out": [], "in": []}; + +function readLittleEndian(arr) { + return arr[0] + arr[1] * 256 + arr[2] * 65536 + arr[3] * 16777216; +} + +function readBigEndian(arr) { + return arr[3] + arr[2] * 256 + arr[1] * 65536 + arr[0] * 16777216; +} + +function writeLittleEndian(n) { + let arr = new Uint8Array(4); + for (let i = 0; i < 4; i++) { + let rest = n % 256; + arr[i] = rest; + n -= rest; + n /= 256; + } + return arr; +} + +function writeBigEndian(n) { + let arr = new Uint8Array(4); + for (let i = 0; i < 4; i++) { + let rest = n % 256; + arr[3 - i] = rest; + n -= rest; + n /= 256; + } + return arr; +} + + + +function packetToString(packet) { + let chars = []; + let textDecode = new TextDecoder('latin1'); + for (let i = 0; i < packet.length; i++) { + let byte = packet[i]; + if (byte < 32 || (byte > 127 && byte < 160) || byte === 93 || byte === 91 || byte === 125 || byte === 127) { + chars.push("["+byte+"]"); + } + else { + chars.push(textDecode.decode(packet.slice(i, i+1))); + } + } + + return chars.join(""); +} + +function printPacket(type, packet) { + packet = new Uint8Array(packet); + if (packet.length > 2000) { + console.log("[" + type + " capture]" + " -> skipped"); + } + else { + console.log("[" + type + " capture]" + " -> " + packetToString(packet)); + } +} + + +let _g_packet_split = 600; + +function inject_out(packet) { + + if (chachas.length > 1) { + packet[5] = _gearth_returnbyte_copy(chachas[0], packet[5], chachaClass); + packet[4] = _gearth_returnbyte_copy(chachas[0], packet[4], chachaClass); + } + + let i = 0; + while (i < packet.length) { + let inject_amount = Math.min(_g_packet_split, packet.length - i); + + let packet_location = _malloc(inject_amount + 16); + unityInstance.Module.HEAPU8.set(out_packet_objid, packet_location); + unityInstance.Module.HEAPU8.fill(0, packet_location + 4, packet_location + 12); + unityInstance.Module.HEAPU8.set(writeLittleEndian(inject_amount), packet_location + 12); + unityInstance.Module.HEAPU8.set(packet.slice(i, i + inject_amount), packet_location + 16); + + _gearth_outgoing_copy(out_send_param1, packet_location, out_send_param3); + _free(packet_location); + + i += inject_amount; + } + +} + +function handle_out(packet) { + if (gearth_isconnected) { + let g_message = new Uint8Array(packet.length + 1); + g_message[0] = 1; // toserver + g_message.set(new Uint8Array(packet), 1); + g_ws.send(g_message); + } + else { + inject_out(packet); + } +} + +function inject_in(packet) { + let i = 0; + while (i < packet.length) { + let inject_amount = Math.min(_g_packet_split, packet.length - i); + + let packet_location = _malloc(inject_amount + 16); + unityInstance.Module.HEAPU8.set(in_packet_prefix, packet_location); + unityInstance.Module.HEAPU8.set(packet.slice(i, i + inject_amount), packet_location + 16); + + _gearth_incoming_copy(in_recv_param1, packet_location, 0, inject_amount, 0); + _free(packet_location); + + i += inject_amount; + } +} + +function handle_in(packet) { + if (gearth_isconnected) { + let g_message = new Uint8Array(packet.length + 1); + g_message[0] = 0; // toclient + g_message.set(new Uint8Array(packet), 1); + g_ws.send(g_message); + } + else { + inject_in(packet); + } +} + + +function collect_packets(type) { + let finishedPackets = []; + + while (packetBuff[type].length >= 6 && readBigEndian(packetBuff[type].slice(0, 4)) + 4 <= packetBuff[type].length) { + let packetLength = readBigEndian(packetBuff[type].slice(0, 4)) + 4; + let packet = packetBuff[type].slice(0, packetLength); + finishedPackets.push(packet); + packetBuff[type] = packetBuff[type].slice(packetLength); + } + + return finishedPackets; +} + + + + + +let onOpen = function() { + g_ws.send(new TextEncoder('latin1').encode(revision)); + gearth_isconnected = true; +}; + +let onClose = function() { + gearth_isconnected = false; +}; + +let onMessage = function(message) { + let buffer = new Uint8Array(message.data); + + if (buffer[0] === 0) { + inject_in([].slice.call(buffer.slice(1))); + } + else if (buffer[0] === 1) { + inject_out([].slice.call(buffer.slice(1))); + } + else { + gearth_isconnected = false; + g_ws.close(); + } +}; + +let onError = function(event) { + gearth_isconnected = false; +}; + +let portRequester = new WebSocket("ws://localhost:9039/ws/portrequest"); +portRequester.onmessage = function(message) { + let port = message.data.split(" ")[1]; + + let _g_packet_url = "ws://localhost:" + port + "/ws/packethandler"; + g_ws = new WebSocket(_g_packet_url); + g_ws.binaryType = "arraybuffer"; + + g_ws.onopen = onOpen; + g_ws.onclose = onClose; + g_ws.onmessage = onMessage; + g_ws.onerror = onError; + + portRequester.close(); +}; + diff --git a/G-Earth/src/main/resources/gearth/services/unity_tools/js_code/unity_exports.js b/G-Earth/src/main/resources/gearth/services/unity_tools/js_code/unity_exports.js new file mode 100644 index 0000000..8d55930 --- /dev/null +++ b/G-Earth/src/main/resources/gearth/services/unity_tools/js_code/unity_exports.js @@ -0,0 +1,11 @@ +_gearth_returnbyte_copy = Module["_gearth_returnbyte_copy"] = (function() { + return Module["asm"]["_gearth_returnbyte_copy"].apply(null, arguments) +}); + +_gearth_outgoing_copy = Module["_gearth_outgoing_copy"] = (function() { + return Module["asm"]["_gearth_outgoing_copy"].apply(null, arguments) +}); + +_gearth_incoming_copy = Module["_gearth_incoming_copy"] = (function() { + return Module["asm"]["_gearth_incoming_copy"].apply(null, arguments) +}); \ No newline at end of file diff --git a/G-Earth/src/main/resources/gearth/services/unity_tools/js_code/unity_imports.js b/G-Earth/src/main/resources/gearth/services/unity_tools/js_code/unity_imports.js new file mode 100644 index 0000000..221d93c --- /dev/null +++ b/G-Earth/src/main/resources/gearth/services/unity_tools/js_code/unity_imports.js @@ -0,0 +1,49 @@ +function g_outgoing_packet(param1, param2, param3) { + out_send_param1 = param1; + out_send_param3 = param3; + out_packet_objid = unityInstance.Module.HEAPU8.slice(param2, param2 + 4); + + let length = readLittleEndian(unityInstance.Module.HEAPU8.subarray(param2 + 12, param2 + 12 + 4)); + let array = [].slice.call(unityInstance.Module.HEAPU8.subarray(param2 + 12 + 4, param2 + 12 + 4 + length)); + + packetBuff["out"] = packetBuff["out"].concat(array); + + for (let packet of collect_packets("out")) { + handle_out(packet); + } +} + +function g_incoming_packet(param1, param2, param3, param4, param5) { + in_recv_param1 = param1; + in_packet_prefix = unityInstance.Module.HEAPU8.slice(param2, param2 + 16); + + let buffer = unityInstance.Module.HEAPU8.slice(param2 + 16, param2 + 16 + param4); + packetBuff["in"] = packetBuff["in"].concat([].slice.call(buffer)); + + let packets = collect_packets("in"); + for (let packet of packets) { + if (chachas.length > 1) { + packet[5] = _gearth_returnbyte_copy(chachas[1], packet[5], chachaClass); + packet[4] = _gearth_returnbyte_copy(chachas[1], packet[4], chachaClass); + } + handle_in(packet); + } +} + +function g_chacha_setkey(param1, param2, param3, param4) { + if (chachas.length === 2) { + chachas = []; + } + + chachas.push(param1); +} + +function g_chacha_returnbyte(param1, param2, param3) { + chachaClass = param3; + return param2; +} + +env["g_outgoing_packet"] = g_outgoing_packet; +env["g_incoming_packet"] = g_incoming_packet; +env["g_chacha_setkey"] = g_chacha_setkey; +env["g_chacha_returnbyte"] = g_chacha_returnbyte; \ No newline at end of file diff --git a/G-Earth/src/main/resources/gearth/ui/G-Earth.fxml b/G-Earth/src/main/resources/gearth/ui/G-Earth.fxml index ecfd221..b33211f 100644 --- a/G-Earth/src/main/resources/gearth/ui/G-Earth.fxml +++ b/G-Earth/src/main/resources/gearth/ui/G-Earth.fxml @@ -11,7 +11,7 @@ fx:controller="gearth.ui.GEarthController"> + minWidth="-Infinity" prefHeight="295.0" prefWidth="650.0" tabClosingPolicy="UNAVAILABLE"> diff --git a/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml b/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml index 4d51f76..a8eda4e 100644 --- a/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml +++ b/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml @@ -11,7 +11,7 @@ - + diff --git a/G-Earth/src/main/resources/gearth/ui/extensions/Extensions.fxml b/G-Earth/src/main/resources/gearth/ui/extensions/Extensions.fxml index 1b4807b..45d2864 100644 --- a/G-Earth/src/main/resources/gearth/ui/extensions/Extensions.fxml +++ b/G-Earth/src/main/resources/gearth/ui/extensions/Extensions.fxml @@ -4,7 +4,7 @@ - + @@ -19,11 +19,11 @@ - - - - - + + + + + @@ -52,14 +52,16 @@ - + - - + + + +