From 87fdca03ac8835f7c32b8a35610e920c2f97ac6d Mon Sep 17 00:00:00 2001 From: sirjonasxx <36828922+sirjonasxx@users.noreply.github.com> Date: Sat, 14 Aug 2021 15:26:29 +0200 Subject: [PATCH] packetlogger hexadecimal packet options --- G-Earth/pom.xml | 5 + .../uilogger/UiLoggerController.java | 85 +++++++----- .../uilogger/hexdumper/Hexdump.java | 129 ++++++++++++++++++ .../uilogger/UiLogger.fxml | 26 +++- .../internal_extensions/uilogger/logger.css | 10 ++ 5 files changed, 217 insertions(+), 38 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/services/internal_extensions/uilogger/hexdumper/Hexdump.java diff --git a/G-Earth/pom.xml b/G-Earth/pom.xml index ab0fa1d..98d01d0 100644 --- a/G-Earth/pom.xml +++ b/G-Earth/pom.xml @@ -204,6 +204,11 @@ commons-io 2.10.0 + + at.favre.lib + bytes + 1.5.0 + diff --git a/G-Earth/src/main/java/gearth/services/internal_extensions/uilogger/UiLoggerController.java b/G-Earth/src/main/java/gearth/services/internal_extensions/uilogger/UiLoggerController.java index 73dba9f..bdef6af 100644 --- a/G-Earth/src/main/java/gearth/services/internal_extensions/uilogger/UiLoggerController.java +++ b/G-Earth/src/main/java/gearth/services/internal_extensions/uilogger/UiLoggerController.java @@ -1,6 +1,8 @@ package gearth.services.internal_extensions.uilogger; + import at.favre.lib.bytes.Bytes; import gearth.misc.Cacher; + import gearth.services.internal_extensions.uilogger.hexdumper.Hexdump; import gearth.services.packet_info.PacketInfo; import gearth.services.packet_info.PacketInfoManager; import gearth.protocol.HMessage; @@ -16,7 +18,8 @@ import javafx.scene.layout.FlowPane; import javafx.stage.Stage; import org.fxmisc.flowless.VirtualizedScrollPane; import org.fxmisc.richtext.StyleClassedTextArea; -import org.fxmisc.richtext.model.StyleSpansBuilder; + import org.fxmisc.richtext.StyledTextArea; + import org.fxmisc.richtext.model.StyleSpansBuilder; import java.io.BufferedWriter; import java.io.File; @@ -43,6 +46,7 @@ public class UiLoggerController implements Initializable { public CheckMenuItem chkSkipBigPackets; public CheckMenuItem chkMessageName; public CheckMenuItem chkMessageHash; + public CheckMenuItem chkMessageId; public Label lblPacketInfo; public CheckMenuItem chkUseNewStructures; public CheckMenuItem chkAlwaysOnTop; @@ -66,6 +70,11 @@ public class UiLoggerController implements Initializable { public Label lblFiltered; public CheckMenuItem chkTimestamp; + public RadioMenuItem chkReprLegacy; + public RadioMenuItem chkReprHex; + public RadioMenuItem chkReprRawHex; + public RadioMenuItem chkReprNone; + private Map> filterTimestamps = new HashMap<>(); private StyleClassedTextArea area; @@ -123,10 +132,10 @@ public class UiLoggerController implements Initializable { public void initialize(URL arg0, ResourceBundle arg1) { allMenuItems.addAll(Arrays.asList( chkViewIncoming, chkViewOutgoing, chkDisplayStructure, chkAutoscroll, - chkSkipBigPackets, chkMessageName, chkMessageHash, chkUseNewStructures, + chkSkipBigPackets, chkMessageName, chkMessageHash, chkMessageId, chkUseNewStructures, chkOpenOnConnect, chkResetOnConnect, chkHideOnDisconnect, chkResetOnDisconnect, chkAntiSpam_none, chkAntiSpam_low, chkAntiSpam_medium, chkAntiSpam_high, chkAntiSpam_ultra, - chkTimestamp + chkTimestamp, chkReprHex, chkReprLegacy, chkReprRawHex, chkReprNone )); loadAllMenuItems(); @@ -218,6 +227,9 @@ public class UiLoggerController implements Initializable { boolean packetInfoAvailable = uiLogger.getPacketInfoManager().getPacketInfoList().size() > 0; + + boolean addedSomeMessageInfo = false; + if ((chkMessageName.isSelected() || chkMessageHash.isSelected()) && packetInfoAvailable) { List messages = uiLogger.getPacketInfoManager().getAllPacketInfoFromHeaderId( (isIncoming ? HMessage.Direction.TOCLIENT : HMessage.Direction.TOSERVER), @@ -228,60 +240,61 @@ public class UiLoggerController implements Initializable { List hashes = messages.stream().map(PacketInfo::getHash) .filter(Objects::nonNull).distinct().collect(Collectors.toList()); - boolean addedSomething = false; if (chkMessageName.isSelected() && names.size() > 0) { for (String name : names) {elements.add(new Element("["+name+"]", "messageinfo")); } - addedSomething = true; + addedSomeMessageInfo = true; } if (chkMessageHash.isSelected() && hashes.size() > 0) { for (String hash : hashes) {elements.add(new Element("["+hash+"]", "messageinfo")); } - addedSomething = true; + addedSomeMessageInfo = true; } + } - if (addedSomething) { - elements.add(new Element("\n", "")); - } + if (chkMessageId.isSelected()) { + elements.add(new Element(String.format("[%d]", packet.headerId()), "messageinfo")); + addedSomeMessageInfo = true; + } + if (addedSomeMessageInfo) { + elements.add(new Element("\n", "")); } if (isBlocked) elements.add(new Element("[Blocked]\n", "blocked")); else if (isReplaced) elements.add(new Element("[Replaced]\n", "replaced")); - if (isIncoming) { - // handle skipped eventually - elements.add(new Element("Incoming[", "incoming")); - elements.add(new Element(String.valueOf(packet.headerId()), "")); - elements.add(new Element("]", "incoming")); + if (!chkReprNone.isSelected()) { + boolean isSkipped = chkSkipBigPackets.isSelected() && (packet.length() > 4000 || (packet.length() > 1000 && chkReprHex.isSelected())); + String packetRepresentation = chkReprHex.isSelected() ? + Hexdump.hexdump(packet.toBytes()) : + (chkReprRawHex.isSelected() ? Bytes.wrap(packet.toBytes()).encodeHex() : packet.toString()); - elements.add(new Element(" <- ", "")); - if (chkSkipBigPackets.isSelected() && packet.length() > 4000) { + String type = isIncoming ? "Incoming" : "Outgoing"; + + if (!chkReprHex.isSelected()) { + elements.add(new Element(String.format("%s[", type), type.toLowerCase())); + elements.add(new Element(String.valueOf(packet.headerId()), "")); + elements.add(new Element("]", type.toLowerCase())); + + elements.add(new Element(" -> ", "")); + } + + if (isSkipped) { elements.add(new Element("", "skipped")); - } - else { - elements.add(new Element(packet.toString(), "incoming")); - } - } else { - elements.add(new Element("Outgoing[", "outgoing")); - elements.add(new Element(String.valueOf(packet.headerId()), "")); - elements.add(new Element("]", "outgoing")); - - elements.add(new Element(" -> ", "")); - - if (chkSkipBigPackets.isSelected() && packet.length() > 4000) { - elements.add(new Element("", "skipped")); - } - else { - elements.add(new Element(packet.toString(), "outgoing")); - } + } else + elements.add(new Element(packetRepresentation, String.format(chkReprHex.isSelected() ? "%sHex": "%s", type.toLowerCase()))); + elements.add(new Element("\n", "")); } + if (packet.length() <= 2000) { try { String expr = packet.toExpression(isIncoming ? HMessage.Direction.TOCLIENT : HMessage.Direction.TOSERVER, uiLogger.getPacketInfoManager(), chkUseNewStructures.isSelected()); String cleaned = cleanTextContent(expr); if (cleaned.equals(expr)) { - if (!expr.equals("") && chkDisplayStructure.isSelected()) - elements.add(new Element("\n" + cleanTextContent(expr), "structure")); + if (!expr.equals("") && chkDisplayStructure.isSelected()) { + elements.add(new Element(cleanTextContent(expr), "structure")); + elements.add(new Element("\n", "")); + } } } catch (Exception e) { @@ -292,7 +305,7 @@ public class UiLoggerController implements Initializable { } - elements.add(new Element("\n--------------------\n", "")); + elements.add(new Element("--------------------\n", "")); synchronized (appendLater) { if (initialized) { diff --git a/G-Earth/src/main/java/gearth/services/internal_extensions/uilogger/hexdumper/Hexdump.java b/G-Earth/src/main/java/gearth/services/internal_extensions/uilogger/hexdumper/Hexdump.java new file mode 100644 index 0000000..5ceb204 --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/internal_extensions/uilogger/hexdumper/Hexdump.java @@ -0,0 +1,129 @@ +package gearth.services.internal_extensions.uilogger.hexdumper; + +// https://github.com/zuckel/hexdump + +/** + * Utility class for generating hexdumps from byte arrays. Mostly for debugging purposes. + */ +public final class Hexdump { + + private static final char[] HEX = + new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + public static final char NON_PRINTABLE = '\u25a1'; // WHITE SQUARE □ + + private Hexdump() { + } + + /** + *

+ * Create human-readable hexdump for a byte array. + *

+ *

+ * This method is not thread-safe in the sense that bytes will be read more than once, thus possibly producing inconsistent + * output if the byte array is mutated concurrently. + *

+ * + * @param bytes array to be rendered + * @return human-readable formatted hexdump as a String + */ + public static String hexdump(byte[] bytes) { + if (bytes == null) { + return "null"; + } + if (bytes.length == 0) { + return "empty"; + } + + StringBuilder out = new StringBuilder(); + + for (int offset = 0; offset < bytes.length; offset += 16) { + appendLine(bytes, offset, out); + } + appendOffset(bytes.length, out); +// out.append('\n'); + + return out.toString(); + } + + // 00000000 2f 2e 63 6c 61 73 73 70 61 74 68 0a 2f 2e 70 72 |/.classpath¶/.pr| + // offset first block second block | displayChars| + private static void appendLine(byte[] bytes, int firstBlockStart, StringBuilder out) { + int firstBlockEnd = Math.min(bytes.length, firstBlockStart + 8); + int secondBlockEnd = Math.min(bytes.length, firstBlockStart + 16); + + appendOffset(firstBlockStart, out); + out.append(' '); + out.append(' '); + + appendHexBytes(bytes, firstBlockStart, firstBlockEnd, out); + out.append(' '); + + appendHexBytes(bytes, firstBlockStart + 8, secondBlockEnd, out); + padMissingBytes(firstBlockStart, secondBlockEnd, out); + out.append(' '); + + out.append('|'); + appendDisplayChars(bytes, firstBlockStart, secondBlockEnd, out); + out.append('|'); + out.append('\n'); + } + + // visible for testing + static void appendOffset(int offset, StringBuilder out) { + appendHexChars((byte) ((offset & 0xFF000000) >> 24), out); + appendHexChars((byte) ((offset & 0x00FF0000) >> 16), out); + appendHexChars((byte) ((offset & 0x0000FF00) >> 8), out); + appendHexChars((byte) ((offset & 0x000000FF) >> 0), out); + } + + private static void appendHexBytes(byte[] bytes, int offset, int limit, StringBuilder out) { + for (int i = offset; i < limit; i++) { + appendHexChars(bytes[i], out); + out.append(' '); + } + } + + private static void appendHexChars(byte b, StringBuilder out) { + out.append(HEX[(b >> 4) & 0x0F]); // 4 high bits + out.append(HEX[b & 0x0F]); // 4 low bits + } + + private static void padMissingBytes(int firstByte, int lastByte, StringBuilder out) { + int charsPerByte = 3; + int maxBytesPerLine = 16; + int bytesWritten = lastByte - firstByte; + + int charsMissing = charsPerByte * (maxBytesPerLine - bytesWritten); + for (int i = 0; i < charsMissing; i++) { + out.append(' '); + } + } + + private static void appendDisplayChars(byte[] bytes, int offset, int blockEnd, StringBuilder out) { + for (int i = offset; i < blockEnd; i++) { + appendDisplayChar(bytes[i], out); + } + } + + private static void appendDisplayChar(byte b, StringBuilder out) { + switch (b) { + case 0x20: + out.append("\u2423"); // SPACE ␣ + break; + case 0x09: + out.append('\u2192'); // TAB → + break; + case 0x0a: + out.append('\u00b6'); // LF ¶ + break; + case 0x0d: + out.append('\u00a4'); // CR ¤ + break; + default: + out.append((32 <= b && b <= 126) ? (char) b : NON_PRINTABLE); // ' ' to '~', non-printable is WHITE SQUARE □ + } + + } + +} \ No newline at end of file diff --git a/G-Earth/src/main/resources/gearth/services/internal_extensions/uilogger/UiLogger.fxml b/G-Earth/src/main/resources/gearth/services/internal_extensions/uilogger/UiLogger.fxml index 3785c88..bad8b25 100644 --- a/G-Earth/src/main/resources/gearth/services/internal_extensions/uilogger/UiLogger.fxml +++ b/G-Earth/src/main/resources/gearth/services/internal_extensions/uilogger/UiLogger.fxml @@ -47,9 +47,26 @@ + + + + + + + + + + + + + + + + + + + - - @@ -69,6 +86,11 @@ + + + + + diff --git a/G-Earth/src/main/resources/gearth/services/internal_extensions/uilogger/logger.css b/G-Earth/src/main/resources/gearth/services/internal_extensions/uilogger/logger.css index 506c571..f22d8e8 100644 --- a/G-Earth/src/main/resources/gearth/services/internal_extensions/uilogger/logger.css +++ b/G-Earth/src/main/resources/gearth/services/internal_extensions/uilogger/logger.css @@ -19,10 +19,20 @@ -fx-fill: #b22222; } +.incomingHex { + -fx-fill: #f06262; + -fx-font-family: monospace; +} + .outgoing { -fx-fill: #0066cc; } +.outgoingHex { + -fx-fill: #4fb7f7; + -fx-font-family: monospace; +} + .structure, .skipped { -fx-fill: cyan; }