diff options
| author | 2020-06-03 13:39:42 -0400 | |
|---|---|---|
| committer | 2020-06-03 18:39:42 +0100 | |
| commit | 0f47403d0220757fed189b76e2071e25b1025cb8 (patch) | |
| tree | 879bf72c4476f0a5e0d82da99d7ff2b2276bcaca /enigma-server/src | |
| parent | Fix search dialog hanging for a short time sometimes (#250) (diff) | |
| download | enigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.tar.gz enigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.tar.xz enigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.zip | |
Split GUI code to separate module (#242)
* Split into modules
* Post merge compile fixes
Co-authored-by: modmuss50 <modmuss50@gmail.com>
Diffstat (limited to 'enigma-server/src')
25 files changed, 1988 insertions, 0 deletions
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java b/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java new file mode 100644 index 0000000..720744b --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 5 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 6 | import cuchaz.enigma.network.packet.Packet; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 8 | |||
| 9 | import java.util.List; | ||
| 10 | |||
| 11 | public interface ClientPacketHandler { | ||
| 12 | void openMappings(EntryTree<EntryMapping> mappings); | ||
| 13 | |||
| 14 | void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree, boolean jumpToReference); | ||
| 15 | |||
| 16 | void removeMapping(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference); | ||
| 17 | |||
| 18 | void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs, boolean jumpToReference); | ||
| 19 | |||
| 20 | void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference); | ||
| 21 | |||
| 22 | void disconnectIfConnected(String reason); | ||
| 23 | |||
| 24 | void sendPacket(Packet<ServerPacketHandler> packet); | ||
| 25 | |||
| 26 | void addMessage(Message message); | ||
| 27 | |||
| 28 | void updateUserList(List<String> users); | ||
| 29 | } | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java new file mode 100644 index 0000000..924302f --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java | |||
| @@ -0,0 +1,200 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import com.google.common.io.MoreFiles; | ||
| 4 | import cuchaz.enigma.*; | ||
| 5 | import cuchaz.enigma.translation.mapping.serde.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 | import joptsimple.ValueConverter; | ||
| 13 | |||
| 14 | import java.io.IOException; | ||
| 15 | import java.io.PrintWriter; | ||
| 16 | import java.nio.file.Files; | ||
| 17 | import java.nio.file.Path; | ||
| 18 | import java.nio.file.Paths; | ||
| 19 | import java.util.concurrent.BlockingQueue; | ||
| 20 | import java.util.concurrent.Executors; | ||
| 21 | import java.util.concurrent.LinkedBlockingDeque; | ||
| 22 | import java.util.concurrent.TimeUnit; | ||
| 23 | |||
| 24 | public class DedicatedEnigmaServer extends EnigmaServer { | ||
| 25 | |||
| 26 | private final EnigmaProfile profile; | ||
| 27 | private final MappingFormat mappingFormat; | ||
| 28 | private final Path mappingsFile; | ||
| 29 | private final PrintWriter log; | ||
| 30 | private BlockingQueue<Runnable> tasks = new LinkedBlockingDeque<>(); | ||
| 31 | |||
| 32 | public DedicatedEnigmaServer( | ||
| 33 | byte[] jarChecksum, | ||
| 34 | char[] password, | ||
| 35 | EnigmaProfile profile, | ||
| 36 | MappingFormat mappingFormat, | ||
| 37 | Path mappingsFile, | ||
| 38 | PrintWriter log, | ||
| 39 | EntryRemapper mappings, | ||
| 40 | int port | ||
| 41 | ) { | ||
| 42 | super(jarChecksum, password, mappings, port); | ||
| 43 | this.profile = profile; | ||
| 44 | this.mappingFormat = mappingFormat; | ||
| 45 | this.mappingsFile = mappingsFile; | ||
| 46 | this.log = log; | ||
| 47 | } | ||
| 48 | |||
| 49 | @Override | ||
| 50 | protected void runOnThread(Runnable task) { | ||
| 51 | tasks.add(task); | ||
| 52 | } | ||
| 53 | |||
| 54 | @Override | ||
| 55 | public void log(String message) { | ||
| 56 | super.log(message); | ||
| 57 | log.println(message); | ||
| 58 | } | ||
| 59 | |||
| 60 | public static void main(String[] args) { | ||
| 61 | OptionParser parser = new OptionParser(); | ||
| 62 | |||
| 63 | OptionSpec<Path> jarOpt = parser.accepts("jar", "Jar file to open at startup") | ||
| 64 | .withRequiredArg() | ||
| 65 | .required() | ||
| 66 | .withValuesConvertedBy(PathConverter.INSTANCE); | ||
| 67 | |||
| 68 | OptionSpec<Path> mappingsOpt = parser.accepts("mappings", "Mappings file to open at startup") | ||
| 69 | .withRequiredArg() | ||
| 70 | .required() | ||
| 71 | .withValuesConvertedBy(PathConverter.INSTANCE); | ||
| 72 | |||
| 73 | OptionSpec<Path> profileOpt = parser.accepts("profile", "Profile json to apply at startup") | ||
| 74 | .withRequiredArg() | ||
| 75 | .withValuesConvertedBy(PathConverter.INSTANCE); | ||
| 76 | |||
| 77 | OptionSpec<Integer> portOpt = parser.accepts("port", "Port to run the server on") | ||
| 78 | .withOptionalArg() | ||
| 79 | .ofType(Integer.class) | ||
| 80 | .defaultsTo(EnigmaServer.DEFAULT_PORT); | ||
| 81 | |||
| 82 | OptionSpec<String> passwordOpt = parser.accepts("password", "The password to join the server") | ||
| 83 | .withRequiredArg() | ||
| 84 | .defaultsTo(""); | ||
| 85 | |||
| 86 | OptionSpec<Path> logFileOpt = parser.accepts("log", "The log file to write to") | ||
| 87 | .withRequiredArg() | ||
| 88 | .withValuesConvertedBy(PathConverter.INSTANCE) | ||
| 89 | .defaultsTo(Paths.get("log.txt")); | ||
| 90 | |||
| 91 | OptionSet parsedArgs = parser.parse(args); | ||
| 92 | Path jar = parsedArgs.valueOf(jarOpt); | ||
| 93 | Path mappingsFile = parsedArgs.valueOf(mappingsOpt); | ||
| 94 | Path profileFile = parsedArgs.valueOf(profileOpt); | ||
| 95 | int port = parsedArgs.valueOf(portOpt); | ||
| 96 | char[] password = parsedArgs.valueOf(passwordOpt).toCharArray(); | ||
| 97 | if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) { | ||
| 98 | System.err.println("Password too long, must be at most " + EnigmaServer.MAX_PASSWORD_LENGTH + " characters"); | ||
| 99 | System.exit(1); | ||
| 100 | } | ||
| 101 | Path logFile = parsedArgs.valueOf(logFileOpt); | ||
| 102 | |||
| 103 | System.out.println("Starting Enigma server"); | ||
| 104 | DedicatedEnigmaServer server; | ||
| 105 | try { | ||
| 106 | byte[] checksum = Utils.zipSha1(parsedArgs.valueOf(jarOpt)); | ||
| 107 | |||
| 108 | EnigmaProfile profile = EnigmaProfile.read(profileFile); | ||
| 109 | Enigma enigma = Enigma.builder().setProfile(profile).build(); | ||
| 110 | System.out.println("Indexing Jar..."); | ||
| 111 | EnigmaProject project = enigma.openJar(jar, ProgressListener.none()); | ||
| 112 | |||
| 113 | MappingFormat mappingFormat = MappingFormat.ENIGMA_DIRECTORY; | ||
| 114 | EntryRemapper mappings; | ||
| 115 | if (!Files.exists(mappingsFile)) { | ||
| 116 | mappings = EntryRemapper.empty(project.getJarIndex()); | ||
| 117 | } else { | ||
| 118 | System.out.println("Reading mappings..."); | ||
| 119 | if (Files.isDirectory(mappingsFile)) { | ||
| 120 | mappingFormat = MappingFormat.ENIGMA_DIRECTORY; | ||
| 121 | } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(mappingsFile))) { | ||
| 122 | mappingFormat = MappingFormat.ENIGMA_ZIP; | ||
| 123 | } else { | ||
| 124 | mappingFormat = MappingFormat.ENIGMA_FILE; | ||
| 125 | } | ||
| 126 | mappings = EntryRemapper.mapped(project.getJarIndex(), mappingFormat.read(mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters())); | ||
| 127 | } | ||
| 128 | |||
| 129 | PrintWriter log = new PrintWriter(Files.newBufferedWriter(logFile)); | ||
| 130 | |||
| 131 | server = new DedicatedEnigmaServer(checksum, password, profile, mappingFormat, mappingsFile, log, mappings, port); | ||
| 132 | server.start(); | ||
| 133 | System.out.println("Server started"); | ||
| 134 | } catch (IOException | MappingParseException e) { | ||
| 135 | System.err.println("Error starting server!"); | ||
| 136 | e.printStackTrace(); | ||
| 137 | System.exit(1); | ||
| 138 | return; | ||
| 139 | } | ||
| 140 | |||
| 141 | // noinspection RedundantSuppression | ||
| 142 | // noinspection Convert2MethodRef - javac 8 bug | ||
| 143 | Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> server.runOnThread(() -> server.saveMappings()), 0, 1, TimeUnit.MINUTES); | ||
| 144 | Runtime.getRuntime().addShutdownHook(new Thread(server::saveMappings)); | ||
| 145 | |||
| 146 | while (true) { | ||
| 147 | try { | ||
| 148 | server.tasks.take().run(); | ||
| 149 | } catch (InterruptedException e) { | ||
| 150 | break; | ||
| 151 | } | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | @Override | ||
| 156 | public synchronized void stop() { | ||
| 157 | super.stop(); | ||
| 158 | System.exit(0); | ||
| 159 | } | ||
| 160 | |||
| 161 | private void saveMappings() { | ||
| 162 | mappingFormat.write(getMappings().getObfToDeobf(), getMappings().takeMappingDelta(), mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters()); | ||
| 163 | log.flush(); | ||
| 164 | } | ||
| 165 | |||
| 166 | public static class PathConverter implements ValueConverter<Path> { | ||
| 167 | public static final ValueConverter<Path> INSTANCE = new PathConverter(); | ||
| 168 | |||
| 169 | PathConverter() { | ||
| 170 | } | ||
| 171 | |||
| 172 | @Override | ||
| 173 | public Path convert(String path) { | ||
| 174 | // expand ~ to the home dir | ||
| 175 | if (path.startsWith("~")) { | ||
| 176 | // get the home dir | ||
| 177 | Path dirHome = Paths.get(System.getProperty("user.home")); | ||
| 178 | |||
| 179 | // is the path just ~/ or is it ~user/ ? | ||
| 180 | if (path.startsWith("~/")) { | ||
| 181 | return dirHome.resolve(path.substring(2)); | ||
| 182 | } else { | ||
| 183 | return dirHome.getParent().resolve(path.substring(1)); | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | return Paths.get(path); | ||
| 188 | } | ||
| 189 | |||
| 190 | @Override | ||
| 191 | public Class<? extends Path> valueType() { | ||
| 192 | return Path.class; | ||
| 193 | } | ||
| 194 | |||
| 195 | @Override | ||
| 196 | public String valuePattern() { | ||
| 197 | return "path"; | ||
| 198 | } | ||
| 199 | } | ||
| 200 | } | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaClient.java b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaClient.java new file mode 100644 index 0000000..71bd011 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaClient.java | |||
| @@ -0,0 +1,78 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.packet.Packet; | ||
| 4 | import cuchaz.enigma.network.packet.PacketRegistry; | ||
| 5 | |||
| 6 | import javax.swing.*; | ||
| 7 | import java.io.*; | ||
| 8 | import java.net.Socket; | ||
| 9 | import java.net.SocketException; | ||
| 10 | |||
| 11 | public class EnigmaClient { | ||
| 12 | |||
| 13 | private final ClientPacketHandler controller; | ||
| 14 | |||
| 15 | private final String ip; | ||
| 16 | private final int port; | ||
| 17 | private Socket socket; | ||
| 18 | private DataOutput output; | ||
| 19 | |||
| 20 | public EnigmaClient(ClientPacketHandler controller, String ip, int port) { | ||
| 21 | this.controller = controller; | ||
| 22 | this.ip = ip; | ||
| 23 | this.port = port; | ||
| 24 | } | ||
| 25 | |||
| 26 | public void connect() throws IOException { | ||
| 27 | socket = new Socket(ip, port); | ||
| 28 | output = new DataOutputStream(socket.getOutputStream()); | ||
| 29 | Thread thread = new Thread(() -> { | ||
| 30 | try { | ||
| 31 | DataInput input = new DataInputStream(socket.getInputStream()); | ||
| 32 | while (true) { | ||
| 33 | int packetId; | ||
| 34 | try { | ||
| 35 | packetId = input.readUnsignedByte(); | ||
| 36 | } catch (EOFException | SocketException e) { | ||
| 37 | break; | ||
| 38 | } | ||
| 39 | Packet<ClientPacketHandler> packet = PacketRegistry.createS2CPacket(packetId); | ||
| 40 | if (packet == null) { | ||
| 41 | throw new IOException("Received invalid packet id " + packetId); | ||
| 42 | } | ||
| 43 | packet.read(input); | ||
| 44 | SwingUtilities.invokeLater(() -> packet.handle(controller)); | ||
| 45 | } | ||
| 46 | } catch (IOException e) { | ||
| 47 | controller.disconnectIfConnected(e.toString()); | ||
| 48 | return; | ||
| 49 | } | ||
| 50 | controller.disconnectIfConnected("Disconnected"); | ||
| 51 | }); | ||
| 52 | thread.setName("Client I/O thread"); | ||
| 53 | thread.setDaemon(true); | ||
| 54 | thread.start(); | ||
| 55 | } | ||
| 56 | |||
| 57 | public synchronized void disconnect() { | ||
| 58 | if (socket != null && !socket.isClosed()) { | ||
| 59 | try { | ||
| 60 | socket.close(); | ||
| 61 | } catch (IOException e1) { | ||
| 62 | System.err.println("Failed to close socket"); | ||
| 63 | e1.printStackTrace(); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | |||
| 69 | public void sendPacket(Packet<ServerPacketHandler> packet) { | ||
| 70 | try { | ||
| 71 | output.writeByte(PacketRegistry.getC2SId(packet)); | ||
| 72 | packet.write(output); | ||
| 73 | } catch (IOException e) { | ||
| 74 | controller.disconnectIfConnected(e.toString()); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | } | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java new file mode 100644 index 0000000..6027a6b --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java | |||
| @@ -0,0 +1,290 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.packet.KickS2CPacket; | ||
| 4 | import cuchaz.enigma.network.packet.MessageS2CPacket; | ||
| 5 | import cuchaz.enigma.network.packet.Packet; | ||
| 6 | import cuchaz.enigma.network.packet.PacketRegistry; | ||
| 7 | import cuchaz.enigma.network.packet.RemoveMappingS2CPacket; | ||
| 8 | import cuchaz.enigma.network.packet.RenameS2CPacket; | ||
| 9 | import cuchaz.enigma.network.packet.UserListS2CPacket; | ||
| 10 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 11 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 12 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 13 | |||
| 14 | import java.io.DataInput; | ||
| 15 | import java.io.DataInputStream; | ||
| 16 | import java.io.DataOutput; | ||
| 17 | import java.io.DataOutputStream; | ||
| 18 | import java.io.EOFException; | ||
| 19 | import java.io.IOException; | ||
| 20 | import java.net.ServerSocket; | ||
| 21 | import java.net.Socket; | ||
| 22 | import java.net.SocketException; | ||
| 23 | import java.util.ArrayList; | ||
| 24 | import java.util.Collections; | ||
| 25 | import java.util.HashMap; | ||
| 26 | import java.util.HashSet; | ||
| 27 | import java.util.List; | ||
| 28 | import java.util.Map; | ||
| 29 | import java.util.Set; | ||
| 30 | import java.util.concurrent.CopyOnWriteArrayList; | ||
| 31 | |||
| 32 | public abstract class EnigmaServer { | ||
| 33 | |||
| 34 | // https://discordapp.com/channels/507304429255393322/566418023372816394/700292322918793347 | ||
| 35 | public static final int DEFAULT_PORT = 34712; | ||
| 36 | public static final int PROTOCOL_VERSION = 0; | ||
| 37 | public static final String OWNER_USERNAME = "Owner"; | ||
| 38 | public static final int CHECKSUM_SIZE = 20; | ||
| 39 | public static final int MAX_PASSWORD_LENGTH = 255; // length is written as a byte in the login packet | ||
| 40 | |||
| 41 | private final int port; | ||
| 42 | private ServerSocket socket; | ||
| 43 | private List<Socket> clients = new CopyOnWriteArrayList<>(); | ||
| 44 | private Map<Socket, String> usernames = new HashMap<>(); | ||
| 45 | private Set<Socket> unapprovedClients = new HashSet<>(); | ||
| 46 | |||
| 47 | private final byte[] jarChecksum; | ||
| 48 | private final char[] password; | ||
| 49 | |||
| 50 | public static final int DUMMY_SYNC_ID = 0; | ||
| 51 | private final EntryRemapper mappings; | ||
| 52 | private Map<Entry<?>, Integer> syncIds = new HashMap<>(); | ||
| 53 | private Map<Integer, Entry<?>> inverseSyncIds = new HashMap<>(); | ||
| 54 | private Map<Integer, Set<Socket>> clientsNeedingConfirmation = new HashMap<>(); | ||
| 55 | private int nextSyncId = DUMMY_SYNC_ID + 1; | ||
| 56 | |||
| 57 | private static int nextIoId = 0; | ||
| 58 | |||
| 59 | public EnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) { | ||
| 60 | this.jarChecksum = jarChecksum; | ||
| 61 | this.password = password; | ||
| 62 | this.mappings = mappings; | ||
| 63 | this.port = port; | ||
| 64 | } | ||
| 65 | |||
| 66 | public void start() throws IOException { | ||
| 67 | socket = new ServerSocket(port); | ||
| 68 | log("Server started on " + socket.getInetAddress() + ":" + port); | ||
| 69 | Thread thread = new Thread(() -> { | ||
| 70 | try { | ||
| 71 | while (!socket.isClosed()) { | ||
| 72 | acceptClient(); | ||
| 73 | } | ||
| 74 | } catch (SocketException e) { | ||
| 75 | System.out.println("Server closed"); | ||
| 76 | } catch (IOException e) { | ||
| 77 | e.printStackTrace(); | ||
| 78 | } | ||
| 79 | }); | ||
| 80 | thread.setName("Server client listener"); | ||
| 81 | thread.setDaemon(true); | ||
| 82 | thread.start(); | ||
| 83 | } | ||
| 84 | |||
| 85 | private void acceptClient() throws IOException { | ||
| 86 | Socket client = socket.accept(); | ||
| 87 | clients.add(client); | ||
| 88 | Thread thread = new Thread(() -> { | ||
| 89 | try { | ||
| 90 | DataInput input = new DataInputStream(client.getInputStream()); | ||
| 91 | while (true) { | ||
| 92 | int packetId; | ||
| 93 | try { | ||
| 94 | packetId = input.readUnsignedByte(); | ||
| 95 | } catch (EOFException | SocketException e) { | ||
| 96 | break; | ||
| 97 | } | ||
| 98 | Packet<ServerPacketHandler> packet = PacketRegistry.createC2SPacket(packetId); | ||
| 99 | if (packet == null) { | ||
| 100 | throw new IOException("Received invalid packet id " + packetId); | ||
| 101 | } | ||
| 102 | packet.read(input); | ||
| 103 | runOnThread(() -> packet.handle(new ServerPacketHandler(client, this))); | ||
| 104 | } | ||
| 105 | } catch (IOException e) { | ||
| 106 | kick(client, e.toString()); | ||
| 107 | e.printStackTrace(); | ||
| 108 | return; | ||
| 109 | } | ||
| 110 | kick(client, "disconnect.disconnected"); | ||
| 111 | }); | ||
| 112 | thread.setName("Server I/O thread #" + (nextIoId++)); | ||
| 113 | thread.setDaemon(true); | ||
| 114 | thread.start(); | ||
| 115 | } | ||
| 116 | |||
| 117 | public void stop() { | ||
| 118 | runOnThread(() -> { | ||
| 119 | if (socket != null && !socket.isClosed()) { | ||
| 120 | for (Socket client : clients) { | ||
| 121 | kick(client, "disconnect.server_closed"); | ||
| 122 | } | ||
| 123 | try { | ||
| 124 | socket.close(); | ||
| 125 | } catch (IOException e) { | ||
| 126 | System.err.println("Failed to close server socket"); | ||
| 127 | e.printStackTrace(); | ||
| 128 | } | ||
| 129 | } | ||
| 130 | }); | ||
| 131 | } | ||
| 132 | |||
| 133 | public void kick(Socket client, String reason) { | ||
| 134 | if (!clients.remove(client)) return; | ||
| 135 | |||
| 136 | sendPacket(client, new KickS2CPacket(reason)); | ||
| 137 | |||
| 138 | clientsNeedingConfirmation.values().removeIf(list -> { | ||
| 139 | list.remove(client); | ||
| 140 | return list.isEmpty(); | ||
| 141 | }); | ||
| 142 | String username = usernames.remove(client); | ||
| 143 | try { | ||
| 144 | client.close(); | ||
| 145 | } catch (IOException e) { | ||
| 146 | System.err.println("Failed to close server client socket"); | ||
| 147 | e.printStackTrace(); | ||
| 148 | } | ||
| 149 | |||
| 150 | if (username != null) { | ||
| 151 | System.out.println("Kicked " + username + " because " + reason); | ||
| 152 | sendMessage(Message.disconnect(username)); | ||
| 153 | } | ||
| 154 | sendUsernamePacket(); | ||
| 155 | } | ||
| 156 | |||
| 157 | public boolean isUsernameTaken(String username) { | ||
| 158 | return usernames.containsValue(username); | ||
| 159 | } | ||
| 160 | |||
| 161 | public void setUsername(Socket client, String username) { | ||
| 162 | usernames.put(client, username); | ||
| 163 | sendUsernamePacket(); | ||
| 164 | } | ||
| 165 | |||
| 166 | private void sendUsernamePacket() { | ||
| 167 | List<String> usernames = new ArrayList<>(this.usernames.values()); | ||
| 168 | Collections.sort(usernames); | ||
| 169 | sendToAll(new UserListS2CPacket(usernames)); | ||
| 170 | } | ||
| 171 | |||
| 172 | public String getUsername(Socket client) { | ||
| 173 | return usernames.get(client); | ||
| 174 | } | ||
| 175 | |||
| 176 | public void sendPacket(Socket client, Packet<ClientPacketHandler> packet) { | ||
| 177 | if (!client.isClosed()) { | ||
| 178 | int packetId = PacketRegistry.getS2CId(packet); | ||
| 179 | try { | ||
| 180 | DataOutput output = new DataOutputStream(client.getOutputStream()); | ||
| 181 | output.writeByte(packetId); | ||
| 182 | packet.write(output); | ||
| 183 | } catch (IOException e) { | ||
| 184 | if (!(packet instanceof KickS2CPacket)) { | ||
| 185 | kick(client, e.toString()); | ||
| 186 | e.printStackTrace(); | ||
| 187 | } | ||
| 188 | } | ||
| 189 | } | ||
| 190 | } | ||
| 191 | |||
| 192 | public void sendToAll(Packet<ClientPacketHandler> packet) { | ||
| 193 | for (Socket client : clients) { | ||
| 194 | sendPacket(client, packet); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | public void sendToAllExcept(Socket excluded, Packet<ClientPacketHandler> packet) { | ||
| 199 | for (Socket client : clients) { | ||
| 200 | if (client != excluded) { | ||
| 201 | sendPacket(client, packet); | ||
| 202 | } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | public boolean canModifyEntry(Socket client, Entry<?> entry) { | ||
| 207 | if (unapprovedClients.contains(client)) { | ||
| 208 | return false; | ||
| 209 | } | ||
| 210 | |||
| 211 | Integer syncId = syncIds.get(entry); | ||
| 212 | if (syncId == null) { | ||
| 213 | return true; | ||
| 214 | } | ||
| 215 | Set<Socket> clients = clientsNeedingConfirmation.get(syncId); | ||
| 216 | return clients == null || !clients.contains(client); | ||
| 217 | } | ||
| 218 | |||
| 219 | public int lockEntry(Socket exception, Entry<?> entry) { | ||
| 220 | int syncId = nextSyncId; | ||
| 221 | nextSyncId++; | ||
| 222 | // sync id is sent as an unsigned short, can't have more than 65536 | ||
| 223 | if (nextSyncId == 65536) { | ||
| 224 | nextSyncId = DUMMY_SYNC_ID + 1; | ||
| 225 | } | ||
| 226 | Integer oldSyncId = syncIds.get(entry); | ||
| 227 | if (oldSyncId != null) { | ||
| 228 | clientsNeedingConfirmation.remove(oldSyncId); | ||
| 229 | } | ||
| 230 | syncIds.put(entry, syncId); | ||
| 231 | inverseSyncIds.put(syncId, entry); | ||
| 232 | Set<Socket> clients = new HashSet<>(this.clients); | ||
| 233 | clients.remove(exception); | ||
| 234 | clientsNeedingConfirmation.put(syncId, clients); | ||
| 235 | return syncId; | ||
| 236 | } | ||
| 237 | |||
| 238 | public void confirmChange(Socket client, int syncId) { | ||
| 239 | if (usernames.containsKey(client)) { | ||
| 240 | unapprovedClients.remove(client); | ||
| 241 | } | ||
| 242 | |||
| 243 | Set<Socket> clients = clientsNeedingConfirmation.get(syncId); | ||
| 244 | if (clients != null) { | ||
| 245 | clients.remove(client); | ||
| 246 | if (clients.isEmpty()) { | ||
| 247 | clientsNeedingConfirmation.remove(syncId); | ||
| 248 | syncIds.remove(inverseSyncIds.remove(syncId)); | ||
| 249 | } | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | public void sendCorrectMapping(Socket client, Entry<?> entry, boolean refreshClassTree) { | ||
| 254 | EntryMapping oldMapping = mappings.getDeobfMapping(entry); | ||
| 255 | String oldName = oldMapping == null ? null : oldMapping.getTargetName(); | ||
| 256 | if (oldName == null) { | ||
| 257 | sendPacket(client, new RemoveMappingS2CPacket(DUMMY_SYNC_ID, entry)); | ||
| 258 | } else { | ||
| 259 | sendPacket(client, new RenameS2CPacket(0, entry, oldName, refreshClassTree)); | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | protected abstract void runOnThread(Runnable task); | ||
| 264 | |||
| 265 | public void log(String message) { | ||
| 266 | System.out.println(message); | ||
| 267 | } | ||
| 268 | |||
| 269 | protected boolean isRunning() { | ||
| 270 | return !socket.isClosed(); | ||
| 271 | } | ||
| 272 | |||
| 273 | public byte[] getJarChecksum() { | ||
| 274 | return jarChecksum; | ||
| 275 | } | ||
| 276 | |||
| 277 | public char[] getPassword() { | ||
| 278 | return password; | ||
| 279 | } | ||
| 280 | |||
| 281 | public EntryRemapper getMappings() { | ||
| 282 | return mappings; | ||
| 283 | } | ||
| 284 | |||
| 285 | public void sendMessage(Message message) { | ||
| 286 | log(String.format("[MSG] %s", message.translate())); | ||
| 287 | sendToAll(new MessageS2CPacket(message)); | ||
| 288 | } | ||
| 289 | |||
| 290 | } | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java new file mode 100644 index 0000000..21c6825 --- /dev/null +++ b/enigma-server/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/enigma-server/src/main/java/cuchaz/enigma/network/Message.java b/enigma-server/src/main/java/cuchaz/enigma/network/Message.java new file mode 100644 index 0000000..c157838 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/Message.java | |||
| @@ -0,0 +1,393 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import java.io.DataInput; | ||
| 4 | import java.io.DataOutput; | ||
| 5 | import java.io.IOException; | ||
| 6 | import java.util.Objects; | ||
| 7 | |||
| 8 | import cuchaz.enigma.network.packet.PacketHelper; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 10 | import cuchaz.enigma.utils.I18n; | ||
| 11 | |||
| 12 | public abstract class Message { | ||
| 13 | |||
| 14 | public final String user; | ||
| 15 | |||
| 16 | public static Chat chat(String user, String message) { | ||
| 17 | return new Chat(user, message); | ||
| 18 | } | ||
| 19 | |||
| 20 | public static Connect connect(String user) { | ||
| 21 | return new Connect(user); | ||
| 22 | } | ||
| 23 | |||
| 24 | public static Disconnect disconnect(String user) { | ||
| 25 | return new Disconnect(user); | ||
| 26 | } | ||
| 27 | |||
| 28 | public static EditDocs editDocs(String user, Entry<?> entry) { | ||
| 29 | return new EditDocs(user, entry); | ||
| 30 | } | ||
| 31 | |||
| 32 | public static MarkDeobf markDeobf(String user, Entry<?> entry) { | ||
| 33 | return new MarkDeobf(user, entry); | ||
| 34 | } | ||
| 35 | |||
| 36 | public static RemoveMapping removeMapping(String user, Entry<?> entry) { | ||
| 37 | return new RemoveMapping(user, entry); | ||
| 38 | } | ||
| 39 | |||
| 40 | public static Rename rename(String user, Entry<?> entry, String newName) { | ||
| 41 | return new Rename(user, entry, newName); | ||
| 42 | } | ||
| 43 | |||
| 44 | public abstract String translate(); | ||
| 45 | |||
| 46 | public abstract Type getType(); | ||
| 47 | |||
| 48 | public static Message read(DataInput input) throws IOException { | ||
| 49 | byte typeId = input.readByte(); | ||
| 50 | if (typeId < 0 || typeId >= Type.values().length) { | ||
| 51 | throw new IOException(String.format("Invalid message type ID %d", typeId)); | ||
| 52 | } | ||
| 53 | Type type = Type.values()[typeId]; | ||
| 54 | String user = input.readUTF(); | ||
| 55 | switch (type) { | ||
| 56 | case CHAT: | ||
| 57 | String message = input.readUTF(); | ||
| 58 | return chat(user, message); | ||
| 59 | case CONNECT: | ||
| 60 | return connect(user); | ||
| 61 | case DISCONNECT: | ||
| 62 | return disconnect(user); | ||
| 63 | case EDIT_DOCS: | ||
| 64 | Entry<?> entry = PacketHelper.readEntry(input); | ||
| 65 | return editDocs(user, entry); | ||
| 66 | case MARK_DEOBF: | ||
| 67 | entry = PacketHelper.readEntry(input); | ||
| 68 | return markDeobf(user, entry); | ||
| 69 | case REMOVE_MAPPING: | ||
| 70 | entry = PacketHelper.readEntry(input); | ||
| 71 | return removeMapping(user, entry); | ||
| 72 | case RENAME: | ||
| 73 | entry = PacketHelper.readEntry(input); | ||
| 74 | String newName = input.readUTF(); | ||
| 75 | return rename(user, entry, newName); | ||
| 76 | default: | ||
| 77 | throw new IllegalStateException("unreachable"); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | public void write(DataOutput output) throws IOException { | ||
| 82 | output.writeByte(getType().ordinal()); | ||
| 83 | PacketHelper.writeString(output, user); | ||
| 84 | } | ||
| 85 | |||
| 86 | private Message(String user) { | ||
| 87 | this.user = user; | ||
| 88 | } | ||
| 89 | |||
| 90 | @Override | ||
| 91 | public boolean equals(Object o) { | ||
| 92 | if (this == o) return true; | ||
| 93 | if (o == null || getClass() != o.getClass()) return false; | ||
| 94 | Message message = (Message) o; | ||
| 95 | return Objects.equals(user, message.user); | ||
| 96 | } | ||
| 97 | |||
| 98 | @Override | ||
| 99 | public int hashCode() { | ||
| 100 | return Objects.hash(user); | ||
| 101 | } | ||
| 102 | |||
| 103 | public enum Type { | ||
| 104 | CHAT, | ||
| 105 | CONNECT, | ||
| 106 | DISCONNECT, | ||
| 107 | EDIT_DOCS, | ||
| 108 | MARK_DEOBF, | ||
| 109 | REMOVE_MAPPING, | ||
| 110 | RENAME, | ||
| 111 | } | ||
| 112 | |||
| 113 | public static final class Chat extends Message { | ||
| 114 | |||
| 115 | public final String message; | ||
| 116 | |||
| 117 | private Chat(String user, String message) { | ||
| 118 | super(user); | ||
| 119 | this.message = message; | ||
| 120 | } | ||
| 121 | |||
| 122 | @Override | ||
| 123 | public void write(DataOutput output) throws IOException { | ||
| 124 | super.write(output); | ||
| 125 | PacketHelper.writeString(output, message); | ||
| 126 | } | ||
| 127 | |||
| 128 | @Override | ||
| 129 | public String translate() { | ||
| 130 | return String.format(I18n.translate("message.chat.text"), user, message); | ||
| 131 | } | ||
| 132 | |||
| 133 | @Override | ||
| 134 | public Type getType() { | ||
| 135 | return Type.CHAT; | ||
| 136 | } | ||
| 137 | |||
| 138 | @Override | ||
| 139 | public boolean equals(Object o) { | ||
| 140 | if (this == o) return true; | ||
| 141 | if (o == null || getClass() != o.getClass()) return false; | ||
| 142 | if (!super.equals(o)) return false; | ||
| 143 | Chat chat = (Chat) o; | ||
| 144 | return Objects.equals(message, chat.message); | ||
| 145 | } | ||
| 146 | |||
| 147 | @Override | ||
| 148 | public int hashCode() { | ||
| 149 | return Objects.hash(super.hashCode(), message); | ||
| 150 | } | ||
| 151 | |||
| 152 | @Override | ||
| 153 | public String toString() { | ||
| 154 | return String.format("Message.Chat { user: '%s', message: '%s' }", user, message); | ||
| 155 | } | ||
| 156 | |||
| 157 | } | ||
| 158 | |||
| 159 | public static final class Connect extends Message { | ||
| 160 | |||
| 161 | private Connect(String user) { | ||
| 162 | super(user); | ||
| 163 | } | ||
| 164 | |||
| 165 | @Override | ||
| 166 | public String translate() { | ||
| 167 | return String.format(I18n.translate("message.connect.text"), user); | ||
| 168 | } | ||
| 169 | |||
| 170 | @Override | ||
| 171 | public Type getType() { | ||
| 172 | return Type.CONNECT; | ||
| 173 | } | ||
| 174 | |||
| 175 | @Override | ||
| 176 | public String toString() { | ||
| 177 | return String.format("Message.Connect { user: '%s' }", user); | ||
| 178 | } | ||
| 179 | |||
| 180 | } | ||
| 181 | |||
| 182 | public static final class Disconnect extends Message { | ||
| 183 | |||
| 184 | private Disconnect(String user) { | ||
| 185 | super(user); | ||
| 186 | } | ||
| 187 | |||
| 188 | @Override | ||
| 189 | public String translate() { | ||
| 190 | return String.format(I18n.translate("message.disconnect.text"), user); | ||
| 191 | } | ||
| 192 | |||
| 193 | @Override | ||
| 194 | public Type getType() { | ||
| 195 | return Type.DISCONNECT; | ||
| 196 | } | ||
| 197 | |||
| 198 | @Override | ||
| 199 | public String toString() { | ||
| 200 | return String.format("Message.Disconnect { user: '%s' }", user); | ||
| 201 | } | ||
| 202 | |||
| 203 | } | ||
| 204 | |||
| 205 | public static final class EditDocs extends Message { | ||
| 206 | |||
| 207 | public final Entry<?> entry; | ||
| 208 | |||
| 209 | private EditDocs(String user, Entry<?> entry) { | ||
| 210 | super(user); | ||
| 211 | this.entry = entry; | ||
| 212 | } | ||
| 213 | |||
| 214 | @Override | ||
| 215 | public void write(DataOutput output) throws IOException { | ||
| 216 | super.write(output); | ||
| 217 | PacketHelper.writeEntry(output, entry); | ||
| 218 | } | ||
| 219 | |||
| 220 | @Override | ||
| 221 | public String translate() { | ||
| 222 | return String.format(I18n.translate("message.edit_docs.text"), user, entry); | ||
| 223 | } | ||
| 224 | |||
| 225 | @Override | ||
| 226 | public Type getType() { | ||
| 227 | return Type.EDIT_DOCS; | ||
| 228 | } | ||
| 229 | |||
| 230 | @Override | ||
| 231 | public boolean equals(Object o) { | ||
| 232 | if (this == o) return true; | ||
| 233 | if (o == null || getClass() != o.getClass()) return false; | ||
| 234 | if (!super.equals(o)) return false; | ||
| 235 | EditDocs editDocs = (EditDocs) o; | ||
| 236 | return Objects.equals(entry, editDocs.entry); | ||
| 237 | } | ||
| 238 | |||
| 239 | @Override | ||
| 240 | public int hashCode() { | ||
| 241 | return Objects.hash(super.hashCode(), entry); | ||
| 242 | } | ||
| 243 | |||
| 244 | @Override | ||
| 245 | public String toString() { | ||
| 246 | return String.format("Message.EditDocs { user: '%s', entry: %s }", user, entry); | ||
| 247 | } | ||
| 248 | |||
| 249 | } | ||
| 250 | |||
| 251 | public static final class MarkDeobf extends Message { | ||
| 252 | |||
| 253 | public final Entry<?> entry; | ||
| 254 | |||
| 255 | private MarkDeobf(String user, Entry<?> entry) { | ||
| 256 | super(user); | ||
| 257 | this.entry = entry; | ||
| 258 | } | ||
| 259 | |||
| 260 | @Override | ||
| 261 | public void write(DataOutput output) throws IOException { | ||
| 262 | super.write(output); | ||
| 263 | PacketHelper.writeEntry(output, entry); | ||
| 264 | } | ||
| 265 | |||
| 266 | @Override | ||
| 267 | public String translate() { | ||
| 268 | return String.format(I18n.translate("message.mark_deobf.text"), user, entry); | ||
| 269 | } | ||
| 270 | |||
| 271 | @Override | ||
| 272 | public Type getType() { | ||
| 273 | return Type.MARK_DEOBF; | ||
| 274 | } | ||
| 275 | |||
| 276 | @Override | ||
| 277 | public boolean equals(Object o) { | ||
| 278 | if (this == o) return true; | ||
| 279 | if (o == null || getClass() != o.getClass()) return false; | ||
| 280 | if (!super.equals(o)) return false; | ||
| 281 | MarkDeobf markDeobf = (MarkDeobf) o; | ||
| 282 | return Objects.equals(entry, markDeobf.entry); | ||
| 283 | } | ||
| 284 | |||
| 285 | @Override | ||
| 286 | public int hashCode() { | ||
| 287 | return Objects.hash(super.hashCode(), entry); | ||
| 288 | } | ||
| 289 | |||
| 290 | @Override | ||
| 291 | public String toString() { | ||
| 292 | return String.format("Message.MarkDeobf { user: '%s', entry: %s }", user, entry); | ||
| 293 | } | ||
| 294 | |||
| 295 | } | ||
| 296 | |||
| 297 | public static final class RemoveMapping extends Message { | ||
| 298 | |||
| 299 | public final Entry<?> entry; | ||
| 300 | |||
| 301 | private RemoveMapping(String user, Entry<?> entry) { | ||
| 302 | super(user); | ||
| 303 | this.entry = entry; | ||
| 304 | } | ||
| 305 | |||
| 306 | @Override | ||
| 307 | public void write(DataOutput output) throws IOException { | ||
| 308 | super.write(output); | ||
| 309 | PacketHelper.writeEntry(output, entry); | ||
| 310 | } | ||
| 311 | |||
| 312 | @Override | ||
| 313 | public String translate() { | ||
| 314 | return String.format(I18n.translate("message.remove_mapping.text"), user, entry); | ||
| 315 | } | ||
| 316 | |||
| 317 | @Override | ||
| 318 | public Type getType() { | ||
| 319 | return Type.REMOVE_MAPPING; | ||
| 320 | } | ||
| 321 | |||
| 322 | @Override | ||
| 323 | public boolean equals(Object o) { | ||
| 324 | if (this == o) return true; | ||
| 325 | if (o == null || getClass() != o.getClass()) return false; | ||
| 326 | if (!super.equals(o)) return false; | ||
| 327 | RemoveMapping that = (RemoveMapping) o; | ||
| 328 | return Objects.equals(entry, that.entry); | ||
| 329 | } | ||
| 330 | |||
| 331 | @Override | ||
| 332 | public int hashCode() { | ||
| 333 | return Objects.hash(super.hashCode(), entry); | ||
| 334 | } | ||
| 335 | |||
| 336 | @Override | ||
| 337 | public String toString() { | ||
| 338 | return String.format("Message.RemoveMapping { user: '%s', entry: %s }", user, entry); | ||
| 339 | } | ||
| 340 | |||
| 341 | } | ||
| 342 | |||
| 343 | public static final class Rename extends Message { | ||
| 344 | |||
| 345 | public final Entry<?> entry; | ||
| 346 | public final String newName; | ||
| 347 | |||
| 348 | private Rename(String user, Entry<?> entry, String newName) { | ||
| 349 | super(user); | ||
| 350 | this.entry = entry; | ||
| 351 | this.newName = newName; | ||
| 352 | } | ||
| 353 | |||
| 354 | @Override | ||
| 355 | public void write(DataOutput output) throws IOException { | ||
| 356 | super.write(output); | ||
| 357 | PacketHelper.writeEntry(output, entry); | ||
| 358 | PacketHelper.writeString(output, newName); | ||
| 359 | } | ||
| 360 | |||
| 361 | @Override | ||
| 362 | public String translate() { | ||
| 363 | return String.format(I18n.translate("message.rename.text"), user, entry, newName); | ||
| 364 | } | ||
| 365 | |||
| 366 | @Override | ||
| 367 | public Type getType() { | ||
| 368 | return Type.RENAME; | ||
| 369 | } | ||
| 370 | |||
| 371 | @Override | ||
| 372 | public boolean equals(Object o) { | ||
| 373 | if (this == o) return true; | ||
| 374 | if (o == null || getClass() != o.getClass()) return false; | ||
| 375 | if (!super.equals(o)) return false; | ||
| 376 | Rename rename = (Rename) o; | ||
| 377 | return Objects.equals(entry, rename.entry) && | ||
| 378 | Objects.equals(newName, rename.newName); | ||
| 379 | } | ||
| 380 | |||
| 381 | @Override | ||
| 382 | public int hashCode() { | ||
| 383 | return Objects.hash(super.hashCode(), entry, newName); | ||
| 384 | } | ||
| 385 | |||
| 386 | @Override | ||
| 387 | public String toString() { | ||
| 388 | return String.format("Message.Rename { user: '%s', entry: %s, newName: '%s' }", user, entry, newName); | ||
| 389 | } | ||
| 390 | |||
| 391 | } | ||
| 392 | |||
| 393 | } | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java b/enigma-server/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java new file mode 100644 index 0000000..8618553 --- /dev/null +++ b/enigma-server/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/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java new file mode 100644 index 0000000..1b52cf1 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java | |||
| @@ -0,0 +1,59 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 4 | import cuchaz.enigma.network.EnigmaServer; | ||
| 5 | import cuchaz.enigma.network.Message; | ||
| 6 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 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/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java new file mode 100644 index 0000000..12a3025 --- /dev/null +++ b/enigma-server/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.network.ClientPacketHandler; | ||
| 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<ClientPacketHandler> { | ||
| 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(ClientPacketHandler controller) { | ||
| 41 | controller.changeDocs(new EntryReference<>(entry, entry.getName()), newDocs, false); | ||
| 42 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | ||
| 43 | } | ||
| 44 | } | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java new file mode 100644 index 0000000..78ef964 --- /dev/null +++ b/enigma-server/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/enigma-server/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java new file mode 100644 index 0000000..9a112a8 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.ClientPacketHandler; | ||
| 4 | |||
| 5 | import java.io.DataInput; | ||
| 6 | import java.io.DataOutput; | ||
| 7 | import java.io.IOException; | ||
| 8 | |||
| 9 | public class KickS2CPacket implements Packet<ClientPacketHandler> { | ||
| 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(ClientPacketHandler controller) { | ||
| 31 | controller.disconnectIfConnected(reason); | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java new file mode 100644 index 0000000..da0f44a --- /dev/null +++ b/enigma-server/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.network.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/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java new file mode 100644 index 0000000..a41c620 --- /dev/null +++ b/enigma-server/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.network.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/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java new file mode 100644 index 0000000..7504430 --- /dev/null +++ b/enigma-server/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.network.ClientPacketHandler; | ||
| 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<ClientPacketHandler> { | ||
| 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(ClientPacketHandler controller) { | ||
| 37 | controller.markAsDeobfuscated(new EntryReference<>(entry, entry.getName()), false); | ||
| 38 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java new file mode 100644 index 0000000..3bc09e7 --- /dev/null +++ b/enigma-server/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.network.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/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java new file mode 100644 index 0000000..2b07968 --- /dev/null +++ b/enigma-server/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.network.ClientPacketHandler; | ||
| 8 | import cuchaz.enigma.network.Message; | ||
| 9 | |||
| 10 | public class MessageS2CPacket implements Packet<ClientPacketHandler> { | ||
| 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(ClientPacketHandler handler) { | ||
| 33 | handler.addMessage(message); | ||
| 34 | } | ||
| 35 | |||
| 36 | } | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/Packet.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/Packet.java new file mode 100644 index 0000000..2f16dfb --- /dev/null +++ b/enigma-server/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/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java new file mode 100644 index 0000000..464606e --- /dev/null +++ b/enigma-server/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/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java new file mode 100644 index 0000000..3b8af81 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.ClientPacketHandler; | ||
| 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<ClientPacketHandler>>, Integer> s2cPacketIds = new HashMap<>(); | ||
| 15 | private static final Map<Integer, Supplier<? extends Packet<ClientPacketHandler>>> 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<ClientPacketHandler>> 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<ClientPacketHandler> packet) { | ||
| 56 | return s2cPacketIds.get(packet.getClass()); | ||
| 57 | } | ||
| 58 | |||
| 59 | public static Packet<ClientPacketHandler> createS2CPacket(int id) { | ||
| 60 | Supplier<? extends Packet<ClientPacketHandler>> creator = s2cPacketCreators.get(id); | ||
| 61 | return creator == null ? null : creator.get(); | ||
| 62 | } | ||
| 63 | |||
| 64 | } | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java new file mode 100644 index 0000000..3f85228 --- /dev/null +++ b/enigma-server/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.translation.mapping.IllegalNameException; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | import cuchaz.enigma.network.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/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java new file mode 100644 index 0000000..70d803c --- /dev/null +++ b/enigma-server/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.network.ClientPacketHandler; | ||
| 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<ClientPacketHandler> { | ||
| 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(ClientPacketHandler controller) { | ||
| 37 | controller.removeMapping(new EntryReference<>(entry, entry.getName()), false); | ||
| 38 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java new file mode 100644 index 0000000..e3e7e37 --- /dev/null +++ b/enigma-server/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.translation.mapping.IllegalNameException; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 7 | import cuchaz.enigma.network.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/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java new file mode 100644 index 0000000..787e89e --- /dev/null +++ b/enigma-server/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.network.ClientPacketHandler; | ||
| 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<ClientPacketHandler> { | ||
| 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(ClientPacketHandler controller) { | ||
| 45 | controller.rename(new EntryReference<>(entry, entry.getName()), newName, refreshClassTree, false); | ||
| 46 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | ||
| 47 | } | ||
| 48 | } | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java new file mode 100644 index 0000000..76ecbc7 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java | |||
| @@ -0,0 +1,88 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 4 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 5 | import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; | ||
| 6 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 7 | import cuchaz.enigma.network.ClientPacketHandler; | ||
| 8 | import cuchaz.enigma.network.EnigmaServer; | ||
| 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<ClientPacketHandler> { | ||
| 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(ClientPacketHandler controller) { | ||
| 85 | controller.openMappings(mappings); | ||
| 86 | controller.sendPacket(new ConfirmChangeC2SPacket(EnigmaServer.DUMMY_SYNC_ID)); | ||
| 87 | } | ||
| 88 | } | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java new file mode 100644 index 0000000..b4a277a --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.ClientPacketHandler; | ||
| 4 | |||
| 5 | import java.io.DataInput; | ||
| 6 | import java.io.DataOutput; | ||
| 7 | import java.io.IOException; | ||
| 8 | import java.util.ArrayList; | ||
| 9 | import java.util.List; | ||
| 10 | |||
| 11 | public class UserListS2CPacket implements Packet<ClientPacketHandler> { | ||
| 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(ClientPacketHandler handler) { | ||
| 41 | handler.updateUserList(users); | ||
| 42 | } | ||
| 43 | |||
| 44 | } | ||