diff options
Diffstat (limited to 'src/main/java/cuchaz/enigma/network')
23 files changed, 1539 insertions, 0 deletions
diff --git a/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java b/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java new file mode 100644 index 0000000..2cfe823 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java | |||
| @@ -0,0 +1,164 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import com.google.common.io.MoreFiles; | ||
| 4 | import cuchaz.enigma.*; | ||
| 5 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 7 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 8 | import cuchaz.enigma.utils.Utils; | ||
| 9 | import joptsimple.OptionParser; | ||
| 10 | import joptsimple.OptionSet; | ||
| 11 | import joptsimple.OptionSpec; | ||
| 12 | |||
| 13 | import java.io.IOException; | ||
| 14 | import java.io.PrintWriter; | ||
| 15 | import java.nio.file.Files; | ||
| 16 | import java.nio.file.Path; | ||
| 17 | import java.nio.file.Paths; | ||
| 18 | import java.util.concurrent.BlockingQueue; | ||
| 19 | import java.util.concurrent.Executors; | ||
| 20 | import java.util.concurrent.LinkedBlockingDeque; | ||
| 21 | import java.util.concurrent.TimeUnit; | ||
| 22 | |||
| 23 | public class DedicatedEnigmaServer extends EnigmaServer { | ||
| 24 | |||
| 25 | private final EnigmaProfile profile; | ||
| 26 | private final MappingFormat mappingFormat; | ||
| 27 | private final Path mappingsFile; | ||
| 28 | private final PrintWriter log; | ||
| 29 | private BlockingQueue<Runnable> tasks = new LinkedBlockingDeque<>(); | ||
| 30 | |||
| 31 | public DedicatedEnigmaServer( | ||
| 32 | byte[] jarChecksum, | ||
| 33 | char[] password, | ||
| 34 | EnigmaProfile profile, | ||
| 35 | MappingFormat mappingFormat, | ||
| 36 | Path mappingsFile, | ||
| 37 | PrintWriter log, | ||
| 38 | EntryRemapper mappings, | ||
| 39 | int port | ||
| 40 | ) { | ||
| 41 | super(jarChecksum, password, mappings, port); | ||
| 42 | this.profile = profile; | ||
| 43 | this.mappingFormat = mappingFormat; | ||
| 44 | this.mappingsFile = mappingsFile; | ||
| 45 | this.log = log; | ||
| 46 | } | ||
| 47 | |||
| 48 | @Override | ||
| 49 | protected void runOnThread(Runnable task) { | ||
| 50 | tasks.add(task); | ||
| 51 | } | ||
| 52 | |||
| 53 | @Override | ||
| 54 | public void log(String message) { | ||
| 55 | super.log(message); | ||
| 56 | log.println(message); | ||
| 57 | } | ||
| 58 | |||
| 59 | public static void main(String[] args) { | ||
| 60 | OptionParser parser = new OptionParser(); | ||
| 61 | |||
| 62 | OptionSpec<Path> jarOpt = parser.accepts("jar", "Jar file to open at startup") | ||
| 63 | .withRequiredArg() | ||
| 64 | .required() | ||
| 65 | .withValuesConvertedBy(Main.PathConverter.INSTANCE); | ||
| 66 | |||
| 67 | OptionSpec<Path> mappingsOpt = parser.accepts("mappings", "Mappings file to open at startup") | ||
| 68 | .withRequiredArg() | ||
| 69 | .required() | ||
| 70 | .withValuesConvertedBy(Main.PathConverter.INSTANCE); | ||
| 71 | |||
| 72 | OptionSpec<Path> profileOpt = parser.accepts("profile", "Profile json to apply at startup") | ||
| 73 | .withRequiredArg() | ||
| 74 | .withValuesConvertedBy(Main.PathConverter.INSTANCE); | ||
| 75 | |||
| 76 | OptionSpec<Integer> portOpt = parser.accepts("port", "Port to run the server on") | ||
| 77 | .withOptionalArg() | ||
| 78 | .ofType(Integer.class) | ||
| 79 | .defaultsTo(EnigmaServer.DEFAULT_PORT); | ||
| 80 | |||
| 81 | OptionSpec<String> passwordOpt = parser.accepts("password", "The password to join the server") | ||
| 82 | .withRequiredArg() | ||
| 83 | .defaultsTo(""); | ||
| 84 | |||
| 85 | OptionSpec<Path> logFileOpt = parser.accepts("log", "The log file to write to") | ||
| 86 | .withRequiredArg() | ||
| 87 | .withValuesConvertedBy(Main.PathConverter.INSTANCE) | ||
| 88 | .defaultsTo(Paths.get("log.txt")); | ||
| 89 | |||
| 90 | OptionSet parsedArgs = parser.parse(args); | ||
| 91 | Path jar = parsedArgs.valueOf(jarOpt); | ||
| 92 | Path mappingsFile = parsedArgs.valueOf(mappingsOpt); | ||
| 93 | Path profileFile = parsedArgs.valueOf(profileOpt); | ||
| 94 | int port = parsedArgs.valueOf(portOpt); | ||
| 95 | char[] password = parsedArgs.valueOf(passwordOpt).toCharArray(); | ||
| 96 | if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) { | ||
| 97 | System.err.println("Password too long, must be at most " + EnigmaServer.MAX_PASSWORD_LENGTH + " characters"); | ||
| 98 | System.exit(1); | ||
| 99 | } | ||
| 100 | Path logFile = parsedArgs.valueOf(logFileOpt); | ||
| 101 | |||
| 102 | System.out.println("Starting Enigma server"); | ||
| 103 | DedicatedEnigmaServer server; | ||
| 104 | try { | ||
| 105 | byte[] checksum = Utils.zipSha1(parsedArgs.valueOf(jarOpt)); | ||
| 106 | |||
| 107 | EnigmaProfile profile = EnigmaProfile.read(profileFile); | ||
| 108 | Enigma enigma = Enigma.builder().setProfile(profile).build(); | ||
| 109 | System.out.println("Indexing Jar..."); | ||
| 110 | EnigmaProject project = enigma.openJar(jar, ProgressListener.none()); | ||
| 111 | |||
| 112 | MappingFormat mappingFormat = MappingFormat.ENIGMA_DIRECTORY; | ||
| 113 | EntryRemapper mappings; | ||
| 114 | if (!Files.exists(mappingsFile)) { | ||
| 115 | mappings = EntryRemapper.empty(project.getJarIndex()); | ||
| 116 | } else { | ||
| 117 | System.out.println("Reading mappings..."); | ||
| 118 | if (Files.isDirectory(mappingsFile)) { | ||
| 119 | mappingFormat = MappingFormat.ENIGMA_DIRECTORY; | ||
| 120 | } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(mappingsFile))) { | ||
| 121 | mappingFormat = MappingFormat.ENIGMA_ZIP; | ||
| 122 | } else { | ||
| 123 | mappingFormat = MappingFormat.ENIGMA_FILE; | ||
| 124 | } | ||
| 125 | mappings = EntryRemapper.mapped(project.getJarIndex(), mappingFormat.read(mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters())); | ||
| 126 | } | ||
| 127 | |||
| 128 | PrintWriter log = new PrintWriter(Files.newBufferedWriter(logFile)); | ||
| 129 | |||
| 130 | server = new DedicatedEnigmaServer(checksum, password, profile, mappingFormat, mappingsFile, log, mappings, port); | ||
| 131 | server.start(); | ||
| 132 | System.out.println("Server started"); | ||
| 133 | } catch (IOException | MappingParseException e) { | ||
| 134 | System.err.println("Error starting server!"); | ||
| 135 | e.printStackTrace(); | ||
| 136 | System.exit(1); | ||
| 137 | return; | ||
| 138 | } | ||
| 139 | |||
| 140 | // noinspection RedundantSuppression | ||
| 141 | // noinspection Convert2MethodRef - javac 8 bug | ||
| 142 | Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> server.runOnThread(() -> server.saveMappings()), 0, 1, TimeUnit.MINUTES); | ||
| 143 | Runtime.getRuntime().addShutdownHook(new Thread(server::saveMappings)); | ||
| 144 | |||
| 145 | while (true) { | ||
| 146 | try { | ||
| 147 | server.tasks.take().run(); | ||
| 148 | } catch (InterruptedException e) { | ||
| 149 | break; | ||
| 150 | } | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | @Override | ||
| 155 | public synchronized void stop() { | ||
| 156 | super.stop(); | ||
| 157 | System.exit(0); | ||
| 158 | } | ||
| 159 | |||
| 160 | private void saveMappings() { | ||
| 161 | mappingFormat.write(getMappings().getObfToDeobf(), getMappings().takeMappingDelta(), mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters()); | ||
| 162 | log.flush(); | ||
| 163 | } | ||
| 164 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/EnigmaClient.java b/src/main/java/cuchaz/enigma/network/EnigmaClient.java new file mode 100644 index 0000000..bfa53d7 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/EnigmaClient.java | |||
| @@ -0,0 +1,85 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.GuiController; | ||
| 4 | import cuchaz.enigma.network.packet.LoginC2SPacket; | ||
| 5 | import cuchaz.enigma.network.packet.Packet; | ||
| 6 | import cuchaz.enigma.network.packet.PacketRegistry; | ||
| 7 | |||
| 8 | import javax.swing.SwingUtilities; | ||
| 9 | import java.io.DataInput; | ||
| 10 | import java.io.DataInputStream; | ||
| 11 | import java.io.DataOutput; | ||
| 12 | import java.io.DataOutputStream; | ||
| 13 | import java.io.EOFException; | ||
| 14 | import java.io.IOException; | ||
| 15 | import java.net.Socket; | ||
| 16 | import java.net.SocketException; | ||
| 17 | |||
| 18 | public class EnigmaClient { | ||
| 19 | |||
| 20 | private final GuiController controller; | ||
| 21 | |||
| 22 | private final String ip; | ||
| 23 | private final int port; | ||
| 24 | private Socket socket; | ||
| 25 | private DataOutput output; | ||
| 26 | |||
| 27 | public EnigmaClient(GuiController controller, String ip, int port) { | ||
| 28 | this.controller = controller; | ||
| 29 | this.ip = ip; | ||
| 30 | this.port = port; | ||
| 31 | } | ||
| 32 | |||
| 33 | public void connect() throws IOException { | ||
| 34 | socket = new Socket(ip, port); | ||
| 35 | output = new DataOutputStream(socket.getOutputStream()); | ||
| 36 | Thread thread = new Thread(() -> { | ||
| 37 | try { | ||
| 38 | DataInput input = new DataInputStream(socket.getInputStream()); | ||
| 39 | while (true) { | ||
| 40 | int packetId; | ||
| 41 | try { | ||
| 42 | packetId = input.readUnsignedByte(); | ||
| 43 | } catch (EOFException | SocketException e) { | ||
| 44 | break; | ||
| 45 | } | ||
| 46 | Packet<GuiController> packet = PacketRegistry.createS2CPacket(packetId); | ||
| 47 | if (packet == null) { | ||
| 48 | throw new IOException("Received invalid packet id " + packetId); | ||
| 49 | } | ||
| 50 | packet.read(input); | ||
| 51 | SwingUtilities.invokeLater(() -> packet.handle(controller)); | ||
| 52 | } | ||
| 53 | } catch (IOException e) { | ||
| 54 | controller.disconnectIfConnected(e.toString()); | ||
| 55 | return; | ||
| 56 | } | ||
| 57 | controller.disconnectIfConnected("Disconnected"); | ||
| 58 | }); | ||
| 59 | thread.setName("Client I/O thread"); | ||
| 60 | thread.setDaemon(true); | ||
| 61 | thread.start(); | ||
| 62 | } | ||
| 63 | |||
| 64 | public synchronized void disconnect() { | ||
| 65 | if (socket != null && !socket.isClosed()) { | ||
| 66 | try { | ||
| 67 | socket.close(); | ||
| 68 | } catch (IOException e1) { | ||
| 69 | System.err.println("Failed to close socket"); | ||
| 70 | e1.printStackTrace(); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | |||
| 76 | public void sendPacket(Packet<ServerPacketHandler> packet) { | ||
| 77 | try { | ||
| 78 | output.writeByte(PacketRegistry.getC2SId(packet)); | ||
| 79 | packet.write(output); | ||
| 80 | } catch (IOException e) { | ||
| 81 | controller.disconnectIfConnected(e.toString()); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/EnigmaServer.java b/src/main/java/cuchaz/enigma/network/EnigmaServer.java new file mode 100644 index 0000000..b0e15a3 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/EnigmaServer.java | |||
| @@ -0,0 +1,292 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.GuiController; | ||
| 4 | import cuchaz.enigma.network.packet.KickS2CPacket; | ||
| 5 | import cuchaz.enigma.network.packet.MessageS2CPacket; | ||
| 6 | import cuchaz.enigma.network.packet.Packet; | ||
| 7 | import cuchaz.enigma.network.packet.PacketRegistry; | ||
| 8 | import cuchaz.enigma.network.packet.RemoveMappingS2CPacket; | ||
| 9 | import cuchaz.enigma.network.packet.RenameS2CPacket; | ||
| 10 | import cuchaz.enigma.network.packet.UserListS2CPacket; | ||
| 11 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 12 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 14 | import cuchaz.enigma.utils.Message; | ||
| 15 | |||
| 16 | import java.io.DataInput; | ||
| 17 | import java.io.DataInputStream; | ||
| 18 | import java.io.DataOutput; | ||
| 19 | import java.io.DataOutputStream; | ||
| 20 | import java.io.EOFException; | ||
| 21 | import java.io.IOException; | ||
| 22 | import java.net.ServerSocket; | ||
| 23 | import java.net.Socket; | ||
| 24 | import java.net.SocketException; | ||
| 25 | import java.util.ArrayList; | ||
| 26 | import java.util.Collections; | ||
| 27 | import java.util.HashMap; | ||
| 28 | import java.util.HashSet; | ||
| 29 | import java.util.List; | ||
| 30 | import java.util.Map; | ||
| 31 | import java.util.Set; | ||
| 32 | import java.util.concurrent.CopyOnWriteArrayList; | ||
| 33 | |||
| 34 | public abstract class EnigmaServer { | ||
| 35 | |||
| 36 | // https://discordapp.com/channels/507304429255393322/566418023372816394/700292322918793347 | ||
| 37 | public static final int DEFAULT_PORT = 34712; | ||
| 38 | public static final int PROTOCOL_VERSION = 0; | ||
| 39 | public static final String OWNER_USERNAME = "Owner"; | ||
| 40 | public static final int CHECKSUM_SIZE = 20; | ||
| 41 | public static final int MAX_PASSWORD_LENGTH = 255; // length is written as a byte in the login packet | ||
| 42 | |||
| 43 | private final int port; | ||
| 44 | private ServerSocket socket; | ||
| 45 | private List<Socket> clients = new CopyOnWriteArrayList<>(); | ||
| 46 | private Map<Socket, String> usernames = new HashMap<>(); | ||
| 47 | private Set<Socket> unapprovedClients = new HashSet<>(); | ||
| 48 | |||
| 49 | private final byte[] jarChecksum; | ||
| 50 | private final char[] password; | ||
| 51 | |||
| 52 | public static final int DUMMY_SYNC_ID = 0; | ||
| 53 | private final EntryRemapper mappings; | ||
| 54 | private Map<Entry<?>, Integer> syncIds = new HashMap<>(); | ||
| 55 | private Map<Integer, Entry<?>> inverseSyncIds = new HashMap<>(); | ||
| 56 | private Map<Integer, Set<Socket>> clientsNeedingConfirmation = new HashMap<>(); | ||
| 57 | private int nextSyncId = DUMMY_SYNC_ID + 1; | ||
| 58 | |||
| 59 | private static int nextIoId = 0; | ||
| 60 | |||
| 61 | public EnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) { | ||
| 62 | this.jarChecksum = jarChecksum; | ||
| 63 | this.password = password; | ||
| 64 | this.mappings = mappings; | ||
| 65 | this.port = port; | ||
| 66 | } | ||
| 67 | |||
| 68 | public void start() throws IOException { | ||
| 69 | socket = new ServerSocket(port); | ||
| 70 | log("Server started on " + socket.getInetAddress() + ":" + port); | ||
| 71 | Thread thread = new Thread(() -> { | ||
| 72 | try { | ||
| 73 | while (!socket.isClosed()) { | ||
| 74 | acceptClient(); | ||
| 75 | } | ||
| 76 | } catch (SocketException e) { | ||
| 77 | System.out.println("Server closed"); | ||
| 78 | } catch (IOException e) { | ||
| 79 | e.printStackTrace(); | ||
| 80 | } | ||
| 81 | }); | ||
| 82 | thread.setName("Server client listener"); | ||
| 83 | thread.setDaemon(true); | ||
| 84 | thread.start(); | ||
| 85 | } | ||
| 86 | |||
| 87 | private void acceptClient() throws IOException { | ||
| 88 | Socket client = socket.accept(); | ||
| 89 | clients.add(client); | ||
| 90 | Thread thread = new Thread(() -> { | ||
| 91 | try { | ||
| 92 | DataInput input = new DataInputStream(client.getInputStream()); | ||
| 93 | while (true) { | ||
| 94 | int packetId; | ||
| 95 | try { | ||
| 96 | packetId = input.readUnsignedByte(); | ||
| 97 | } catch (EOFException | SocketException e) { | ||
| 98 | break; | ||
| 99 | } | ||
| 100 | Packet<ServerPacketHandler> packet = PacketRegistry.createC2SPacket(packetId); | ||
| 101 | if (packet == null) { | ||
| 102 | throw new IOException("Received invalid packet id " + packetId); | ||
| 103 | } | ||
| 104 | packet.read(input); | ||
| 105 | runOnThread(() -> packet.handle(new ServerPacketHandler(client, this))); | ||
| 106 | } | ||
| 107 | } catch (IOException e) { | ||
| 108 | kick(client, e.toString()); | ||
| 109 | e.printStackTrace(); | ||
| 110 | return; | ||
| 111 | } | ||
| 112 | kick(client, "disconnect.disconnected"); | ||
| 113 | }); | ||
| 114 | thread.setName("Server I/O thread #" + (nextIoId++)); | ||
| 115 | thread.setDaemon(true); | ||
| 116 | thread.start(); | ||
| 117 | } | ||
| 118 | |||
| 119 | public void stop() { | ||
| 120 | runOnThread(() -> { | ||
| 121 | if (socket != null && !socket.isClosed()) { | ||
| 122 | for (Socket client : clients) { | ||
| 123 | kick(client, "disconnect.server_closed"); | ||
| 124 | } | ||
| 125 | try { | ||
| 126 | socket.close(); | ||
| 127 | } catch (IOException e) { | ||
| 128 | System.err.println("Failed to close server socket"); | ||
| 129 | e.printStackTrace(); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | }); | ||
| 133 | } | ||
| 134 | |||
| 135 | public void kick(Socket client, String reason) { | ||
| 136 | if (!clients.remove(client)) return; | ||
| 137 | |||
| 138 | sendPacket(client, new KickS2CPacket(reason)); | ||
| 139 | |||
| 140 | clientsNeedingConfirmation.values().removeIf(list -> { | ||
| 141 | list.remove(client); | ||
| 142 | return list.isEmpty(); | ||
| 143 | }); | ||
| 144 | String username = usernames.remove(client); | ||
| 145 | try { | ||
| 146 | client.close(); | ||
| 147 | } catch (IOException e) { | ||
| 148 | System.err.println("Failed to close server client socket"); | ||
| 149 | e.printStackTrace(); | ||
| 150 | } | ||
| 151 | |||
| 152 | if (username != null) { | ||
| 153 | System.out.println("Kicked " + username + " because " + reason); | ||
| 154 | sendMessage(Message.disconnect(username)); | ||
| 155 | } | ||
| 156 | sendUsernamePacket(); | ||
| 157 | } | ||
| 158 | |||
| 159 | public boolean isUsernameTaken(String username) { | ||
| 160 | return usernames.containsValue(username); | ||
| 161 | } | ||
| 162 | |||
| 163 | public void setUsername(Socket client, String username) { | ||
| 164 | usernames.put(client, username); | ||
| 165 | sendUsernamePacket(); | ||
| 166 | } | ||
| 167 | |||
| 168 | private void sendUsernamePacket() { | ||
| 169 | List<String> usernames = new ArrayList<>(this.usernames.values()); | ||
| 170 | Collections.sort(usernames); | ||
| 171 | sendToAll(new UserListS2CPacket(usernames)); | ||
| 172 | } | ||
| 173 | |||
| 174 | public String getUsername(Socket client) { | ||
| 175 | return usernames.get(client); | ||
| 176 | } | ||
| 177 | |||
| 178 | public void sendPacket(Socket client, Packet<GuiController> packet) { | ||
| 179 | if (!client.isClosed()) { | ||
| 180 | int packetId = PacketRegistry.getS2CId(packet); | ||
| 181 | try { | ||
| 182 | DataOutput output = new DataOutputStream(client.getOutputStream()); | ||
| 183 | output.writeByte(packetId); | ||
| 184 | packet.write(output); | ||
| 185 | } catch (IOException e) { | ||
| 186 | if (!(packet instanceof KickS2CPacket)) { | ||
| 187 | kick(client, e.toString()); | ||
| 188 | e.printStackTrace(); | ||
| 189 | } | ||
| 190 | } | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | public void sendToAll(Packet<GuiController> packet) { | ||
| 195 | for (Socket client : clients) { | ||
| 196 | sendPacket(client, packet); | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | public void sendToAllExcept(Socket excluded, Packet<GuiController> packet) { | ||
| 201 | for (Socket client : clients) { | ||
| 202 | if (client != excluded) { | ||
| 203 | sendPacket(client, packet); | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | public boolean canModifyEntry(Socket client, Entry<?> entry) { | ||
| 209 | if (unapprovedClients.contains(client)) { | ||
| 210 | return false; | ||
| 211 | } | ||
| 212 | |||
| 213 | Integer syncId = syncIds.get(entry); | ||
| 214 | if (syncId == null) { | ||
| 215 | return true; | ||
| 216 | } | ||
| 217 | Set<Socket> clients = clientsNeedingConfirmation.get(syncId); | ||
| 218 | return clients == null || !clients.contains(client); | ||
| 219 | } | ||
| 220 | |||
| 221 | public int lockEntry(Socket exception, Entry<?> entry) { | ||
| 222 | int syncId = nextSyncId; | ||
| 223 | nextSyncId++; | ||
| 224 | // sync id is sent as an unsigned short, can't have more than 65536 | ||
| 225 | if (nextSyncId == 65536) { | ||
| 226 | nextSyncId = DUMMY_SYNC_ID + 1; | ||
| 227 | } | ||
| 228 | Integer oldSyncId = syncIds.get(entry); | ||
| 229 | if (oldSyncId != null) { | ||
| 230 | clientsNeedingConfirmation.remove(oldSyncId); | ||
| 231 | } | ||
| 232 | syncIds.put(entry, syncId); | ||
| 233 | inverseSyncIds.put(syncId, entry); | ||
| 234 | Set<Socket> clients = new HashSet<>(this.clients); | ||
| 235 | clients.remove(exception); | ||
| 236 | clientsNeedingConfirmation.put(syncId, clients); | ||
| 237 | return syncId; | ||
| 238 | } | ||
| 239 | |||
| 240 | public void confirmChange(Socket client, int syncId) { | ||
| 241 | if (usernames.containsKey(client)) { | ||
| 242 | unapprovedClients.remove(client); | ||
| 243 | } | ||
| 244 | |||
| 245 | Set<Socket> clients = clientsNeedingConfirmation.get(syncId); | ||
| 246 | if (clients != null) { | ||
| 247 | clients.remove(client); | ||
| 248 | if (clients.isEmpty()) { | ||
| 249 | clientsNeedingConfirmation.remove(syncId); | ||
| 250 | syncIds.remove(inverseSyncIds.remove(syncId)); | ||
| 251 | } | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | public void sendCorrectMapping(Socket client, Entry<?> entry, boolean refreshClassTree) { | ||
| 256 | EntryMapping oldMapping = mappings.getDeobfMapping(entry); | ||
| 257 | String oldName = oldMapping == null ? null : oldMapping.getTargetName(); | ||
| 258 | if (oldName == null) { | ||
| 259 | sendPacket(client, new RemoveMappingS2CPacket(DUMMY_SYNC_ID, entry)); | ||
| 260 | } else { | ||
| 261 | sendPacket(client, new RenameS2CPacket(0, entry, oldName, refreshClassTree)); | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | protected abstract void runOnThread(Runnable task); | ||
| 266 | |||
| 267 | public void log(String message) { | ||
| 268 | System.out.println(message); | ||
| 269 | } | ||
| 270 | |||
| 271 | protected boolean isRunning() { | ||
| 272 | return !socket.isClosed(); | ||
| 273 | } | ||
| 274 | |||
| 275 | public byte[] getJarChecksum() { | ||
| 276 | return jarChecksum; | ||
| 277 | } | ||
| 278 | |||
| 279 | public char[] getPassword() { | ||
| 280 | return password; | ||
| 281 | } | ||
| 282 | |||
| 283 | public EntryRemapper getMappings() { | ||
| 284 | return mappings; | ||
| 285 | } | ||
| 286 | |||
| 287 | public void sendMessage(Message message) { | ||
| 288 | log(String.format("[MSG] %s", message.translate())); | ||
| 289 | sendToAll(new MessageS2CPacket(message)); | ||
| 290 | } | ||
| 291 | |||
| 292 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java b/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java new file mode 100644 index 0000000..21c6825 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 4 | |||
| 5 | import javax.swing.*; | ||
| 6 | |||
| 7 | public class IntegratedEnigmaServer extends EnigmaServer { | ||
| 8 | public IntegratedEnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) { | ||
| 9 | super(jarChecksum, password, mappings, port); | ||
| 10 | } | ||
| 11 | |||
| 12 | @Override | ||
| 13 | protected void runOnThread(Runnable task) { | ||
| 14 | SwingUtilities.invokeLater(task); | ||
| 15 | } | ||
| 16 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java b/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java new file mode 100644 index 0000000..8618553 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import java.net.Socket; | ||
| 4 | |||
| 5 | public class ServerPacketHandler { | ||
| 6 | |||
| 7 | private final Socket client; | ||
| 8 | private final EnigmaServer server; | ||
| 9 | |||
| 10 | public ServerPacketHandler(Socket client, EnigmaServer server) { | ||
| 11 | this.client = client; | ||
| 12 | this.server = server; | ||
| 13 | } | ||
| 14 | |||
| 15 | public Socket getClient() { | ||
| 16 | return client; | ||
| 17 | } | ||
| 18 | |||
| 19 | public EnigmaServer getServer() { | ||
| 20 | return server; | ||
| 21 | } | ||
| 22 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java new file mode 100644 index 0000000..4d5d86f --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java | |||
| @@ -0,0 +1,59 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.EnigmaServer; | ||
| 4 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 7 | import cuchaz.enigma.utils.Message; | ||
| 8 | import cuchaz.enigma.utils.Utils; | ||
| 9 | |||
| 10 | import java.io.DataInput; | ||
| 11 | import java.io.DataOutput; | ||
| 12 | import java.io.IOException; | ||
| 13 | |||
| 14 | public class ChangeDocsC2SPacket implements Packet<ServerPacketHandler> { | ||
| 15 | private Entry<?> entry; | ||
| 16 | private String newDocs; | ||
| 17 | |||
| 18 | ChangeDocsC2SPacket() { | ||
| 19 | } | ||
| 20 | |||
| 21 | public ChangeDocsC2SPacket(Entry<?> entry, String newDocs) { | ||
| 22 | this.entry = entry; | ||
| 23 | this.newDocs = newDocs; | ||
| 24 | } | ||
| 25 | |||
| 26 | @Override | ||
| 27 | public void read(DataInput input) throws IOException { | ||
| 28 | this.entry = PacketHelper.readEntry(input); | ||
| 29 | this.newDocs = PacketHelper.readString(input); | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public void write(DataOutput output) throws IOException { | ||
| 34 | PacketHelper.writeEntry(output, entry); | ||
| 35 | PacketHelper.writeString(output, newDocs); | ||
| 36 | } | ||
| 37 | |||
| 38 | @Override | ||
| 39 | public void handle(ServerPacketHandler handler) { | ||
| 40 | EntryMapping mapping = handler.getServer().getMappings().getDeobfMapping(entry); | ||
| 41 | |||
| 42 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); | ||
| 43 | if (!valid) { | ||
| 44 | String oldDocs = mapping == null ? null : mapping.getJavadoc(); | ||
| 45 | handler.getServer().sendPacket(handler.getClient(), new ChangeDocsS2CPacket(EnigmaServer.DUMMY_SYNC_ID, entry, oldDocs == null ? "" : oldDocs)); | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | |||
| 49 | if (mapping == null) { | ||
| 50 | mapping = new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName()); | ||
| 51 | } | ||
| 52 | handler.getServer().getMappings().mapFromObf(entry, mapping.withDocs(Utils.isBlank(newDocs) ? null : newDocs)); | ||
| 53 | |||
| 54 | int syncId = handler.getServer().lockEntry(handler.getClient(), entry); | ||
| 55 | handler.getServer().sendToAllExcept(handler.getClient(), new ChangeDocsS2CPacket(syncId, entry, newDocs)); | ||
| 56 | handler.getServer().sendMessage(Message.editDocs(handler.getServer().getUsername(handler.getClient()), entry)); | ||
| 57 | } | ||
| 58 | |||
| 59 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java new file mode 100644 index 0000000..bf5b7cb --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.gui.GuiController; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | |||
| 7 | import java.io.DataInput; | ||
| 8 | import java.io.DataOutput; | ||
| 9 | import java.io.IOException; | ||
| 10 | |||
| 11 | public class ChangeDocsS2CPacket implements Packet<GuiController> { | ||
| 12 | private int syncId; | ||
| 13 | private Entry<?> entry; | ||
| 14 | private String newDocs; | ||
| 15 | |||
| 16 | ChangeDocsS2CPacket() { | ||
| 17 | } | ||
| 18 | |||
| 19 | public ChangeDocsS2CPacket(int syncId, Entry<?> entry, String newDocs) { | ||
| 20 | this.syncId = syncId; | ||
| 21 | this.entry = entry; | ||
| 22 | this.newDocs = newDocs; | ||
| 23 | } | ||
| 24 | |||
| 25 | @Override | ||
| 26 | public void read(DataInput input) throws IOException { | ||
| 27 | this.syncId = input.readUnsignedShort(); | ||
| 28 | this.entry = PacketHelper.readEntry(input); | ||
| 29 | this.newDocs = PacketHelper.readString(input); | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public void write(DataOutput output) throws IOException { | ||
| 34 | output.writeShort(syncId); | ||
| 35 | PacketHelper.writeEntry(output, entry); | ||
| 36 | PacketHelper.writeString(output, newDocs); | ||
| 37 | } | ||
| 38 | |||
| 39 | @Override | ||
| 40 | public void handle(GuiController controller) { | ||
| 41 | controller.changeDocs(new EntryReference<>(entry, entry.getName()), newDocs, false); | ||
| 42 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | ||
| 43 | } | ||
| 44 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java new file mode 100644 index 0000000..78ef964 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 4 | |||
| 5 | import java.io.DataInput; | ||
| 6 | import java.io.DataOutput; | ||
| 7 | import java.io.IOException; | ||
| 8 | |||
| 9 | public class ConfirmChangeC2SPacket implements Packet<ServerPacketHandler> { | ||
| 10 | private int syncId; | ||
| 11 | |||
| 12 | ConfirmChangeC2SPacket() { | ||
| 13 | } | ||
| 14 | |||
| 15 | public ConfirmChangeC2SPacket(int syncId) { | ||
| 16 | this.syncId = syncId; | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public void read(DataInput input) throws IOException { | ||
| 21 | this.syncId = input.readUnsignedShort(); | ||
| 22 | } | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public void write(DataOutput output) throws IOException { | ||
| 26 | output.writeShort(syncId); | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public void handle(ServerPacketHandler handler) { | ||
| 31 | handler.getServer().confirmChange(handler.getClient(), syncId); | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java new file mode 100644 index 0000000..bd007d3 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.GuiController; | ||
| 4 | |||
| 5 | import java.io.DataInput; | ||
| 6 | import java.io.DataOutput; | ||
| 7 | import java.io.IOException; | ||
| 8 | |||
| 9 | public class KickS2CPacket implements Packet<GuiController> { | ||
| 10 | private String reason; | ||
| 11 | |||
| 12 | KickS2CPacket() { | ||
| 13 | } | ||
| 14 | |||
| 15 | public KickS2CPacket(String reason) { | ||
| 16 | this.reason = reason; | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public void read(DataInput input) throws IOException { | ||
| 21 | this.reason = PacketHelper.readString(input); | ||
| 22 | } | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public void write(DataOutput output) throws IOException { | ||
| 26 | PacketHelper.writeString(output, reason); | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public void handle(GuiController controller) { | ||
| 31 | controller.disconnectIfConnected(reason); | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java new file mode 100644 index 0000000..722cbbf --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.EnigmaServer; | ||
| 4 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 5 | import cuchaz.enigma.utils.Message; | ||
| 6 | |||
| 7 | import java.io.DataInput; | ||
| 8 | import java.io.DataOutput; | ||
| 9 | import java.io.IOException; | ||
| 10 | import java.util.Arrays; | ||
| 11 | |||
| 12 | public class LoginC2SPacket implements Packet<ServerPacketHandler> { | ||
| 13 | private byte[] jarChecksum; | ||
| 14 | private char[] password; | ||
| 15 | private String username; | ||
| 16 | |||
| 17 | LoginC2SPacket() { | ||
| 18 | } | ||
| 19 | |||
| 20 | public LoginC2SPacket(byte[] jarChecksum, char[] password, String username) { | ||
| 21 | this.jarChecksum = jarChecksum; | ||
| 22 | this.password = password; | ||
| 23 | this.username = username; | ||
| 24 | } | ||
| 25 | |||
| 26 | @Override | ||
| 27 | public void read(DataInput input) throws IOException { | ||
| 28 | if (input.readUnsignedShort() != EnigmaServer.PROTOCOL_VERSION) { | ||
| 29 | throw new IOException("Mismatching protocol"); | ||
| 30 | } | ||
| 31 | this.jarChecksum = new byte[EnigmaServer.CHECKSUM_SIZE]; | ||
| 32 | input.readFully(jarChecksum); | ||
| 33 | this.password = new char[input.readUnsignedByte()]; | ||
| 34 | for (int i = 0; i < password.length; i++) { | ||
| 35 | password[i] = input.readChar(); | ||
| 36 | } | ||
| 37 | this.username = PacketHelper.readString(input); | ||
| 38 | } | ||
| 39 | |||
| 40 | @Override | ||
| 41 | public void write(DataOutput output) throws IOException { | ||
| 42 | output.writeShort(EnigmaServer.PROTOCOL_VERSION); | ||
| 43 | output.write(jarChecksum); | ||
| 44 | output.writeByte(password.length); | ||
| 45 | for (char c : password) { | ||
| 46 | output.writeChar(c); | ||
| 47 | } | ||
| 48 | PacketHelper.writeString(output, username); | ||
| 49 | } | ||
| 50 | |||
| 51 | @Override | ||
| 52 | public void handle(ServerPacketHandler handler) { | ||
| 53 | boolean usernameTaken = handler.getServer().isUsernameTaken(username); | ||
| 54 | handler.getServer().setUsername(handler.getClient(), username); | ||
| 55 | handler.getServer().log(username + " logged in with IP " + handler.getClient().getInetAddress().toString() + ":" + handler.getClient().getPort()); | ||
| 56 | |||
| 57 | if (!Arrays.equals(password, handler.getServer().getPassword())) { | ||
| 58 | handler.getServer().kick(handler.getClient(), "disconnect.wrong_password"); | ||
| 59 | return; | ||
| 60 | } | ||
| 61 | |||
| 62 | if (usernameTaken) { | ||
| 63 | handler.getServer().kick(handler.getClient(), "disconnect.username_taken"); | ||
| 64 | return; | ||
| 65 | } | ||
| 66 | |||
| 67 | if (!Arrays.equals(jarChecksum, handler.getServer().getJarChecksum())) { | ||
| 68 | handler.getServer().kick(handler.getClient(), "disconnect.wrong_jar"); | ||
| 69 | return; | ||
| 70 | } | ||
| 71 | |||
| 72 | handler.getServer().sendPacket(handler.getClient(), new SyncMappingsS2CPacket(handler.getServer().getMappings().getObfToDeobf())); | ||
| 73 | handler.getServer().sendMessage(Message.connect(username)); | ||
| 74 | } | ||
| 75 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java new file mode 100644 index 0000000..98d20d9 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 4 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | import cuchaz.enigma.utils.Message; | ||
| 7 | |||
| 8 | import java.io.DataInput; | ||
| 9 | import java.io.DataOutput; | ||
| 10 | import java.io.IOException; | ||
| 11 | |||
| 12 | public class MarkDeobfuscatedC2SPacket implements Packet<ServerPacketHandler> { | ||
| 13 | private Entry<?> entry; | ||
| 14 | |||
| 15 | MarkDeobfuscatedC2SPacket() { | ||
| 16 | } | ||
| 17 | |||
| 18 | public MarkDeobfuscatedC2SPacket(Entry<?> entry) { | ||
| 19 | this.entry = entry; | ||
| 20 | } | ||
| 21 | |||
| 22 | @Override | ||
| 23 | public void read(DataInput input) throws IOException { | ||
| 24 | this.entry = PacketHelper.readEntry(input); | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public void write(DataOutput output) throws IOException { | ||
| 29 | PacketHelper.writeEntry(output, entry); | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public void handle(ServerPacketHandler handler) { | ||
| 34 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); | ||
| 35 | if (!valid) { | ||
| 36 | handler.getServer().sendCorrectMapping(handler.getClient(), entry, true); | ||
| 37 | return; | ||
| 38 | } | ||
| 39 | |||
| 40 | handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName())); | ||
| 41 | handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " marked " + entry + " as deobfuscated"); | ||
| 42 | |||
| 43 | int syncId = handler.getServer().lockEntry(handler.getClient(), entry); | ||
| 44 | handler.getServer().sendToAllExcept(handler.getClient(), new MarkDeobfuscatedS2CPacket(syncId, entry)); | ||
| 45 | handler.getServer().sendMessage(Message.markDeobf(handler.getServer().getUsername(handler.getClient()), entry)); | ||
| 46 | |||
| 47 | } | ||
| 48 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java new file mode 100644 index 0000000..b7d6eda --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.gui.GuiController; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | |||
| 7 | import java.io.DataInput; | ||
| 8 | import java.io.DataOutput; | ||
| 9 | import java.io.IOException; | ||
| 10 | |||
| 11 | public class MarkDeobfuscatedS2CPacket implements Packet<GuiController> { | ||
| 12 | private int syncId; | ||
| 13 | private Entry<?> entry; | ||
| 14 | |||
| 15 | MarkDeobfuscatedS2CPacket() { | ||
| 16 | } | ||
| 17 | |||
| 18 | public MarkDeobfuscatedS2CPacket(int syncId, Entry<?> entry) { | ||
| 19 | this.syncId = syncId; | ||
| 20 | this.entry = entry; | ||
| 21 | } | ||
| 22 | |||
| 23 | @Override | ||
| 24 | public void read(DataInput input) throws IOException { | ||
| 25 | this.syncId = input.readUnsignedShort(); | ||
| 26 | this.entry = PacketHelper.readEntry(input); | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public void write(DataOutput output) throws IOException { | ||
| 31 | output.writeShort(syncId); | ||
| 32 | PacketHelper.writeEntry(output, entry); | ||
| 33 | } | ||
| 34 | |||
| 35 | @Override | ||
| 36 | public void handle(GuiController controller) { | ||
| 37 | controller.markAsDeobfuscated(new EntryReference<>(entry, entry.getName()), false); | ||
| 38 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java new file mode 100644 index 0000000..b8e0f14 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import java.io.DataInput; | ||
| 4 | import java.io.DataOutput; | ||
| 5 | import java.io.IOException; | ||
| 6 | |||
| 7 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 8 | import cuchaz.enigma.utils.Message; | ||
| 9 | |||
| 10 | public class MessageC2SPacket implements Packet<ServerPacketHandler> { | ||
| 11 | |||
| 12 | private String message; | ||
| 13 | |||
| 14 | MessageC2SPacket() { | ||
| 15 | } | ||
| 16 | |||
| 17 | public MessageC2SPacket(String message) { | ||
| 18 | this.message = message; | ||
| 19 | } | ||
| 20 | |||
| 21 | @Override | ||
| 22 | public void read(DataInput input) throws IOException { | ||
| 23 | message = PacketHelper.readString(input); | ||
| 24 | } | ||
| 25 | |||
| 26 | @Override | ||
| 27 | public void write(DataOutput output) throws IOException { | ||
| 28 | PacketHelper.writeString(output, message); | ||
| 29 | } | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public void handle(ServerPacketHandler handler) { | ||
| 33 | String message = this.message.trim(); | ||
| 34 | if (!message.isEmpty()) { | ||
| 35 | handler.getServer().sendMessage(Message.chat(handler.getServer().getUsername(handler.getClient()), message)); | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java new file mode 100644 index 0000000..edeaae0 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import java.io.DataInput; | ||
| 4 | import java.io.DataOutput; | ||
| 5 | import java.io.IOException; | ||
| 6 | |||
| 7 | import cuchaz.enigma.gui.GuiController; | ||
| 8 | import cuchaz.enigma.utils.Message; | ||
| 9 | |||
| 10 | public class MessageS2CPacket implements Packet<GuiController> { | ||
| 11 | |||
| 12 | private Message message; | ||
| 13 | |||
| 14 | MessageS2CPacket() { | ||
| 15 | } | ||
| 16 | |||
| 17 | public MessageS2CPacket(Message message) { | ||
| 18 | this.message = message; | ||
| 19 | } | ||
| 20 | |||
| 21 | @Override | ||
| 22 | public void read(DataInput input) throws IOException { | ||
| 23 | message = Message.read(input); | ||
| 24 | } | ||
| 25 | |||
| 26 | @Override | ||
| 27 | public void write(DataOutput output) throws IOException { | ||
| 28 | message.write(output); | ||
| 29 | } | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public void handle(GuiController handler) { | ||
| 33 | handler.addMessage(message); | ||
| 34 | } | ||
| 35 | |||
| 36 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/Packet.java b/src/main/java/cuchaz/enigma/network/packet/Packet.java new file mode 100644 index 0000000..2f16dfb --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/Packet.java | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import java.io.DataInput; | ||
| 4 | import java.io.DataOutput; | ||
| 5 | import java.io.IOException; | ||
| 6 | |||
| 7 | public interface Packet<H> { | ||
| 8 | |||
| 9 | void read(DataInput input) throws IOException; | ||
| 10 | |||
| 11 | void write(DataOutput output) throws IOException; | ||
| 12 | |||
| 13 | void handle(H handler); | ||
| 14 | |||
| 15 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java b/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java new file mode 100644 index 0000000..464606e --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java | |||
| @@ -0,0 +1,135 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 4 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 10 | |||
| 11 | import java.io.DataInput; | ||
| 12 | import java.io.DataOutput; | ||
| 13 | import java.io.IOException; | ||
| 14 | import java.nio.charset.StandardCharsets; | ||
| 15 | |||
| 16 | public class PacketHelper { | ||
| 17 | |||
| 18 | private static final int ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3; | ||
| 19 | private static final int MAX_STRING_LENGTH = 65535; | ||
| 20 | |||
| 21 | public static Entry<?> readEntry(DataInput input) throws IOException { | ||
| 22 | return readEntry(input, null, true); | ||
| 23 | } | ||
| 24 | |||
| 25 | public static Entry<?> readEntry(DataInput input, Entry<?> parent, boolean includeParent) throws IOException { | ||
| 26 | int type = input.readUnsignedByte(); | ||
| 27 | |||
| 28 | if (includeParent && input.readBoolean()) { | ||
| 29 | parent = readEntry(input, null, true); | ||
| 30 | } | ||
| 31 | |||
| 32 | String name = readString(input); | ||
| 33 | |||
| 34 | String javadocs = null; | ||
| 35 | if (input.readBoolean()) { | ||
| 36 | javadocs = readString(input); | ||
| 37 | } | ||
| 38 | |||
| 39 | switch (type) { | ||
| 40 | case ENTRY_CLASS: { | ||
| 41 | if (parent != null && !(parent instanceof ClassEntry)) { | ||
| 42 | throw new IOException("Class requires class parent"); | ||
| 43 | } | ||
| 44 | return new ClassEntry((ClassEntry) parent, name, javadocs); | ||
| 45 | } | ||
| 46 | case ENTRY_FIELD: { | ||
| 47 | if (!(parent instanceof ClassEntry)) { | ||
| 48 | throw new IOException("Field requires class parent"); | ||
| 49 | } | ||
| 50 | TypeDescriptor desc = new TypeDescriptor(readString(input)); | ||
| 51 | return new FieldEntry((ClassEntry) parent, name, desc, javadocs); | ||
| 52 | } | ||
| 53 | case ENTRY_METHOD: { | ||
| 54 | if (!(parent instanceof ClassEntry)) { | ||
| 55 | throw new IOException("Method requires class parent"); | ||
| 56 | } | ||
| 57 | MethodDescriptor desc = new MethodDescriptor(readString(input)); | ||
| 58 | return new MethodEntry((ClassEntry) parent, name, desc, javadocs); | ||
| 59 | } | ||
| 60 | case ENTRY_LOCAL_VAR: { | ||
| 61 | if (!(parent instanceof MethodEntry)) { | ||
| 62 | throw new IOException("Local variable requires method parent"); | ||
| 63 | } | ||
| 64 | int index = input.readUnsignedShort(); | ||
| 65 | boolean parameter = input.readBoolean(); | ||
| 66 | return new LocalVariableEntry((MethodEntry) parent, index, name, parameter, javadocs); | ||
| 67 | } | ||
| 68 | default: throw new IOException("Received unknown entry type " + type); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | public static void writeEntry(DataOutput output, Entry<?> entry) throws IOException { | ||
| 73 | writeEntry(output, entry, true); | ||
| 74 | } | ||
| 75 | |||
| 76 | public static void writeEntry(DataOutput output, Entry<?> entry, boolean includeParent) throws IOException { | ||
| 77 | // type | ||
| 78 | if (entry instanceof ClassEntry) { | ||
| 79 | output.writeByte(ENTRY_CLASS); | ||
| 80 | } else if (entry instanceof FieldEntry) { | ||
| 81 | output.writeByte(ENTRY_FIELD); | ||
| 82 | } else if (entry instanceof MethodEntry) { | ||
| 83 | output.writeByte(ENTRY_METHOD); | ||
| 84 | } else if (entry instanceof LocalVariableEntry) { | ||
| 85 | output.writeByte(ENTRY_LOCAL_VAR); | ||
| 86 | } else { | ||
| 87 | throw new IOException("Don't know how to serialize entry of type " + entry.getClass().getSimpleName()); | ||
| 88 | } | ||
| 89 | |||
| 90 | // parent | ||
| 91 | if (includeParent) { | ||
| 92 | output.writeBoolean(entry.getParent() != null); | ||
| 93 | if (entry.getParent() != null) { | ||
| 94 | writeEntry(output, entry.getParent(), true); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | // name | ||
| 99 | writeString(output, entry.getName()); | ||
| 100 | |||
| 101 | // javadocs | ||
| 102 | output.writeBoolean(entry.getJavadocs() != null); | ||
| 103 | if (entry.getJavadocs() != null) { | ||
| 104 | writeString(output, entry.getJavadocs()); | ||
| 105 | } | ||
| 106 | |||
| 107 | // type-specific stuff | ||
| 108 | if (entry instanceof FieldEntry) { | ||
| 109 | writeString(output, ((FieldEntry) entry).getDesc().toString()); | ||
| 110 | } else if (entry instanceof MethodEntry) { | ||
| 111 | writeString(output, ((MethodEntry) entry).getDesc().toString()); | ||
| 112 | } else if (entry instanceof LocalVariableEntry) { | ||
| 113 | LocalVariableEntry localVar = (LocalVariableEntry) entry; | ||
| 114 | output.writeShort(localVar.getIndex()); | ||
| 115 | output.writeBoolean(localVar.isArgument()); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | public static String readString(DataInput input) throws IOException { | ||
| 120 | int length = input.readUnsignedShort(); | ||
| 121 | byte[] bytes = new byte[length]; | ||
| 122 | input.readFully(bytes); | ||
| 123 | return new String(bytes, StandardCharsets.UTF_8); | ||
| 124 | } | ||
| 125 | |||
| 126 | public static void writeString(DataOutput output, String str) throws IOException { | ||
| 127 | byte[] bytes = str.getBytes(StandardCharsets.UTF_8); | ||
| 128 | if (bytes.length > MAX_STRING_LENGTH) { | ||
| 129 | throw new IOException("String too long, was " + bytes.length + " bytes, max " + MAX_STRING_LENGTH + " allowed"); | ||
| 130 | } | ||
| 131 | output.writeShort(bytes.length); | ||
| 132 | output.write(bytes); | ||
| 133 | } | ||
| 134 | |||
| 135 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java b/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java new file mode 100644 index 0000000..ba5d9de --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.GuiController; | ||
| 4 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 5 | |||
| 6 | import java.util.HashMap; | ||
| 7 | import java.util.Map; | ||
| 8 | import java.util.function.Supplier; | ||
| 9 | |||
| 10 | public class PacketRegistry { | ||
| 11 | |||
| 12 | private static final Map<Class<? extends Packet<ServerPacketHandler>>, Integer> c2sPacketIds = new HashMap<>(); | ||
| 13 | private static final Map<Integer, Supplier<? extends Packet<ServerPacketHandler>>> c2sPacketCreators = new HashMap<>(); | ||
| 14 | private static final Map<Class<? extends Packet<GuiController>>, Integer> s2cPacketIds = new HashMap<>(); | ||
| 15 | private static final Map<Integer, Supplier<? extends Packet<GuiController>>> s2cPacketCreators = new HashMap<>(); | ||
| 16 | |||
| 17 | private static <T extends Packet<ServerPacketHandler>> void registerC2S(int id, Class<T> clazz, Supplier<T> creator) { | ||
| 18 | c2sPacketIds.put(clazz, id); | ||
| 19 | c2sPacketCreators.put(id, creator); | ||
| 20 | } | ||
| 21 | |||
| 22 | private static <T extends Packet<GuiController>> void registerS2C(int id, Class<T> clazz, Supplier<T> creator) { | ||
| 23 | s2cPacketIds.put(clazz, id); | ||
| 24 | s2cPacketCreators.put(id, creator); | ||
| 25 | } | ||
| 26 | |||
| 27 | static { | ||
| 28 | registerC2S(0, LoginC2SPacket.class, LoginC2SPacket::new); | ||
| 29 | registerC2S(1, ConfirmChangeC2SPacket.class, ConfirmChangeC2SPacket::new); | ||
| 30 | registerC2S(2, RenameC2SPacket.class, RenameC2SPacket::new); | ||
| 31 | registerC2S(3, RemoveMappingC2SPacket.class, RemoveMappingC2SPacket::new); | ||
| 32 | registerC2S(4, ChangeDocsC2SPacket.class, ChangeDocsC2SPacket::new); | ||
| 33 | registerC2S(5, MarkDeobfuscatedC2SPacket.class, MarkDeobfuscatedC2SPacket::new); | ||
| 34 | registerC2S(6, MessageC2SPacket.class, MessageC2SPacket::new); | ||
| 35 | |||
| 36 | registerS2C(0, KickS2CPacket.class, KickS2CPacket::new); | ||
| 37 | registerS2C(1, SyncMappingsS2CPacket.class, SyncMappingsS2CPacket::new); | ||
| 38 | registerS2C(2, RenameS2CPacket.class, RenameS2CPacket::new); | ||
| 39 | registerS2C(3, RemoveMappingS2CPacket.class, RemoveMappingS2CPacket::new); | ||
| 40 | registerS2C(4, ChangeDocsS2CPacket.class, ChangeDocsS2CPacket::new); | ||
| 41 | registerS2C(5, MarkDeobfuscatedS2CPacket.class, MarkDeobfuscatedS2CPacket::new); | ||
| 42 | registerS2C(6, MessageS2CPacket.class, MessageS2CPacket::new); | ||
| 43 | registerS2C(7, UserListS2CPacket.class, UserListS2CPacket::new); | ||
| 44 | } | ||
| 45 | |||
| 46 | public static int getC2SId(Packet<ServerPacketHandler> packet) { | ||
| 47 | return c2sPacketIds.get(packet.getClass()); | ||
| 48 | } | ||
| 49 | |||
| 50 | public static Packet<ServerPacketHandler> createC2SPacket(int id) { | ||
| 51 | Supplier<? extends Packet<ServerPacketHandler>> creator = c2sPacketCreators.get(id); | ||
| 52 | return creator == null ? null : creator.get(); | ||
| 53 | } | ||
| 54 | |||
| 55 | public static int getS2CId(Packet<GuiController> packet) { | ||
| 56 | return s2cPacketIds.get(packet.getClass()); | ||
| 57 | } | ||
| 58 | |||
| 59 | public static Packet<GuiController> createS2CPacket(int id) { | ||
| 60 | Supplier<? extends Packet<GuiController>> creator = s2cPacketCreators.get(id); | ||
| 61 | return creator == null ? null : creator.get(); | ||
| 62 | } | ||
| 63 | |||
| 64 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java new file mode 100644 index 0000000..a3f3d91 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 4 | import cuchaz.enigma.throwables.IllegalNameException; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | import cuchaz.enigma.utils.Message; | ||
| 7 | |||
| 8 | import java.io.DataInput; | ||
| 9 | import java.io.DataOutput; | ||
| 10 | import java.io.IOException; | ||
| 11 | |||
| 12 | public class RemoveMappingC2SPacket implements Packet<ServerPacketHandler> { | ||
| 13 | private Entry<?> entry; | ||
| 14 | |||
| 15 | RemoveMappingC2SPacket() { | ||
| 16 | } | ||
| 17 | |||
| 18 | public RemoveMappingC2SPacket(Entry<?> entry) { | ||
| 19 | this.entry = entry; | ||
| 20 | } | ||
| 21 | |||
| 22 | @Override | ||
| 23 | public void read(DataInput input) throws IOException { | ||
| 24 | this.entry = PacketHelper.readEntry(input); | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public void write(DataOutput output) throws IOException { | ||
| 29 | PacketHelper.writeEntry(output, entry); | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public void handle(ServerPacketHandler handler) { | ||
| 34 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); | ||
| 35 | |||
| 36 | if (valid) { | ||
| 37 | try { | ||
| 38 | handler.getServer().getMappings().removeByObf(entry); | ||
| 39 | } catch (IllegalNameException e) { | ||
| 40 | valid = false; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | if (!valid) { | ||
| 45 | handler.getServer().sendCorrectMapping(handler.getClient(), entry, true); | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | |||
| 49 | handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " removed the mapping for " + entry); | ||
| 50 | |||
| 51 | int syncId = handler.getServer().lockEntry(handler.getClient(), entry); | ||
| 52 | handler.getServer().sendToAllExcept(handler.getClient(), new RemoveMappingS2CPacket(syncId, entry)); | ||
| 53 | handler.getServer().sendMessage(Message.removeMapping(handler.getServer().getUsername(handler.getClient()), entry)); | ||
| 54 | } | ||
| 55 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java new file mode 100644 index 0000000..7bb1b00 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.gui.GuiController; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | |||
| 7 | import java.io.DataInput; | ||
| 8 | import java.io.DataOutput; | ||
| 9 | import java.io.IOException; | ||
| 10 | |||
| 11 | public class RemoveMappingS2CPacket implements Packet<GuiController> { | ||
| 12 | private int syncId; | ||
| 13 | private Entry<?> entry; | ||
| 14 | |||
| 15 | RemoveMappingS2CPacket() { | ||
| 16 | } | ||
| 17 | |||
| 18 | public RemoveMappingS2CPacket(int syncId, Entry<?> entry) { | ||
| 19 | this.syncId = syncId; | ||
| 20 | this.entry = entry; | ||
| 21 | } | ||
| 22 | |||
| 23 | @Override | ||
| 24 | public void read(DataInput input) throws IOException { | ||
| 25 | this.syncId = input.readUnsignedShort(); | ||
| 26 | this.entry = PacketHelper.readEntry(input); | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public void write(DataOutput output) throws IOException { | ||
| 31 | output.writeShort(syncId); | ||
| 32 | PacketHelper.writeEntry(output, entry); | ||
| 33 | } | ||
| 34 | |||
| 35 | @Override | ||
| 36 | public void handle(GuiController controller) { | ||
| 37 | controller.removeMapping(new EntryReference<>(entry, entry.getName()), false); | ||
| 38 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java new file mode 100644 index 0000000..03e95d6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 4 | import cuchaz.enigma.throwables.IllegalNameException; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 7 | import cuchaz.enigma.utils.Message; | ||
| 8 | |||
| 9 | import java.io.DataInput; | ||
| 10 | import java.io.DataOutput; | ||
| 11 | import java.io.IOException; | ||
| 12 | |||
| 13 | public class RenameC2SPacket implements Packet<ServerPacketHandler> { | ||
| 14 | private Entry<?> entry; | ||
| 15 | private String newName; | ||
| 16 | private boolean refreshClassTree; | ||
| 17 | |||
| 18 | RenameC2SPacket() { | ||
| 19 | } | ||
| 20 | |||
| 21 | public RenameC2SPacket(Entry<?> entry, String newName, boolean refreshClassTree) { | ||
| 22 | this.entry = entry; | ||
| 23 | this.newName = newName; | ||
| 24 | this.refreshClassTree = refreshClassTree; | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public void read(DataInput input) throws IOException { | ||
| 29 | this.entry = PacketHelper.readEntry(input); | ||
| 30 | this.newName = PacketHelper.readString(input); | ||
| 31 | this.refreshClassTree = input.readBoolean(); | ||
| 32 | } | ||
| 33 | |||
| 34 | @Override | ||
| 35 | public void write(DataOutput output) throws IOException { | ||
| 36 | PacketHelper.writeEntry(output, entry); | ||
| 37 | PacketHelper.writeString(output, newName); | ||
| 38 | output.writeBoolean(refreshClassTree); | ||
| 39 | } | ||
| 40 | |||
| 41 | @Override | ||
| 42 | public void handle(ServerPacketHandler handler) { | ||
| 43 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); | ||
| 44 | |||
| 45 | if (valid) { | ||
| 46 | try { | ||
| 47 | handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(newName)); | ||
| 48 | } catch (IllegalNameException e) { | ||
| 49 | valid = false; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | if (!valid) { | ||
| 54 | handler.getServer().sendCorrectMapping(handler.getClient(), entry, refreshClassTree); | ||
| 55 | return; | ||
| 56 | } | ||
| 57 | |||
| 58 | handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " renamed " + entry + " to " + newName); | ||
| 59 | |||
| 60 | int syncId = handler.getServer().lockEntry(handler.getClient(), entry); | ||
| 61 | handler.getServer().sendToAllExcept(handler.getClient(), new RenameS2CPacket(syncId, entry, newName, refreshClassTree)); | ||
| 62 | handler.getServer().sendMessage(Message.rename(handler.getServer().getUsername(handler.getClient()), entry, newName)); | ||
| 63 | } | ||
| 64 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java new file mode 100644 index 0000000..058f0e5 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.gui.GuiController; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | |||
| 7 | import java.io.DataInput; | ||
| 8 | import java.io.DataOutput; | ||
| 9 | import java.io.IOException; | ||
| 10 | |||
| 11 | public class RenameS2CPacket implements Packet<GuiController> { | ||
| 12 | private int syncId; | ||
| 13 | private Entry<?> entry; | ||
| 14 | private String newName; | ||
| 15 | private boolean refreshClassTree; | ||
| 16 | |||
| 17 | RenameS2CPacket() { | ||
| 18 | } | ||
| 19 | |||
| 20 | public RenameS2CPacket(int syncId, Entry<?> entry, String newName, boolean refreshClassTree) { | ||
| 21 | this.syncId = syncId; | ||
| 22 | this.entry = entry; | ||
| 23 | this.newName = newName; | ||
| 24 | this.refreshClassTree = refreshClassTree; | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public void read(DataInput input) throws IOException { | ||
| 29 | this.syncId = input.readUnsignedShort(); | ||
| 30 | this.entry = PacketHelper.readEntry(input); | ||
| 31 | this.newName = PacketHelper.readString(input); | ||
| 32 | this.refreshClassTree = input.readBoolean(); | ||
| 33 | } | ||
| 34 | |||
| 35 | @Override | ||
| 36 | public void write(DataOutput output) throws IOException { | ||
| 37 | output.writeShort(syncId); | ||
| 38 | PacketHelper.writeEntry(output, entry); | ||
| 39 | PacketHelper.writeString(output, newName); | ||
| 40 | output.writeBoolean(refreshClassTree); | ||
| 41 | } | ||
| 42 | |||
| 43 | @Override | ||
| 44 | public void handle(GuiController controller) { | ||
| 45 | controller.rename(new EntryReference<>(entry, entry.getName()), newName, refreshClassTree, false); | ||
| 46 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | ||
| 47 | } | ||
| 48 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java new file mode 100644 index 0000000..e6378d1 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java | |||
| @@ -0,0 +1,88 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.GuiController; | ||
| 4 | import cuchaz.enigma.network.EnigmaServer; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 7 | import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; | ||
| 8 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 10 | |||
| 11 | import java.io.DataInput; | ||
| 12 | import java.io.DataOutput; | ||
| 13 | import java.io.IOException; | ||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.List; | ||
| 16 | import java.util.stream.Collectors; | ||
| 17 | |||
| 18 | public class SyncMappingsS2CPacket implements Packet<GuiController> { | ||
| 19 | private EntryTree<EntryMapping> mappings; | ||
| 20 | |||
| 21 | SyncMappingsS2CPacket() { | ||
| 22 | } | ||
| 23 | |||
| 24 | public SyncMappingsS2CPacket(EntryTree<EntryMapping> mappings) { | ||
| 25 | this.mappings = mappings; | ||
| 26 | } | ||
| 27 | |||
| 28 | @Override | ||
| 29 | public void read(DataInput input) throws IOException { | ||
| 30 | mappings = new HashEntryTree<>(); | ||
| 31 | int size = input.readInt(); | ||
| 32 | for (int i = 0; i < size; i++) { | ||
| 33 | readEntryTreeNode(input, null); | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | private void readEntryTreeNode(DataInput input, Entry<?> parent) throws IOException { | ||
| 38 | Entry<?> entry = PacketHelper.readEntry(input, parent, false); | ||
| 39 | EntryMapping mapping = null; | ||
| 40 | if (input.readBoolean()) { | ||
| 41 | String name = input.readUTF(); | ||
| 42 | if (input.readBoolean()) { | ||
| 43 | String javadoc = input.readUTF(); | ||
| 44 | mapping = new EntryMapping(name, javadoc); | ||
| 45 | } else { | ||
| 46 | mapping = new EntryMapping(name); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | mappings.insert(entry, mapping); | ||
| 50 | int size = input.readUnsignedShort(); | ||
| 51 | for (int i = 0; i < size; i++) { | ||
| 52 | readEntryTreeNode(input, entry); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | @Override | ||
| 57 | public void write(DataOutput output) throws IOException { | ||
| 58 | List<EntryTreeNode<EntryMapping>> roots = mappings.getRootNodes().collect(Collectors.toList()); | ||
| 59 | output.writeInt(roots.size()); | ||
| 60 | for (EntryTreeNode<EntryMapping> node : roots) { | ||
| 61 | writeEntryTreeNode(output, node); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | private static void writeEntryTreeNode(DataOutput output, EntryTreeNode<EntryMapping> node) throws IOException { | ||
| 66 | PacketHelper.writeEntry(output, node.getEntry(), false); | ||
| 67 | EntryMapping value = node.getValue(); | ||
| 68 | output.writeBoolean(value != null); | ||
| 69 | if (value != null) { | ||
| 70 | PacketHelper.writeString(output, value.getTargetName()); | ||
| 71 | output.writeBoolean(value.getJavadoc() != null); | ||
| 72 | if (value.getJavadoc() != null) { | ||
| 73 | PacketHelper.writeString(output, value.getJavadoc()); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | Collection<? extends EntryTreeNode<EntryMapping>> children = node.getChildNodes(); | ||
| 77 | output.writeShort(children.size()); | ||
| 78 | for (EntryTreeNode<EntryMapping> child : children) { | ||
| 79 | writeEntryTreeNode(output, child); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | @Override | ||
| 84 | public void handle(GuiController controller) { | ||
| 85 | controller.openMappings(mappings); | ||
| 86 | controller.sendPacket(new ConfirmChangeC2SPacket(EnigmaServer.DUMMY_SYNC_ID)); | ||
| 87 | } | ||
| 88 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java new file mode 100644 index 0000000..8904848 --- /dev/null +++ b/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import java.io.DataInput; | ||
| 4 | import java.io.DataOutput; | ||
| 5 | import java.io.IOException; | ||
| 6 | import java.util.ArrayList; | ||
| 7 | import java.util.List; | ||
| 8 | |||
| 9 | import cuchaz.enigma.gui.GuiController; | ||
| 10 | |||
| 11 | public class UserListS2CPacket implements Packet<GuiController> { | ||
| 12 | |||
| 13 | private List<String> users; | ||
| 14 | |||
| 15 | UserListS2CPacket() { | ||
| 16 | } | ||
| 17 | |||
| 18 | public UserListS2CPacket(List<String> users) { | ||
| 19 | this.users = users; | ||
| 20 | } | ||
| 21 | |||
| 22 | @Override | ||
| 23 | public void read(DataInput input) throws IOException { | ||
| 24 | int len = input.readUnsignedShort(); | ||
| 25 | users = new ArrayList<>(len); | ||
| 26 | for (int i = 0; i < len; i++) { | ||
| 27 | users.add(input.readUTF()); | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public void write(DataOutput output) throws IOException { | ||
| 33 | output.writeShort(users.size()); | ||
| 34 | for (String user : users) { | ||
| 35 | PacketHelper.writeString(output, user); | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | @Override | ||
| 40 | public void handle(GuiController handler) { | ||
| 41 | handler.updateUserList(users); | ||
| 42 | } | ||
| 43 | |||
| 44 | } | ||