packetlogger hexadecimal packet options

This commit is contained in:
sirjonasxx 2021-08-14 15:26:29 +02:00
parent c968a24e8d
commit 87fdca03ac
5 changed files with 217 additions and 38 deletions

View File

@ -204,6 +204,11 @@
<artifactId>commons-io</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>at.favre.lib</groupId>
<artifactId>bytes</artifactId>
<version>1.5.0</version>
</dependency>

View File

@ -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<Integer, LinkedList<Long>> 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<PacketInfo> messages = uiLogger.getPacketInfoManager().getAllPacketInfoFromHeaderId(
(isIncoming ? HMessage.Direction.TOCLIENT : HMessage.Direction.TOSERVER),
@ -228,60 +240,61 @@ public class UiLoggerController implements Initializable {
List<String> 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("<packet skipped>", "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("<packet skipped>", "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) {

View File

@ -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() {
}
/**
* <p>
* Create human-readable hexdump for a byte array.
* </p>
* <p>
* 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.
* </p>
*
* @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
}
}
}

View File

@ -47,9 +47,26 @@
<items>
<Menu mnemonicParsing="false" text="Display Details">
<items>
<Menu mnemonicParsing="false" text="Byte representation">
<items>
<RadioMenuItem fx:id="chkReprLegacy" mnemonicParsing="false" selected="true" text="Legacy">
<toggleGroup>
<ToggleGroup fx:id="byterepr" />
</toggleGroup>
</RadioMenuItem>
<RadioMenuItem fx:id="chkReprHex" mnemonicParsing="false" text="Hexdump" toggleGroup="$byterepr" />
<RadioMenuItem fx:id="chkReprRawHex" mnemonicParsing="false" text="Raw hex" toggleGroup="$byterepr" />
<RadioMenuItem fx:id="chkReprNone" mnemonicParsing="false" text="None" toggleGroup="$byterepr" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Message">
<items>
<CheckMenuItem fx:id="chkMessageName" mnemonicParsing="false" selected="true" text="Name" />
<CheckMenuItem fx:id="chkMessageHash" mnemonicParsing="false" text="Hash" />
<CheckMenuItem fx:id="chkMessageId" mnemonicParsing="false" text="Id" />
</items>
</Menu>
<CheckMenuItem fx:id="chkDisplayStructure" mnemonicParsing="false" selected="true" text="Structure" />
<CheckMenuItem fx:id="chkMessageName" mnemonicParsing="false" selected="true" text="Message Name" />
<CheckMenuItem fx:id="chkMessageHash" mnemonicParsing="false" text="Message Hash" />
<CheckMenuItem fx:id="chkUseNewStructures" mnemonicParsing="false" selected="true" text="New structures" />
<CheckMenuItem fx:id="chkTimestamp" mnemonicParsing="false" text="Timestamp" />
</items>
@ -69,6 +86,11 @@
</Menu>
<CheckMenuItem fx:id="chkSkipBigPackets" mnemonicParsing="false" selected="true" text="Skip big packets" />
<MenuItem mnemonicParsing="false" onAction="#exportAll" text="Export all" />
<Menu mnemonicParsing="false" text="Unspecified Menu">
<items>
<MenuItem mnemonicParsing="false" text="Action 1" />
</items>
</Menu>
</items>
</Menu>
</MenuBar>

View File

@ -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;
}