Developer mode and unsafe packet protection (wip)

This commit is contained in:
sirjonasxx 2022-12-27 22:39:32 +01:00
parent f98ea5385e
commit 7912fa35a6
17 changed files with 268 additions and 48 deletions

View File

@ -1,6 +1,8 @@
package gearth.protocol;
import gearth.misc.listenerpattern.Observable;
import gearth.protocol.connection.packetsafety.PacketSafetyManager;
import gearth.protocol.connection.packetsafety.SafePacketsContainer;
import gearth.protocol.connection.proxy.nitro.NitroProxyProvider;
import gearth.services.packet_info.PacketInfoManager;
import gearth.protocol.connection.HClient;
@ -30,6 +32,8 @@ public class HConnection {
private ProxyProviderFactory proxyProviderFactory;
private ProxyProvider proxyProvider = null;
private volatile boolean developerMode = false;
public HConnection() {
HConnection selff = this;
proxyProviderFactory = new ProxyProviderFactory(
@ -37,6 +41,8 @@ public class HConnection {
selff::setState,
this
);
PacketSafetyManager.PACKET_SAFETY_MANAGER.initialize(this);
}
public HState getState() {
@ -147,30 +153,47 @@ public class HConnection {
public boolean sendToClient(HPacket packet) {
HProxy proxy = this.proxy;
if (proxy == null) return false;
if (!packet.isPacketComplete()) {
PacketInfoManager packetInfoManager = getPacketInfoManager();
packet.completePacket(packetInfoManager);
if (!packet.isPacketComplete() || !packet.canSendToClient()) return false;
}
if (!canSendPacket(HMessage.Direction.TOCLIENT, packet)) return false;
return proxy.sendToClient(packet);
}
public boolean sendToServer(HPacket packet) {
if (!canSendPacket(HMessage.Direction.TOSERVER, packet)) return false;
return proxy.sendToServer(packet);
}
private boolean canSendPacket(HMessage.Direction direction, HPacket packet) {
return isPacketSendingAllowed(direction, packet) && (developerMode || isPacketSendingSafe(direction, packet));
}
public boolean isPacketSendingAllowed(HMessage.Direction direction, HPacket packet) {
HProxy proxy = this.proxy;
if (proxy == null) return false;
if (packet.isCorrupted()) return false;
if (!packet.isPacketComplete()) {
PacketInfoManager packetInfoManager = getPacketInfoManager();
packet.completePacket(packetInfoManager);
if (!packet.isPacketComplete() || !packet.canSendToServer()) return false;
return packet.isPacketComplete() &&
(packet.canSendToClient() || direction != HMessage.Direction.TOCLIENT) &&
(packet.canSendToServer() || direction != HMessage.Direction.TOSERVER);
}
return proxy.sendToServer(packet);
return true;
}
public boolean isPacketSendingSafe(HMessage.Direction direction, HPacket packet) {
String hotelVersion = proxy.getHotelVersion();
if (hotelVersion == null) return false;
SafePacketsContainer packetsContainer = PacketSafetyManager.PACKET_SAFETY_MANAGER.getPacketContainer(hotelVersion);
return packetsContainer.isPacketSafe(packet.headerId(), direction);
}
public void setDeveloperMode(boolean developerMode) {
this.developerMode = developerMode;
}
public String getClientHost() {
@ -205,7 +228,7 @@ public class HConnection {
if (proxy == null) {
return null;
}
return proxy.gethClient();
return proxy.getHClient();
}
public PacketInfoManager getPacketInfoManager() {

View File

@ -1,6 +1,9 @@
package gearth.protocol.connection;
import gearth.protocol.HPacket;
import gearth.protocol.connection.packetsafety.PacketSafetyManager;
import gearth.protocol.connection.packetsafety.SafePacketsContainer;
import gearth.services.packet_info.PacketInfo;
import gearth.services.packet_info.PacketInfoManager;
import gearth.protocol.packethandler.PacketHandler;
@ -45,6 +48,11 @@ public class HProxy {
this.hotelVersion = hotelVersion;
this.clientIdentifier = clientIdentifier;
this.packetInfoManager = PacketInfoManager.fromHotelVersion(hotelVersion, hClient);
SafePacketsContainer packetsContainer = PacketSafetyManager.PACKET_SAFETY_MANAGER.getPacketContainer(hotelVersion);
for (PacketInfo packetInfo : packetInfoManager.getPacketInfoList()) {
packetsContainer.validateSafePacket(packetInfo.getHeaderId(), packetInfo.getDestination());
}
}
public boolean sendToServer(HPacket packet) {
@ -101,7 +109,7 @@ public class HProxy {
return hotelVersion;
}
public HClient gethClient() {
public HClient getHClient() {
return hClient;
}

View File

@ -0,0 +1,32 @@
package gearth.protocol.connection.packetsafety;
import gearth.protocol.HConnection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class PacketSafetyManager {
public static final PacketSafetyManager PACKET_SAFETY_MANAGER = new PacketSafetyManager();
private final Map<String, SafePacketsContainer> safePacketContainers = new ConcurrentHashMap<>();
private PacketSafetyManager() {
}
public void initialize(HConnection connection) {
connection.addTrafficListener(0, message -> {
String hotelVersion = connection.getHotelVersion();
if (hotelVersion.equals("")) return;
SafePacketsContainer safePacketsContainer = getPacketContainer(hotelVersion);
safePacketsContainer.validateSafePacket(message.getPacket().headerId(), message.getDestination());
});
}
public SafePacketsContainer getPacketContainer(String hotelVersion) {
return safePacketContainers.computeIfAbsent(hotelVersion, v -> new SafePacketsContainer());
}
}

View File

@ -0,0 +1,24 @@
package gearth.protocol.connection.packetsafety;
import gearth.protocol.HMessage;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class SafePacketsContainer {
private final Set<Integer> safeIncomingIds = Collections.synchronizedSet(new HashSet<>());
private final Set<Integer> safeOutgoingIds = Collections.synchronizedSet(new HashSet<>());
public void validateSafePacket(int headerId, HMessage.Direction direction) {
Set<Integer> headerIds = direction == HMessage.Direction.TOCLIENT ? safeIncomingIds : safeOutgoingIds;
headerIds.add(headerId);
}
public boolean isPacketSafe(int headerId, HMessage.Direction direction) {
Set<Integer> headerIds = direction == HMessage.Direction.TOCLIENT ? safeIncomingIds : safeOutgoingIds;
return headerIds.contains(headerId);
}
}

View File

@ -113,6 +113,10 @@ public class ExtensionsController extends SubForm {
}
}
public void setLocalInstallingEnabled(boolean enabled) {
btn_install.setDisable(!enabled);
}
private volatile int gpytonShellCounter = 1;
private volatile boolean pythonShellLaunching = false;

View File

@ -16,14 +16,13 @@ import gearth.ui.translations.TranslatableString;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
import org.json.JSONObject;
import java.io.IOException;
import java.util.Optional;
/**
* Created by Jonas on 06/04/18.
@ -33,6 +32,7 @@ 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 DEVELOP_CACHE_KEY = "develop_mode";
public static final String ALWAYS_ADMIN_KEY = "always_admin";
public static final String SOCKS_CACHE_KEY = "socks_config";
public static final String GPYTHON_CACHE_KEY = "use_gpython";
@ -58,10 +58,10 @@ public class ExtraController extends SubForm implements SocksConfiguration {
public CheckBox cbx_useSocks;
public GridPane grd_socksInfo;
public TextField txt_socksPort;
public TextField txt_socksIp;
public CheckBox cbx_admin;
public Label lbl_notepad, lbl_proxyIp, lbl_proxyPort;
public Label lbl_notepad, lbl_proxyIp;
public CheckBox cbx_develop;
private AdminService adminService;
@ -76,8 +76,7 @@ public class ExtraController extends SubForm implements SocksConfiguration {
if (Cacher.getCacheContents().has(SOCKS_CACHE_KEY)) {
JSONObject socksInitValue = Cacher.getCacheContents().getJSONObject(SOCKS_CACHE_KEY);
txt_socksIp.setText(socksInitValue.getString(SOCKS_IP));
txt_socksPort.setText(socksInitValue.getString(SOCKS_PORT));
txt_socksIp.setText(socksInitValue.getString(SOCKS_IP) + ":" + socksInitValue.getInt(SOCKS_PORT));
// cbx_socksUseIfNeeded.setSelected(socksInitValue.getBoolean(IGNORE_ONCE));
}
@ -115,6 +114,11 @@ public class ExtraController extends SubForm implements SocksConfiguration {
}
});
if (Cacher.getCacheContents().has(DEVELOP_CACHE_KEY)) {
boolean inDevelopMode = Cacher.getCacheContents().getBoolean(DEVELOP_CACHE_KEY);
setDevelopMode(inDevelopMode);
}
updateAdvancedUI();
}
@ -123,13 +127,14 @@ public class ExtraController extends SubForm implements SocksConfiguration {
Cacher.put(NOTEPAD_CACHE_KEY, txtarea_notepad.getText());
Cacher.put(GPYTHON_CACHE_KEY, cbx_gpython.isSelected());
Cacher.put(ALWAYS_ADMIN_KEY, cbx_admin.isSelected());
Cacher.put(DEVELOP_CACHE_KEY, cbx_develop.isSelected());
saveSocksConfig();
}
private void saveSocksConfig() {
JSONObject jsonObject = new JSONObject();
jsonObject.put(SOCKS_IP, txt_socksIp.getText());
jsonObject.put(SOCKS_PORT, txt_socksPort.getText());
jsonObject.put(SOCKS_IP, getSocksHost());
jsonObject.put(SOCKS_PORT, getSocksPort());
// jsonObject.put(IGNORE_ONCE, cbx_socksUseIfNeeded.isSelected());
Cacher.put(SOCKS_CACHE_KEY, jsonObject);
}
@ -155,12 +160,12 @@ public class ExtraController extends SubForm implements SocksConfiguration {
@Override
public int getSocksPort() {
return Integer.parseInt(txt_socksPort.getText());
return Integer.parseInt(txt_socksIp.getText().split(":")[1]);
}
@Override
public String getSocksHost() {
return txt_socksIp.getText();
return txt_socksIp.getText().split(":")[0];
}
@Override
@ -221,9 +226,43 @@ public class ExtraController extends SubForm implements SocksConfiguration {
}
public void developCbxClick(ActionEvent actionEvent) {
if (cbx_develop.isSelected()) {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.WARNING, LanguageBundle.get("tab.extra.options.developmode.alert.title"), ButtonType.NO, ButtonType.YES);
alert.setTitle(LanguageBundle.get("tab.extra.options.developmode.alert.title"));
Label lbl = new Label(LanguageBundle.get("tab.extra.options.developmode.alert.content"));
alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
alert.getDialogPane().setContent(lbl);
try {
Optional<ButtonType> result = TitleBarController.create(alert).showAlertAndWait();
if (!result.isPresent() || result.get() == ButtonType.NO) {
cbx_develop.setSelected(false);
}
else {
setDevelopMode(true);
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
else {
setDevelopMode(false);
}
}
private void setDevelopMode(boolean enabled) {
cbx_develop.setSelected(enabled);
getHConnection().setDeveloperMode(enabled);
parentController.extensionsController.setLocalInstallingEnabled(enabled);
}
public void adminCbxClick(ActionEvent actionEvent) {
adminService.setEnabled(cbx_admin.isSelected());
}
private void initLanguageBinding() {
@ -231,10 +270,10 @@ public class ExtraController extends SubForm implements SocksConfiguration {
lbl_notepad.textProperty().bind(new TranslatableString("%s:", "tab.extra.notepad"));
lbl_proxyIp.textProperty().bind(new TranslatableString("%s:", "tab.extra.options.advanced.proxy.ip"));
lbl_proxyPort.textProperty().bind(new TranslatableString("%s:", "tab.extra.options.advanced.proxy.port"));
cbx_alwaysOnTop.textProperty().bind(new TranslatableString("%s", "tab.extra.options.alwaysontop"));
cbx_develop.textProperty().bind(new TranslatableString("%s", "tab.extra.options.developmode"));
cbx_admin.textProperty().bind(new TranslatableString("%s", "tab.extra.options.staffpermissions"));
cbx_gpython.textProperty().bind(new TranslatableString("%s", "tab.extra.options.pythonscripting"));
cbx_advanced.textProperty().bind(new TranslatableString("%s", "tab.extra.options.advanced"));

View File

@ -4,7 +4,7 @@
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<GridPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="258.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.subforms.extensions.ExtensionsController">
<GridPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="258.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.subforms.extensions.ExtensionsController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="10.0" prefWidth="277.0" />
</columnConstraints>
@ -72,7 +72,7 @@
<Insets left="3.0" />
</GridPane.margin>
</Label>
<Button fx:id="btn_install" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#installBtnClicked" text="Install" GridPane.columnIndex="5" />
<Button fx:id="btn_install" disable="true" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#installBtnClicked" text="Install" GridPane.columnIndex="5" />
<Button fx:id="btn_viewExtensionConsole" layoutX="401.0" layoutY="10.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#extConsoleBtnClicked" text="View logs" GridPane.columnIndex="4" />
</GridPane>
</GridPane>

View File

@ -3,8 +3,9 @@
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<GridPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="258.0" prefWidth="650.0" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.subforms.extra.ExtraController">
<GridPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="258.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.subforms.extra.ExtraController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="331.0" minWidth="10.0" prefWidth="318.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="390.0" minWidth="10.0" prefWidth="247.0" />
@ -29,17 +30,18 @@
<RowConstraints maxHeight="7.0" minHeight="7.0" prefHeight="7.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="28.0" minHeight="28.0" prefHeight="28.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="28.0" minHeight="28.0" prefHeight="28.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="28.0" minHeight="28.0" prefHeight="28.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="28.0" minHeight="28.0" prefHeight="28.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" prefHeight="232.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<GridPane fx:id="grd_advanced" disable="true" prefHeight="161.0" prefWidth="299.0" style="-fx-border-color: #888888; -fx-border-radius: 5px;" GridPane.rowIndex="4">
<GridPane fx:id="grd_advanced" disable="true" prefHeight="161.0" prefWidth="299.0" style="-fx-border-color: #888888; -fx-border-radius: 5px;" GridPane.rowIndex="5">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="30.0" minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="60.0" minHeight="60.0" prefHeight="60.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="29.0" minHeight="29.0" prefHeight="29.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="28.0" minHeight="28.0" prefHeight="28.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="30.0" minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
@ -58,7 +60,7 @@
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="60.0" minHeight="60.0" prefHeight="60.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="29.0" minHeight="29.0" prefHeight="29.0" vgrow="SOMETIMES" />
</rowConstraints>
<GridPane.margin>
<Insets />
@ -69,25 +71,15 @@
<children>
<GridPane prefHeight="119.0" prefWidth="259.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="159.0" minWidth="10.0" prefWidth="68.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="193.0" minWidth="10.0" prefWidth="145.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="159.0" minWidth="10.0" prefWidth="72.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="240.0" minWidth="10.0" prefWidth="213.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="2.0" minHeight="29.0" prefHeight="29.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="29.0" minHeight="29.0" prefHeight="29.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label fx:id="lbl_proxyIp" text="Proxy IP:" />
<Label fx:id="lbl_proxyPort" text="Proxy port:" GridPane.rowIndex="1" />
<TextField fx:id="txt_socksPort" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" GridPane.columnIndex="1" GridPane.rowIndex="1">
<opaqueInsets>
<Insets />
</opaqueInsets>
<GridPane.margin>
<Insets bottom="3.0" right="7.0" top="3.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="txt_socksIp" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" GridPane.columnIndex="1">
<TextField fx:id="txt_socksIp" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" promptText="ip:port" GridPane.columnIndex="1">
<opaqueInsets>
<Insets />
</opaqueInsets>
@ -104,7 +96,7 @@
</GridPane>
</children>
</GridPane>
<CheckBox fx:id="cbx_advanced" mnemonicParsing="false" text="Advanced" textFill="#000000ba" GridPane.rowIndex="3">
<CheckBox fx:id="cbx_advanced" mnemonicParsing="false" text="Advanced" textFill="#000000ba" GridPane.rowIndex="4">
<GridPane.margin>
<Insets left="10.0" top="2.0" />
</GridPane.margin>
@ -112,16 +104,24 @@
<Insets top="2.0" />
</padding>
</CheckBox>
<CheckBox fx:id="cbx_gpython" mnemonicParsing="false" onAction="#gpythonCbxClick" text="G-Python scripting" textFill="#000000ba" GridPane.rowIndex="2">
<CheckBox fx:id="cbx_gpython" mnemonicParsing="false" onAction="#gpythonCbxClick" text="G-Python scripting" textFill="#000000ba" GridPane.rowIndex="3">
<GridPane.margin>
<Insets left="10.0" top="2.0" />
</GridPane.margin>
</CheckBox>
<CheckBox fx:id="cbx_admin" layoutX="20.0" layoutY="25.0" mnemonicParsing="false" onAction="#adminCbxClick" selected="true" text="Client-side staff permissions" textFill="#000000ba" GridPane.rowIndex="1">
<CheckBox fx:id="cbx_admin" layoutX="20.0" layoutY="25.0" mnemonicParsing="false" onAction="#adminCbxClick" selected="true" text="Client-side staff permissions" textFill="#000000ba" GridPane.rowIndex="2">
<GridPane.margin>
<Insets left="10.0" top="2.0" />
</GridPane.margin>
</CheckBox>
<CheckBox fx:id="cbx_develop" layoutX="20.0" layoutY="52.0" mnemonicParsing="false" onAction="#developCbxClick" text="Developer mode" textFill="#000000ba" GridPane.rowIndex="1">
<GridPane.margin>
<Insets left="10.0" />
</GridPane.margin>
<font>
<Font name="System Bold" size="12.0" />
</font>
</CheckBox>
</children>
<GridPane.margin>
<Insets />

View File

@ -97,6 +97,16 @@ tab.extra=Extras
tab.extra.notepad=Notepad
tab.extra.troubleshooting=Fehlerbehebungen
tab.extra.options.alwaysontop=Immer im Vordergrund
tab.extra.options.developmode=Developer mode
tab.extra.options.developmode.alert.title=About developer mode
tab.extra.options.developmode.alert.content=By enabling developer mode, you can send unsafe packets and install extensions from local files.\n\
Be warned, sending unsafe packets may get you permanently banned if the Habbo servers detect\n\
they have an invalid header ID. If you enabled developer mode to install extensions from\n\
external sources, make sure you trust the creator. A malicious creator could try to hack you!\n\
\n\
All the extensions from the G-ExtensionStore are safe.\n\
\n\
Are you sure you want to continue?
tab.extra.options.staffpermissions=Client-seitige Staff-Berechtigungen
tab.extra.options.pythonscripting=G-Python Scripting
tab.extra.options.pythonscripting.alert.title=G-Python Installation

View File

@ -94,6 +94,16 @@ tab.extra=Extra
tab.extra.notepad=Notepad
tab.extra.troubleshooting=Troubleshooting
tab.extra.options.alwaysontop=Always on top
tab.extra.options.developmode=Developer mode
tab.extra.options.developmode.alert.title=About developer mode
tab.extra.options.developmode.alert.content=By enabling developer mode, you can send unsafe packets and install extensions from local files.\n\
Be warned, sending unsafe packets may get you permanently banned if the Habbo servers detect\n\
they have an invalid header ID. If you enabled developer mode to install extensions from\n\
external sources, make sure you trust the creator. A malicious creator could try to hack you!\n\
\n\
All the extensions from the G-ExtensionStore are safe.\n\
\n\
Are you sure you want to continue?
tab.extra.options.staffpermissions=Client-side staff permissions
tab.extra.options.pythonscripting=G-Python scripting
tab.extra.options.pythonscripting.alert.title=G-Python installation

View File

@ -95,6 +95,16 @@ tab.extra=Extra
tab.extra.notepad=Notepad
tab.extra.troubleshooting=Ayuda
tab.extra.options.alwaysontop=Mostrar siempre arriba
tab.extra.options.developmode=Developer mode
tab.extra.options.developmode.alert.title=About developer mode
tab.extra.options.developmode.alert.content=By enabling developer mode, you can send unsafe packets and install extensions from local files.\n\
Be warned, sending unsafe packets may get you permanently banned if the Habbo servers detect\n\
they have an invalid header ID. If you enabled developer mode to install extensions from\n\
external sources, make sure you trust the creator. A malicious creator could try to hack you!\n\
\n\
All the extensions from the G-ExtensionStore are safe.\n\
\n\
Are you sure you want to continue?
tab.extra.options.staffpermissions=Permisos Staff en cliente
tab.extra.options.pythonscripting=G-Python scripting
tab.extra.options.pythonscripting.alert.title=Instalación de G-Python

View File

@ -95,6 +95,16 @@ tab.extra=Lisät
tab.extra.notepad=Muistio
tab.extra.troubleshooting=Vianmääritys
tab.extra.options.alwaysontop=Aina ylimmäisenä
tab.extra.options.developmode=Developer mode
tab.extra.options.developmode.alert.title=About developer mode
tab.extra.options.developmode.alert.content=By enabling developer mode, you can send unsafe packets and install extensions from local files.\n\
Be warned, sending unsafe packets may get you permanently banned if the Habbo servers detect\n\
they have an invalid header ID. If you enabled developer mode to install extensions from\n\
external sources, make sure you trust the creator. A malicious creator could try to hack you!\n\
\n\
All the extensions from the G-ExtensionStore are safe.\n\
\n\
Are you sure you want to continue?
tab.extra.options.staffpermissions=Client-side staffioikeudet
tab.extra.options.pythonscripting=G-Python scripting
tab.extra.options.pythonscripting.alert.title=G-Python -asennus

View File

@ -97,6 +97,16 @@ tab.extra=Extra
tab.extra.notepad=Notepad
tab.extra.troubleshooting=Dépannage
tab.extra.options.alwaysontop=Toujours au top
tab.extra.options.developmode=Developer mode
tab.extra.options.developmode.alert.title=About developer mode
tab.extra.options.developmode.alert.content=By enabling developer mode, you can send unsafe packets and install extensions from local files.\n\
Be warned, sending unsafe packets may get you permanently banned if the Habbo servers detect\n\
they have an invalid header ID. If you enabled developer mode to install extensions from\n\
external sources, make sure you trust the creator. A malicious creator could try to hack you!\n\
\n\
All the extensions from the G-ExtensionStore are safe.\n\
\n\
Are you sure you want to continue?
tab.extra.options.staffpermissions=Permission staff client
tab.extra.options.pythonscripting=G-Python scripting
tab.extra.options.pythonscripting.alert.title=G-Python installation

View File

@ -94,6 +94,16 @@ tab.extra=Extra
tab.extra.notepad=Notepad
tab.extra.troubleshooting=Risoluzione dei problemi
tab.extra.options.alwaysontop=Sempre in primo piano
tab.extra.options.developmode=Developer mode
tab.extra.options.developmode.alert.title=About developer mode
tab.extra.options.developmode.alert.content=By enabling developer mode, you can send unsafe packets and install extensions from local files.\n\
Be warned, sending unsafe packets may get you permanently banned if the Habbo servers detect\n\
they have an invalid header ID. If you enabled developer mode to install extensions from\n\
external sources, make sure you trust the creator. A malicious creator could try to hack you!\n\
\n\
All the extensions from the G-ExtensionStore are safe.\n\
\n\
Are you sure you want to continue?
tab.extra.options.staffpermissions=Permessi staff client-side
tab.extra.options.pythonscripting=G-Python scripting
tab.extra.options.pythonscripting.alert.title=Installazione di G-Python

View File

@ -94,6 +94,16 @@ tab.extra=Extra
tab.extra.notepad=Kladblok
tab.extra.troubleshooting=Troubleshooting
tab.extra.options.alwaysontop=Altijd bovenop
tab.extra.options.developmode=Developer mode
tab.extra.options.developmode.alert.title=About developer mode
tab.extra.options.developmode.alert.content=By enabling developer mode, you can send unsafe packets and install extensions from local files.\n\
Be warned, sending unsafe packets may get you permanently banned if the Habbo servers detect\n\
they have an invalid header ID. If you enabled developer mode to install extensions from\n\
external sources, make sure you trust the creator. A malicious creator could try to hack you!\n\
\n\
All the extensions from the G-ExtensionStore are safe.\n\
\n\
Are you sure you want to continue?
tab.extra.options.staffpermissions=Client-side staff machtigingen
tab.extra.options.pythonscripting=G-Python scripting
tab.extra.options.pythonscripting.alert.title=G-Python installatie

View File

@ -94,6 +94,16 @@ tab.extra=Extra
tab.extra.notepad=Anotações
tab.extra.troubleshooting=Resolução de problemas
tab.extra.options.alwaysontop=Manter no topo
tab.extra.options.developmode=Developer mode
tab.extra.options.developmode.alert.title=About developer mode
tab.extra.options.developmode.alert.content=By enabling developer mode, you can send unsafe packets and install extensions from local files.\n\
Be warned, sending unsafe packets may get you permanently banned if the Habbo servers detect\n\
they have an invalid header ID. If you enabled developer mode to install extensions from\n\
external sources, make sure you trust the creator. A malicious creator could try to hack you!\n\
\n\
All the extensions from the G-ExtensionStore are safe.\n\
\n\
Are you sure you want to continue?
tab.extra.options.staffpermissions=Permissões de staff no Cliente
tab.extra.options.pythonscripting=G-Python scripting
tab.extra.options.pythonscripting.alert.title=Instalação do G-Python

View File

@ -95,6 +95,16 @@ tab.extra=Ekstra
tab.extra.notepad=Not dosyası
tab.extra.troubleshooting=Sorun giderme
tab.extra.options.alwaysontop=Her zaman üstte
tab.extra.options.developmode=Developer mode
tab.extra.options.developmode.alert.title=About developer mode
tab.extra.options.developmode.alert.content=By enabling developer mode, you can send unsafe packets and install extensions from local files.\n\
Be warned, sending unsafe packets may get you permanently banned if the Habbo servers detect\n\
they have an invalid header ID. If you enabled developer mode to install extensions from\n\
external sources, make sure you trust the creator. A malicious creator could try to hack you!\n\
\n\
All the extensions from the G-ExtensionStore are safe.\n\
\n\
Are you sure you want to continue?
tab.extra.options.staffpermissions=İstemci tarafı personel izinleri
tab.extra.options.pythonscripting=G-Python scripting
tab.extra.options.pythonscripting.alert.title=G-Python Kurulum