From f4def5ef5825ab925768bd86f1839ea23a5f72f3 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Thu, 23 Jan 2020 19:58:44 +0800
Subject: [PATCH] Add tests for Jce

---
 mirai-debug/build.gradle.kts                  |    8 +
 .../src/test/java/jce/jce/HexUtil.java        |   79 ++
 .../test/java/jce/jce/JceDecodeException.java |    7 +
 .../test/java/jce/jce/JceEncodeException.java |    7 +
 .../java/jce/jce/JceInputStream$HeadData.java |   11 +
 .../src/test/java/jce/jce/JceInputStream.java | 1008 +++++++++++++++++
 .../test/java/jce/jce/JceOutputStream.java    |  430 +++++++
 .../src/test/java/jce/jce/JceStruct.java      |   78 ++
 .../src/test/java/jce/jce/JceUtil.java        |  594 ++++++++++
 .../jce/jce/OnIllegalArgumentException.java   |    7 +
 .../src/test/kotlin/jceTest/jceTest.kt        |  293 +++++
 11 files changed, 2522 insertions(+)
 create mode 100644 mirai-debug/src/test/java/jce/jce/HexUtil.java
 create mode 100644 mirai-debug/src/test/java/jce/jce/JceDecodeException.java
 create mode 100644 mirai-debug/src/test/java/jce/jce/JceEncodeException.java
 create mode 100644 mirai-debug/src/test/java/jce/jce/JceInputStream$HeadData.java
 create mode 100644 mirai-debug/src/test/java/jce/jce/JceInputStream.java
 create mode 100644 mirai-debug/src/test/java/jce/jce/JceOutputStream.java
 create mode 100644 mirai-debug/src/test/java/jce/jce/JceStruct.java
 create mode 100644 mirai-debug/src/test/java/jce/jce/JceUtil.java
 create mode 100644 mirai-debug/src/test/java/jce/jce/OnIllegalArgumentException.java
 create mode 100644 mirai-debug/src/test/kotlin/jceTest/jceTest.kt

diff --git a/mirai-debug/build.gradle.kts b/mirai-debug/build.gradle.kts
index bb3c47204..98ae1f8bc 100644
--- a/mirai-debug/build.gradle.kts
+++ b/mirai-debug/build.gradle.kts
@@ -41,8 +41,11 @@ fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
 
 dependencies {
 
+    runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // IDE bug
     runtimeOnly(files("../mirai-core-timpc/build/classes/kotlin/jvm/main")) // IDE bug
     implementation(project(":mirai-core-timpc"))
+    runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main")) // IDE bug
+    implementation(project(":mirai-core-qqandroid"))
     // runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE
 
     implementation("org.bouncycastle:bcprov-jdk15on:1.64")
@@ -70,4 +73,9 @@ dependencies {
     implementation(ktor("client-core", ktorVersion))
     implementation(ktor("network", ktorVersion))
 
+    testImplementation(kotlin("test-annotations-common"))
+    testImplementation(kotlin("test"))
+    testImplementation(kotlin("test-junit"))
+    testImplementation(kotlin("script-runtime"))
+
 }
\ No newline at end of file
diff --git a/mirai-debug/src/test/java/jce/jce/HexUtil.java b/mirai-debug/src/test/java/jce/jce/HexUtil.java
new file mode 100644
index 000000000..8a3e9b783
--- /dev/null
+++ b/mirai-debug/src/test/java/jce/jce/HexUtil.java
@@ -0,0 +1,79 @@
+package jce.jce;
+
+import java.io.UnsupportedEncodingException;
+
+public class HexUtil {
+   private static final char[] digits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+   public static final byte[] emptybytes = new byte[0];
+
+   public static String byte2HexStr(byte var0) {
+      char var1 = digits[var0 & 15];
+      var0 = (byte) (var0 >>> 4);
+      return new String(new char[]{digits[var0 & 15], var1});
+   }
+
+   public static String bytes2HexStr(byte[] var0) {
+      if (var0 != null && var0.length != 0) {
+         char[] var3 = new char[var0.length * 2];
+
+         for (int var1 = 0; var1 < var0.length; ++var1) {
+            byte var2 = var0[var1];
+            var3[var1 * 2 + 1] = digits[var2 & 15];
+            var2 = (byte) (var2 >>> 4);
+            var3[var1 * 2 + 0] = digits[var2 & 15];
+         }
+
+         return new String(var3);
+      } else {
+         return null;
+      }
+   }
+
+   public static byte char2Byte(char var0) {
+      if (var0 >= '0' && var0 <= '9') {
+         return (byte) (var0 - 48);
+      } else if (var0 >= 'a' && var0 <= 'f') {
+         return (byte) (var0 - 97 + 10);
+      } else {
+         return var0 >= 'A' && var0 <= 'F' ? (byte) (var0 - 65 + 10) : 0;
+      }
+   }
+
+   public static byte hexStr2Byte(String var0) {
+      byte var2 = 0;
+      byte var1 = var2;
+      if (var0 != null) {
+         var1 = var2;
+         if (var0.length() == 1) {
+            var1 = char2Byte(var0.charAt(0));
+         }
+      }
+
+      return var1;
+   }
+
+   public static byte[] hexStr2Bytes(String var0) {
+      if (var0 != null && !var0.equals("")) {
+         byte[] var4 = new byte[var0.length() / 2];
+
+         for (int var3 = 0; var3 < var4.length; ++var3) {
+            char var1 = var0.charAt(var3 * 2);
+            char var2 = var0.charAt(var3 * 2 + 1);
+            var4[var3] = (byte) (char2Byte(var1) * 16 + char2Byte(var2));
+         }
+
+         return var4;
+      } else {
+         return emptybytes;
+      }
+   }
+
+   public static void main(String[] var0) {
+      try {
+         byte[] var2 = "Hello WebSocket World?".getBytes("gbk");
+         System.out.println(bytes2HexStr(var2));
+      } catch (UnsupportedEncodingException var1) {
+         var1.printStackTrace();
+      }
+   }
+}
diff --git a/mirai-debug/src/test/java/jce/jce/JceDecodeException.java b/mirai-debug/src/test/java/jce/jce/JceDecodeException.java
new file mode 100644
index 000000000..49c4bd5b7
--- /dev/null
+++ b/mirai-debug/src/test/java/jce/jce/JceDecodeException.java
@@ -0,0 +1,7 @@
+package jce.jce;
+
+public class JceDecodeException extends RuntimeException {
+    public JceDecodeException(String var1) {
+        super(var1);
+    }
+}
diff --git a/mirai-debug/src/test/java/jce/jce/JceEncodeException.java b/mirai-debug/src/test/java/jce/jce/JceEncodeException.java
new file mode 100644
index 000000000..40c7a5e11
--- /dev/null
+++ b/mirai-debug/src/test/java/jce/jce/JceEncodeException.java
@@ -0,0 +1,7 @@
+package jce.jce;
+
+public class JceEncodeException extends RuntimeException {
+    public JceEncodeException(String var1) {
+        super(var1);
+    }
+}
diff --git a/mirai-debug/src/test/java/jce/jce/JceInputStream$HeadData.java b/mirai-debug/src/test/java/jce/jce/JceInputStream$HeadData.java
new file mode 100644
index 000000000..df97f8166
--- /dev/null
+++ b/mirai-debug/src/test/java/jce/jce/JceInputStream$HeadData.java
@@ -0,0 +1,11 @@
+package jce.jce;
+
+public class JceInputStream$HeadData {
+    public int tag;
+    public byte type;
+
+    public void clear() {
+        this.type = 0;
+        this.tag = 0;
+    }
+}
diff --git a/mirai-debug/src/test/java/jce/jce/JceInputStream.java b/mirai-debug/src/test/java/jce/jce/JceInputStream.java
new file mode 100644
index 000000000..bb7495b28
--- /dev/null
+++ b/mirai-debug/src/test/java/jce/jce/JceInputStream.java
@@ -0,0 +1,1008 @@
+package jce.jce;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Array;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public final class JceInputStream {
+    // $FF: renamed from: bs java.nio.ByteBuffer
+    private ByteBuffer buffer;
+    protected String sServerEncoding = "GBK";
+
+    public JceInputStream() {
+    }
+
+    public JceInputStream(ByteBuffer var1) {
+        this.buffer = var1;
+    }
+
+    public JceInputStream(byte[] var1) {
+        this.buffer = ByteBuffer.wrap(var1);
+    }
+
+    public JceInputStream(byte[] var1, int var2) {
+        this.buffer = ByteBuffer.wrap(var1);
+        this.buffer.position(var2);
+    }
+
+    public static void main(String[] var0) {
+    }
+
+    private int peakHead(JceInputStream$HeadData var1) {
+        return readHead(var1, this.buffer.duplicate());
+    }
+
+    private <T> T[] readArrayImpl(T var1, int var2, boolean var3) {
+        Object[] var7;
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var5 = new JceInputStream$HeadData();
+            this.readHead(var5);
+            if (var5.type == 9) {
+                int var4 = this.read(0, 0, true);
+                if (var4 < 0) {
+                    throw new JceDecodeException("size invalid: " + var4);
+                } else {
+                    Object[] var6 = (Object[]) Array.newInstance(var1.getClass(), var4);
+                    var2 = 0;
+
+                    while (true) {
+                        var7 = var6;
+                        if (var2 >= var4) {
+                            return (T[]) var7;
+                        }
+
+                        var6[var2] = this.read(var1, 0, true);
+                        ++var2;
+                    }
+                }
+            }
+            throw new JceDecodeException("type mismatch.");
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            var7 = null;
+            return (T[]) var7;
+        }
+    }
+
+    public static int readHead(JceInputStream$HeadData var0, ByteBuffer var1) {
+        byte var2 = var1.get();
+        var0.type = (byte) (var2 & 15);
+        var0.tag = (var2 & 240) >> 4;
+        if (var0.tag == 15) {
+            var0.tag = var1.get() & 255;
+            return 2;
+        } else {
+            return 1;
+        }
+    }
+
+    private <K, V> Map<K, V> readMap(Map<K, V> var1, Map<K, V> var2, int var3, boolean var4) {
+        Map<K, V> var8;
+        if (var2 != null && !var2.isEmpty()) {
+            Entry<K, V> var9 = var2.entrySet().iterator().next();
+            K var6 = var9.getKey();
+            V var7 = var9.getValue();
+            if (this.skipToTag(var3)) {
+                JceInputStream$HeadData var10 = new JceInputStream$HeadData();
+                this.readHead(var10);
+                if (var10.type == 8) {
+                    int var5 = this.read(0, 0, true);
+                    if (var5 < 0) {
+                        throw new JceDecodeException("size invalid: " + var5);
+                    }
+
+                    var3 = 0;
+
+                    while (true) {
+                        var8 = var1;
+                        if (var3 >= var5) {
+                            return var8;
+                        }
+
+                        var1.put((K) this.read(var6, 0, true), (V) this.read(var7, 1, true));
+                        ++var3;
+                    }
+                }
+                throw new JceDecodeException("type mismatch.");
+            } else {
+                var8 = var1;
+                if (var4) {
+                    throw new JceDecodeException("require field not exist.");
+                }
+            }
+        } else {
+            var8 = new HashMap<>();
+        }
+
+        return var8;
+    }
+
+    private void skip(int var1) {
+        this.buffer.position(this.buffer.position() + var1);
+    }
+
+    private void skipField() {
+        JceInputStream$HeadData var1 = new JceInputStream$HeadData();
+        this.readHead(var1);
+        this.skipField(var1.type);
+    }
+
+    private void skipField(byte var1) {
+        byte var3 = 0;
+        byte var2 = 0;
+        int var5;
+        switch (var1) {
+            case 0:
+                this.skip(1);
+                break;
+            case 1:
+                this.skip(2);
+                return;
+            case 2:
+                this.skip(4);
+                return;
+            case 3:
+                this.skip(8);
+                return;
+            case 4:
+                this.skip(4);
+                return;
+            case 5:
+                this.skip(8);
+                return;
+            case 6:
+                byte var7 = this.buffer.get();
+                var5 = var7;
+                if (var7 < 0) {
+                    var5 = var7 + 256;
+                }
+
+                this.skip(var5);
+                return;
+            case 7:
+                this.skip(this.buffer.getInt());
+                return;
+            case 8:
+                int var8 = this.read(0, 0, true);
+
+                for (var5 = var2; var5 < var8 * 2; ++var5) {
+                    this.skipField();
+                }
+
+                return;
+            case 9:
+                int var6 = this.read(0, 0, true);
+
+                for (var5 = var3; var5 < var6; ++var5) {
+                    this.skipField();
+                }
+
+                return;
+            case 10:
+                this.skipToStructEnd();
+                return;
+            case 11:
+            case 12:
+                break;
+            case 13:
+                JceInputStream$HeadData var4 = new JceInputStream$HeadData();
+                this.readHead(var4);
+                if (var4.type != 0) {
+                    throw new JceDecodeException("skipField with invalid type, type value: " + var1 + ", " + var4.type);
+                }
+
+                this.skip(this.read(0, 0, true));
+                return;
+            default:
+                throw new JceDecodeException("invalid type.");
+        }
+
+    }
+
+    public JceStruct directRead(JceStruct var1, int var2, boolean var3) {
+        JceInputStream$HeadData var4 = null;
+        if (this.skipToTag(var2)) {
+            try {
+                var1 = var1.newInit();
+            } catch (Exception var5) {
+                throw new JceDecodeException(var5.getMessage());
+            }
+
+            var4 = new JceInputStream$HeadData();
+            this.readHead(var4);
+            if (var4.type != 10) {
+                throw new JceDecodeException("type mismatch.");
+            }
+
+            var1.readFrom(this);
+            this.skipToStructEnd();
+        } else {
+            var1 = null;
+            if (var3) {
+                throw new JceDecodeException("require field not exist.");
+            }
+        }
+
+        return var1;
+    }
+
+    public ByteBuffer getBs() {
+        return this.buffer;
+    }
+
+    public byte read(byte var1, int var2, boolean var3) {
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var4 = new JceInputStream$HeadData();
+            this.readHead(var4);
+            switch (var4.type) {
+                case 0:
+                    return this.buffer.get();
+                case 12:
+                    return 0;
+                default:
+                    throw new JceDecodeException("type mismatch.");
+            }
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public double read(double var1, int var3, boolean var4) {
+        if (this.skipToTag(var3)) {
+            JceInputStream$HeadData var5 = new JceInputStream$HeadData();
+            this.readHead(var5);
+            switch (var5.type) {
+                case 4:
+                    return this.buffer.getFloat();
+                case 5:
+                    return this.buffer.getDouble();
+                case 12:
+                    return 0.0D;
+                default:
+                    throw new JceDecodeException("type mismatch.");
+            }
+        } else if (var4) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public float read(float var1, int var2, boolean var3) {
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var4 = new JceInputStream$HeadData();
+            this.readHead(var4);
+            switch (var4.type) {
+                case 4:
+                    return this.buffer.getFloat();
+                case 12:
+                    return 0.0F;
+                default:
+                    throw new JceDecodeException("type mismatch.");
+            }
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public int read(int var1, int var2, boolean var3) {
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var4 = new JceInputStream$HeadData();
+            this.readHead(var4);
+            switch (var4.type) {
+                case 0:
+                    return this.buffer.get();
+                case 1:
+                    return this.buffer.getShort();
+                case 2:
+                    return this.buffer.getInt();
+                case 12:
+                    return 0;
+                default:
+                    throw new JceDecodeException("type mismatch.");
+            }
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public long read(long var1, int var3, boolean var4) {
+        if (this.skipToTag(var3)) {
+            JceInputStream$HeadData var5 = new JceInputStream$HeadData();
+            this.readHead(var5);
+            switch (var5.type) {
+                case 0:
+                    return this.buffer.get();
+                case 1:
+                    return this.buffer.getShort();
+                case 2:
+                    return this.buffer.getInt();
+                case 3:
+                    return this.buffer.getLong();
+                case 12:
+                    return 0L;
+                default:
+                    throw new JceDecodeException("type mismatch.");
+            }
+        } else if (var4) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public JceStruct read(JceStruct var1, int var2, boolean var3) {
+        JceInputStream$HeadData var4 = null;
+        if (this.skipToTag(var2)) {
+            try {
+                var1 = var1.getClass().newInstance();
+            } catch (Exception var5) {
+                throw new JceDecodeException(var5.getMessage());
+            }
+
+            var4 = new JceInputStream$HeadData();
+            this.readHead(var4);
+            if (var4.type != 10) {
+                throw new JceDecodeException("type mismatch.");
+            }
+
+            var1.readFrom(this);
+            this.skipToStructEnd();
+        } else {
+            var1 = null;
+            if (var3) {
+                throw new JceDecodeException("require field not exist.");
+            }
+        }
+
+        return var1;
+    }
+
+    public <T> Object read(T var1, int var2, boolean var3) {
+        if (var1 instanceof Byte) {
+            return this.read((byte) 0, var2, var3);
+        } else if (var1 instanceof Boolean) {
+            return this.read(false, var2, var3);
+        } else if (var1 instanceof Short) {
+            return this.read((short) 0, var2, var3);
+        } else if (var1 instanceof Integer) {
+            return this.read(0, var2, var3);
+        } else if (var1 instanceof Long) {
+            return this.read(0L, var2, var3);
+        } else if (var1 instanceof Float) {
+            return this.read(0.0F, var2, var3);
+        } else if (var1 instanceof Double) {
+            return this.read(0.0D, var2, var3);
+        } else if (var1 instanceof String) {
+            return this.readString(var2, var3);
+        } else if (var1 instanceof Map) {
+            return this.readMap((Map) var1, var2, var3);
+        } else if (var1 instanceof List) {
+            return this.readArray((List) var1, var2, var3);
+        } else if (var1 instanceof JceStruct) {
+            return this.read((JceStruct) var1, var2, var3);
+        } else if (var1.getClass().isArray()) {
+            if (!(var1 instanceof byte[]) && !(var1 instanceof Byte[])) {
+                if (var1 instanceof boolean[]) {
+                    return this.read((boolean[]) null, var2, var3);
+                } else if (var1 instanceof short[]) {
+                    return this.read((short[]) null, var2, var3);
+                } else if (var1 instanceof int[]) {
+                    return this.read((int[]) null, var2, var3);
+                } else if (var1 instanceof long[]) {
+                    return this.read((long[]) null, var2, var3);
+                } else if (var1 instanceof float[]) {
+                    return this.read((float[]) null, var2, var3);
+                } else {
+                    return var1 instanceof double[] ? this.read((double[]) null, var2, var3) : this.readArray((Object[]) var1, var2, var3);
+                }
+            } else {
+                return this.read((byte[]) null, var2, var3);
+            }
+        } else {
+            throw new JceDecodeException("read object error: unsupport type.");
+        }
+    }
+
+    public String read(String var1, int var2, boolean var3) {
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var8 = new JceInputStream$HeadData();
+            this.readHead(var8);
+            String var5;
+            byte[] var9;
+            switch (var8.type) {
+                case 6:
+                    byte var4 = this.buffer.get();
+                    var2 = var4;
+                    if (var4 < 0) {
+                        var2 = var4 + 256;
+                    }
+
+                    var9 = new byte[var2];
+                    this.buffer.get(var9);
+
+                    try {
+                        var5 = new String(var9, this.sServerEncoding);
+                        return var5;
+                    } catch (UnsupportedEncodingException var7) {
+                        return new String(var9);
+                    }
+                case 7:
+                    var2 = this.buffer.getInt();
+                    if (var2 <= 104857600 && var2 >= 0 && var2 <= this.buffer.capacity()) {
+                        var9 = new byte[var2];
+                        this.buffer.get(var9);
+
+                        try {
+                            var5 = new String(var9, this.sServerEncoding);
+                            return var5;
+                        } catch (UnsupportedEncodingException var6) {
+                            return new String(var9);
+                        }
+                    }
+
+                    throw new JceDecodeException("String too long: " + var2);
+                default:
+                    throw new JceDecodeException("type mismatch.");
+            }
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public short read(short var1, int var2, boolean var3) {
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var4 = new JceInputStream$HeadData();
+            this.readHead(var4);
+            switch (var4.type) {
+                case 0:
+                    return this.buffer.get();
+                case 1:
+                    return this.buffer.getShort();
+                case 12:
+                    return 0;
+                default:
+                    throw new JceDecodeException("type mismatch.");
+            }
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public boolean read(boolean var1, int var2, boolean var3) {
+        var1 = this.read((byte) 0, var2, var3) != 0;
+
+        return var1;
+    }
+
+    public byte[] read(byte[] var1, int var2, boolean var3) {
+        var1 = null;
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var6 = new JceInputStream$HeadData();
+            this.readHead(var6);
+            int var4;
+            switch (var6.type) {
+                case 9:
+                    var4 = this.read(0, 0, true);
+                    if (var4 >= 0 && var4 <= this.buffer.capacity()) {
+                        byte[] var7 = new byte[var4];
+                        var2 = 0;
+
+                        while (true) {
+                            var1 = var7;
+                            if (var2 >= var4) {
+                                return var1;
+                            }
+
+                            var7[var2] = this.read(var7[0], 0, true);
+                            ++var2;
+                        }
+                    }
+
+                    throw new JceDecodeException("size invalid: " + var4);
+                case 13:
+                    JceInputStream$HeadData var5 = new JceInputStream$HeadData();
+                    this.readHead(var5);
+                    if (var5.type != 0) {
+                        throw new JceDecodeException("type mismatch, tag: " + var2 + ", type: " + var6.type + ", " + var5.type);
+                    }
+
+                    var4 = this.read(0, 0, true);
+                    if (var4 < 0 || var4 > this.buffer.capacity()) {
+                        throw new JceDecodeException("invalid size, tag: " + var2 + ", type: " + var6.type + ", " + var5.type + ", size: " + var4);
+                    }
+
+                    var1 = new byte[var4];
+                    this.buffer.get(var1);
+                    break;
+                default:
+                    throw new JceDecodeException("type mismatch.");
+            }
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        }
+
+        return var1;
+    }
+
+    public double[] read(double[] var1, int var2, boolean var3) {
+        var1 = null;
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var6 = new JceInputStream$HeadData();
+            this.readHead(var6);
+            if (var6.type == 9) {
+                int var4 = this.read(0, 0, true);
+                if (var4 < 0) {
+                    throw new JceDecodeException("size invalid: " + var4);
+                } else {
+                    double[] var5 = new double[var4];
+                    var2 = 0;
+
+                    while (true) {
+                        var1 = var5;
+                        if (var2 >= var4) {
+                            return var1;
+                        }
+
+                        var5[var2] = this.read(var5[0], 0, true);
+                        ++var2;
+                    }
+                }
+            }
+            throw new JceDecodeException("type mismatch.");
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public float[] read(float[] var1, int var2, boolean var3) {
+        var1 = null;
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var6 = new JceInputStream$HeadData();
+            this.readHead(var6);
+            if (var6.type == 9) {
+                int var4 = this.read(0, 0, true);
+                if (var4 < 0) {
+                    throw new JceDecodeException("size invalid: " + var4);
+                } else {
+                    float[] var5 = new float[var4];
+                    var2 = 0;
+
+                    while (true) {
+                        var1 = var5;
+                        if (var2 >= var4) {
+                            return var1;
+                        }
+
+                        var5[var2] = this.read(var5[0], 0, true);
+                        ++var2;
+                    }
+                }
+            }
+            throw new JceDecodeException("type mismatch.");
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public int[] read(int[] var1, int var2, boolean var3) {
+        var1 = null;
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var6 = new JceInputStream$HeadData();
+            this.readHead(var6);
+            if (var6.type == 9) {
+                int var4 = this.read(0, 0, true);
+                if (var4 < 0) {
+                    throw new JceDecodeException("size invalid: " + var4);
+                } else {
+                    int[] var5 = new int[var4];
+                    var2 = 0;
+
+                    while (true) {
+                        var1 = var5;
+                        if (var2 >= var4) {
+                            return var1;
+                        }
+
+                        var5[var2] = this.read(var5[0], 0, true);
+                        ++var2;
+                    }
+                }
+            }
+            throw new JceDecodeException("type mismatch.");
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public long[] read(long[] var1, int var2, boolean var3) {
+        var1 = null;
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var6 = new JceInputStream$HeadData();
+            this.readHead(var6);
+            if (var6.type == 9) {
+                int var4 = this.read(0, 0, true);
+                if (var4 < 0) {
+                    throw new JceDecodeException("size invalid: " + var4);
+                } else {
+                    long[] var5 = new long[var4];
+                    var2 = 0;
+
+                    while (true) {
+                        var1 = var5;
+                        if (var2 >= var4) {
+                            return var1;
+                        }
+
+                        var5[var2] = this.read(var5[0], 0, true);
+                        ++var2;
+                    }
+                }
+            }
+            throw new JceDecodeException("type mismatch.");
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public JceStruct[] read(JceStruct[] var1, int var2, boolean var3) {
+        return (JceStruct[]) this.readArray((Object[]) var1, var2, var3);
+    }
+
+    public String[] read(String[] var1, int var2, boolean var3) {
+        return (String[]) this.readArray((Object[]) var1, var2, var3);
+    }
+
+    public short[] read(short[] var1, int var2, boolean var3) {
+        var1 = null;
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var6 = new JceInputStream$HeadData();
+            this.readHead(var6);
+            if (var6.type == 9) {
+                int var4 = this.read(0, 0, true);
+                if (var4 < 0) {
+                    throw new JceDecodeException("size invalid: " + var4);
+                } else {
+                    short[] var5 = new short[var4];
+                    var2 = 0;
+
+                    while (true) {
+                        var1 = var5;
+                        if (var2 >= var4) {
+                            return var1;
+                        }
+
+                        var5[var2] = this.read(var5[0], 0, true);
+                        ++var2;
+                    }
+                }
+            }
+            throw new JceDecodeException("type mismatch.");
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public boolean[] read(boolean[] var1, int var2, boolean var3) {
+        var1 = null;
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var6 = new JceInputStream$HeadData();
+            this.readHead(var6);
+            if (var6.type == 9) {
+                int var4 = this.read(0, 0, true);
+                if (var4 < 0) {
+                    throw new JceDecodeException("size invalid: " + var4);
+                } else {
+                    boolean[] var5 = new boolean[var4];
+                    var2 = 0;
+
+                    while (true) {
+                        var1 = var5;
+                        if (var2 >= var4) {
+                            return var1;
+                        }
+
+                        var5[var2] = this.read(var5[0], 0, true);
+                        ++var2;
+                    }
+                }
+            }
+            throw new JceDecodeException("type mismatch.");
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public <T> List<T> readArray(List<T> var1, int var2, boolean var3) {
+        byte var4 = 0;
+        if (var1 != null && !var1.isEmpty()) {
+            Object[] var6 = this.readArrayImpl(var1.get(0), var2, var3);
+            if (var6 == null) {
+                return null;
+            } else {
+                ArrayList var5 = new ArrayList();
+
+                for (var2 = var4; var2 < var6.length; ++var2) {
+                    var5.add(var6[var2]);
+                }
+
+                return var5;
+            }
+        } else {
+            return new ArrayList<>();
+        }
+    }
+
+    public <T> T[] readArray(T[] var1, int var2, boolean var3) {
+        if (var1 != null && var1.length != 0) {
+            return this.readArrayImpl(var1[0], var2, var3);
+        } else {
+            throw new JceDecodeException("unable to get type of key and value.");
+        }
+    }
+
+    public String readByteString(String var1, int var2, boolean var3) {
+        if (this.skipToTag(var2)) {
+            JceInputStream$HeadData var5 = new JceInputStream$HeadData();
+            this.readHead(var5);
+            byte[] var6;
+            switch (var5.type) {
+                case 6:
+                    byte var4 = this.buffer.get();
+                    var2 = var4;
+                    if (var4 < 0) {
+                        var2 = var4 + 256;
+                    }
+
+                    var6 = new byte[var2];
+                    this.buffer.get(var6);
+                    return HexUtil.bytes2HexStr(var6);
+                case 7:
+                    var2 = this.buffer.getInt();
+                    if (var2 <= 104857600 && var2 >= 0 && var2 <= this.buffer.capacity()) {
+                        var6 = new byte[var2];
+                        this.buffer.get(var6);
+                        return HexUtil.bytes2HexStr(var6);
+                    }
+
+                    throw new JceDecodeException("String too long: " + var2);
+                default:
+                    throw new JceDecodeException("type mismatch.");
+            }
+        } else if (var3) {
+            throw new JceDecodeException("require field not exist.");
+        } else {
+            return var1;
+        }
+    }
+
+    public void readHead(JceInputStream$HeadData var1) {
+        readHead(var1, this.buffer);
+    }
+
+    public List<java.io.Serializable> readList(int var1, boolean var2) {
+        ArrayList<java.io.Serializable> var6 = new ArrayList<java.io.Serializable>();
+        if (this.skipToTag(var1)) {
+            JceInputStream$HeadData var7 = new JceInputStream$HeadData();
+            this.readHead(var7);
+            if (var7.type == 9) {
+                int var5 = this.read(0, 0, true);
+                if (var5 < 0) {
+                    throw new JceDecodeException("size invalid: " + var5);
+                }
+
+                var1 = 0;
+
+                for (; var1 < var5; ++var1) {
+                    var7 = new JceInputStream$HeadData();
+                    this.readHead(var7);
+                    switch (var7.type) {
+                        case 0:
+                            this.skip(1);
+                            break;
+                        case 1:
+                            this.skip(2);
+                            break;
+                        case 2:
+                            this.skip(4);
+                            break;
+                        case 3:
+                            this.skip(8);
+                            break;
+                        case 4:
+                            this.skip(4);
+                            break;
+                        case 5:
+                            this.skip(8);
+                            break;
+                        case 6:
+                            byte var4 = this.buffer.get();
+                            int var3 = var4;
+                            if (var4 < 0) {
+                                var3 = var4 + 256;
+                            }
+
+                            this.skip(var3);
+                            break;
+                        case 7:
+                            this.skip(this.buffer.getInt());
+                        case 8:
+                        case 9:
+                            break;
+                        case 10:
+                            try {
+                                JceStruct var9 = (JceStruct) Class.forName(JceStruct.class.getName()).getConstructor().newInstance();
+                                var9.readFrom(this);
+                                this.skipToStructEnd();
+                                var6.add(var9);
+                                break;
+                            } catch (Exception var8) {
+                                var8.printStackTrace();
+                                throw new JceDecodeException("type mismatch." + var8);
+                            }
+                        case 11:
+                        default:
+                            throw new JceDecodeException("type mismatch.");
+                        case 12:
+                            var6.add(new Integer(0));
+                    }
+                }
+            } else {
+                throw new JceDecodeException("type mismatch.");
+            }
+        } else if (var2) {
+            throw new JceDecodeException("require field not exist.");
+        }
+
+        return var6;
+    }
+
+    public <K, V> HashMap<K, V> readMap(Map<K, V> var1, int var2, boolean var3) {
+        return (HashMap<K, V>) this.readMap(new HashMap<K, V>(), var1, var2, var3);
+    }
+
+    public String readString(int var1, boolean var2) {
+        String var4 = null;
+        if (this.skipToTag(var1)) {
+            JceInputStream$HeadData var8 = new JceInputStream$HeadData();
+            this.readHead(var8);
+            switch (var8.type) {
+                case 6:
+                    byte var3 = this.buffer.get();
+                    var1 = var3;
+                    if (var3 < 0) {
+                        var1 = var3 + 256;
+                    }
+
+                    byte[] var10 = new byte[var1];
+                    this.buffer.get(var10);
+
+                    try {
+                        var4 = new String(var10, this.sServerEncoding);
+                        break;
+                    } catch (UnsupportedEncodingException var7) {
+                        return new String(var10);
+                    }
+                case 7:
+                    var1 = this.buffer.getInt();
+                    if (var1 <= 104857600 && var1 >= 0 && var1 <= this.buffer.capacity()) {
+                        byte[] var9 = new byte[var1];
+                        this.buffer.get(var9);
+
+                        try {
+                            String var5 = new String(var9, this.sServerEncoding);
+                            return var5;
+                        } catch (UnsupportedEncodingException var6) {
+                            return new String(var9);
+                        }
+                    }
+
+                    throw new JceDecodeException("String too long: " + var1);
+                default:
+                    throw new JceDecodeException("type mismatch.");
+            }
+        } else if (var2) {
+            throw new JceDecodeException("require field not exist.");
+        }
+
+        return var4;
+    }
+
+    public Map<String, String> readStringMap(int var1, boolean var2) {
+        HashMap<String, String> var4 = new HashMap<>();
+        if (this.skipToTag(var1)) {
+            JceInputStream$HeadData var5 = new JceInputStream$HeadData();
+            this.readHead(var5);
+            if (var5.type == 8) {
+                int var3 = this.read(0, 0, true);
+                if (var3 < 0) {
+                    throw new JceDecodeException("size invalid: " + var3);
+                }
+
+                for (var1 = 0; var1 < var3; ++var1) {
+                    var4.put(this.readString(0, true), this.readString(1, true));
+                }
+            } else {
+                throw new JceDecodeException("type mismatch.");
+            }
+        } else if (var2) {
+            throw new JceDecodeException("require field not exist.");
+        }
+
+        return var4;
+    }
+
+    public int setServerEncoding(String var1) {
+        this.sServerEncoding = var1;
+        return 0;
+    }
+
+    public void skipToStructEnd() {
+        JceInputStream$HeadData var1 = new JceInputStream$HeadData();
+
+        do {
+            this.readHead(var1);
+            this.skipField(var1.type);
+        } while (var1.type != 11);
+
+    }
+
+    public boolean skipToTag(int n2) {
+        try {
+            JceInputStream$HeadData jceInputStream$HeadData = new JceInputStream$HeadData();
+            do {
+                int n3 = this.peakHead(jceInputStream$HeadData);
+                if (jceInputStream$HeadData.type == 11) {
+                    return false;
+                }
+                if (n2 <= jceInputStream$HeadData.tag) {
+                    return n2 == jceInputStream$HeadData.tag;
+                }
+                this.skip(n3);
+                this.skipField(jceInputStream$HeadData.type);
+            } while (true);
+        } catch (JceDecodeException jceDecodeException) {
+            return false;
+        } catch (BufferUnderflowException bufferUnderflowException) {
+            // empty catch block
+        }
+        return false;
+    }
+
+    public void warp(byte[] var1) {
+        this.wrap(var1);
+    }
+
+    public void wrap(byte[] var1) {
+        this.buffer = ByteBuffer.wrap(var1);
+    }
+}
diff --git a/mirai-debug/src/test/java/jce/jce/JceOutputStream.java b/mirai-debug/src/test/java/jce/jce/JceOutputStream.java
new file mode 100644
index 000000000..7a2a24202
--- /dev/null
+++ b/mirai-debug/src/test/java/jce/jce/JceOutputStream.java
@@ -0,0 +1,430 @@
+package jce.jce;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class JceOutputStream {
+   // $FF: renamed from: bs java.nio.ByteBuffer
+   private ByteBuffer field_80728;
+   private OnIllegalArgumentException exceptionHandler;
+   protected String sServerEncoding;
+
+   public JceOutputStream() {
+      this(128);
+   }
+
+   public JceOutputStream(int var1) {
+      this.sServerEncoding = "GBK";
+      this.field_80728 = ByteBuffer.allocate(var1);
+   }
+
+   public JceOutputStream(ByteBuffer var1) {
+      this.sServerEncoding = "GBK";
+      this.field_80728 = var1;
+   }
+
+   public static void main(String[] var0) {
+      JceOutputStream var2 = new JceOutputStream();
+      var2.write(1311768467283714885L, 0);
+      ByteBuffer var1 = var2.getByteBuffer();
+      System.out.println(HexUtil.bytes2HexStr(var1.array()));
+      System.out.println(Arrays.toString(var2.toByteArray()));
+   }
+
+   private void writeArray(Object[] var1, int var2) {
+      this.reserve(8);
+      this.writeHead((byte) 9, var2);
+      this.write(var1.length, 0);
+      int var3 = var1.length;
+
+      for (var2 = 0; var2 < var3; ++var2) {
+         this.write(var1[var2], 0);
+      }
+
+   }
+
+   public ByteBuffer getByteBuffer() {
+      return this.field_80728;
+   }
+
+   public OnIllegalArgumentException getExceptionHandler() {
+      return this.exceptionHandler;
+   }
+
+   public void reserve(int var1) {
+      if (this.field_80728.remaining() < var1) {
+         int var2 = (this.field_80728.capacity() + var1) * 2;
+
+         ByteBuffer var3;
+         try {
+            var3 = ByteBuffer.allocate(var2);
+            var3.put(this.field_80728.array(), 0, this.field_80728.position());
+         } catch (IllegalArgumentException var4) {
+            if (this.exceptionHandler != null) {
+               this.exceptionHandler.onException(var4, this.field_80728, var1, var2);
+            }
+
+            throw var4;
+         }
+
+         this.field_80728 = var3;
+      }
+
+   }
+
+   public void setExceptionHandler(OnIllegalArgumentException var1) {
+      this.exceptionHandler = var1;
+   }
+
+   public int setServerEncoding(String var1) {
+      this.sServerEncoding = var1;
+      return 0;
+   }
+
+   public byte[] toByteArray() {
+      byte[] var1 = new byte[this.field_80728.position()];
+      System.arraycopy(this.field_80728.array(), 0, var1, 0, this.field_80728.position());
+      return var1;
+   }
+
+   public void write(byte var1, int var2) {
+      this.reserve(3);
+      if (var1 == 0) {
+         this.writeHead((byte) 12, var2);
+      } else {
+         this.writeHead((byte) 0, var2);
+         this.field_80728.put(var1);
+      }
+   }
+
+   public void write(double var1, int var3) {
+      this.reserve(10);
+      this.writeHead((byte) 5, var3);
+      this.field_80728.putDouble(var1);
+   }
+
+   public void write(float var1, int var2) {
+      this.reserve(6);
+      this.writeHead((byte) 4, var2);
+      this.field_80728.putFloat(var1);
+   }
+
+   public void write(int var1, int var2) {
+      this.reserve(6);
+      if (var1 >= -32768 && var1 <= 32767) {
+         this.write((short) var1, var2);
+      } else {
+         this.writeHead((byte) 2, var2);
+         this.field_80728.putInt(var1);
+      }
+   }
+
+   public void write(long var1, int id) {
+      this.reserve(10);
+      if (var1 >= -2147483648L && var1 <= 2147483647L) {
+         this.write((int) var1, id);
+      } else {
+         this.writeHead((byte) 3, id);
+         this.field_80728.putLong(var1);
+      }
+   }
+
+   public void write(JceStruct var1, int var2) {
+      this.reserve(2);
+      this.writeHead((byte) 10, var2);
+      var1.writeTo(this);
+      this.reserve(2);
+      this.writeHead((byte) 11, 0);
+   }
+
+   public void write(Boolean var1, int var2) {
+      this.write((boolean) var1, var2);
+   }
+
+   public void write(Byte var1, int var2) {
+      this.write((byte) var1, var2);
+   }
+
+   public void write(Double var1, int var2) {
+      this.write((double) var1, var2);
+   }
+
+   public void write(Float var1, int var2) {
+      this.write((float) var1, var2);
+   }
+
+   public void write(Integer var1, int var2) {
+      this.write((int) var1, var2);
+   }
+
+   public void write(Long var1, int var2) {
+      this.write((long) var1, var2);
+   }
+
+   public void write(Object var1, int var2) {
+      if (var1 instanceof Byte) {
+         this.write((Byte) var1, var2);
+      } else if (var1 instanceof Boolean) {
+         this.write((Boolean) var1, var2);
+      } else if (var1 instanceof Short) {
+         this.write((Short) var1, var2);
+      } else if (var1 instanceof Integer) {
+         this.write((Integer) var1, var2);
+      } else if (var1 instanceof Long) {
+         this.write((Long) var1, var2);
+      } else if (var1 instanceof Float) {
+         this.write((Float) var1, var2);
+      } else if (var1 instanceof Double) {
+         this.write((Double) var1, var2);
+      } else if (var1 instanceof String) {
+         this.write((String) var1, var2);
+      } else if (var1 instanceof Map) {
+         this.write((Map) var1, var2);
+      } else if (var1 instanceof List) {
+         this.write((List) var1, var2);
+      } else if (var1 instanceof JceStruct) {
+         this.write((JceStruct) var1, var2);
+      } else if (var1 instanceof byte[]) {
+         this.write((byte[]) var1, var2);
+      } else if (var1 instanceof boolean[]) {
+         this.write((boolean[]) var1, var2);
+      } else if (var1 instanceof short[]) {
+         this.write((short[]) var1, var2);
+      } else if (var1 instanceof int[]) {
+         this.write((int[]) var1, var2);
+      } else if (var1 instanceof long[]) {
+         this.write((long[]) var1, var2);
+      } else if (var1 instanceof float[]) {
+         this.write((float[]) var1, var2);
+      } else if (var1 instanceof double[]) {
+         this.write((double[]) var1, var2);
+      } else if (var1.getClass().isArray()) {
+         this.writeArray((Object[]) var1, var2);
+      } else if (var1 instanceof Collection) {
+         this.write((Collection) var1, var2);
+      } else {
+         throw new JceEncodeException("write object error: unsupport type. " + var1.getClass());
+      }
+   }
+
+   public void write(Short var1, int var2) {
+      this.write((short) var1, var2);
+   }
+
+   public void write(String var1, int var2) {
+      byte[] var5;
+      label16:
+      {
+         byte[] var3;
+         try {
+            var3 = var1.getBytes(this.sServerEncoding);
+         } catch (UnsupportedEncodingException var4) {
+            var5 = var1.getBytes();
+            break label16;
+         }
+
+         var5 = var3;
+      }
+
+      this.reserve(var5.length + 10);
+      if (var5.length > 255) {
+         this.writeHead((byte) 7, var2);
+         this.field_80728.putInt(var5.length);
+      } else {
+         this.writeHead((byte) 6, var2);
+         this.field_80728.put((byte) var5.length);
+      }
+      this.field_80728.put(var5);
+   }
+
+   public <T> void write(Collection<T> var1, int var2) {
+      this.reserve(8);
+      this.writeHead((byte) 9, var2);
+      if (var1 == null) {
+         var2 = 0;
+      } else {
+         var2 = var1.size();
+      }
+
+      this.write(var2, 0);
+      if (var1 != null) {
+
+         for (T t : var1) {
+            this.write(t, 0);
+         }
+      }
+
+   }
+
+   public <K, V> void write(Map<K, V> var1, int var2) {
+      this.reserve(8);
+      this.writeHead((byte) 8, var2);
+      if (var1 == null) {
+         var2 = 0;
+      } else {
+         var2 = var1.size();
+      }
+
+      this.write(var2, 0);
+      if (var1 != null) {
+
+         for (Entry<K, V> kvEntry : var1.entrySet()) {
+            this.write(((Entry) kvEntry).getKey(), 0);
+            this.write(((Entry) kvEntry).getValue(), 1);
+         }
+      }
+
+   }
+
+   public void write(short var1, int var2) {
+      this.reserve(4);
+      if (var1 >= -128 && var1 <= 127) {
+         this.write((byte) var1, var2);
+      } else {
+         this.writeHead((byte) 1, var2);
+         this.field_80728.putShort(var1);
+      }
+   }
+
+   public void write(boolean var1, int var2) {
+      byte var3;
+      if (var1) {
+         var3 = 1;
+      } else {
+         var3 = 0;
+      }
+
+      this.write(var3, var2);
+   }
+
+   public void write(byte[] var1, int var2) {
+      this.reserve(var1.length + 8);
+      this.writeHead((byte) 13, var2);
+      this.writeHead((byte) 0, 0);
+      this.write(var1.length, 0);
+      this.field_80728.put(var1);
+   }
+
+   public void write(double[] var1, int var2) {
+      this.reserve(8);
+      this.writeHead((byte) 9, var2);
+      this.write(var1.length, 0);
+      int var3 = var1.length;
+
+      for (var2 = 0; var2 < var3; ++var2) {
+         this.write(var1[var2], 0);
+      }
+
+   }
+
+   public void write(float[] var1, int var2) {
+      this.reserve(8);
+      this.writeHead((byte) 9, var2);
+      this.write(var1.length, 0);
+      int var3 = var1.length;
+
+      for (var2 = 0; var2 < var3; ++var2) {
+         this.write(var1[var2], 0);
+      }
+
+   }
+
+   public void write(int[] var1, int var2) {
+      this.reserve(8);
+      this.writeHead((byte) 9, var2);
+      this.write(var1.length, 0);
+      int var3 = var1.length;
+
+      for (var2 = 0; var2 < var3; ++var2) {
+         this.write(var1[var2], 0);
+      }
+
+   }
+
+   public void write(long[] var1, int var2) {
+      this.reserve(8);
+      this.writeHead((byte) 9, var2);
+      this.write(var1.length, 0);
+      int var3 = var1.length;
+
+      for (var2 = 0; var2 < var3; ++var2) {
+         this.write(var1[var2], 0);
+      }
+
+   }
+
+   public <T> void write(T[] var1, int var2) {
+      this.writeArray(var1, var2);
+   }
+
+   public void write(short[] var1, int var2) {
+      this.reserve(8);
+      this.writeHead((byte) 9, var2);
+      this.write(var1.length, 0);
+      int var3 = var1.length;
+
+      for (var2 = 0; var2 < var3; ++var2) {
+         this.write(var1[var2], 0);
+      }
+
+   }
+
+   public void write(boolean[] var1, int var2) {
+      this.reserve(8);
+      this.writeHead((byte) 9, var2);
+      this.write(var1.length, 0);
+      int var3 = var1.length;
+
+      for (var2 = 0; var2 < var3; ++var2) {
+         this.write(var1[var2], 0);
+      }
+
+   }
+
+   public void writeByteString(String var1, int var2) {
+      this.reserve(var1.length() + 10);
+      byte[] var3 = HexUtil.hexStr2Bytes(var1);
+      if (var3.length > 255) {
+         this.writeHead((byte) 7, var2);
+         this.field_80728.putInt(var3.length);
+         this.field_80728.put(var3);
+      } else {
+         this.writeHead((byte) 6, var2);
+         this.field_80728.put((byte) var3.length);
+         this.field_80728.put(var3);
+      }
+   }
+
+   public void writeHead(byte var1, int var2) {
+      byte var3;
+      if (var2 < 15) {
+         var3 = (byte) (var2 << 4 | var1);
+         this.field_80728.put(var3);
+      } else if (var2 < 256) {
+         var3 = (byte) (var1 | 240);
+         this.field_80728.put(var3);
+         this.field_80728.put((byte) var2);
+      } else {
+         throw new JceEncodeException("tag is too large: " + var2);
+      }
+   }
+
+   public void writeStringByte(String var1, int var2) {
+      byte[] var3 = HexUtil.hexStr2Bytes(var1);
+      this.reserve(var3.length + 10);
+      if (var3.length > 255) {
+         this.writeHead((byte) 7, var2);
+         this.field_80728.putInt(var3.length);
+         this.field_80728.put(var3);
+      } else {
+         this.writeHead((byte) 6, var2);
+         this.field_80728.put((byte) var3.length);
+         this.field_80728.put(var3);
+      }
+   }
+}
diff --git a/mirai-debug/src/test/java/jce/jce/JceStruct.java b/mirai-debug/src/test/java/jce/jce/JceStruct.java
new file mode 100644
index 000000000..9c32025d6
--- /dev/null
+++ b/mirai-debug/src/test/java/jce/jce/JceStruct.java
@@ -0,0 +1,78 @@
+package jce.jce;
+
+import java.io.Serializable;
+
+public abstract class JceStruct implements Serializable {
+   public static final byte BYTE = 0;
+   public static final byte DOUBLE = 5;
+   public static final byte FLOAT = 4;
+   public static final byte INT = 2;
+   public static final int JCE_MAX_STRING_LENGTH = 104857600;
+   public static final byte LIST = 9;
+   public static final byte LONG = 3;
+   public static final byte MAP = 8;
+   public static final byte SHORT = 1;
+   public static final byte SIMPLE_LIST = 13;
+   public static final byte STRING1 = 6;
+   public static final byte STRING4 = 7;
+   public static final byte STRUCT_BEGIN = 10;
+   public static final byte STRUCT_END = 11;
+   public static final byte ZERO_TAG = 12;
+
+   public static String toDisplaySimpleString(JceStruct var0) {
+      if (var0 == null) {
+         return null;
+      } else {
+         StringBuilder var1 = new StringBuilder();
+         var0.displaySimple(var1, 0);
+         return var1.toString();
+      }
+   }
+
+   public boolean containField(String var1) {
+      return false;
+   }
+
+   public void display(StringBuilder var1, int var2) {
+   }
+
+   public void displaySimple(StringBuilder var1, int var2) {
+   }
+
+   public Object getFieldByName(String var1) {
+      return null;
+   }
+
+   public JceStruct newInit() {
+      return null;
+   }
+
+   public abstract void readFrom(JceInputStream var1);
+
+   public void recyle() {
+   }
+
+   public void setFieldByName(String var1, Object var2) {
+   }
+
+   public byte[] toByteArray() {
+      JceOutputStream var1 = new JceOutputStream();
+      this.writeTo(var1);
+      return var1.toByteArray();
+   }
+
+   public byte[] toByteArray(String var1) {
+      JceOutputStream var2 = new JceOutputStream();
+      var2.setServerEncoding(var1);
+      this.writeTo(var2);
+      return var2.toByteArray();
+   }
+
+   public String toString() {
+      StringBuilder var1 = new StringBuilder();
+      this.display(var1, 0);
+      return var1.toString();
+   }
+
+   public abstract void writeTo(JceOutputStream var1);
+}
diff --git a/mirai-debug/src/test/java/jce/jce/JceUtil.java b/mirai-debug/src/test/java/jce/jce/JceUtil.java
new file mode 100644
index 000000000..a41763e75
--- /dev/null
+++ b/mirai-debug/src/test/java/jce/jce/JceUtil.java
@@ -0,0 +1,594 @@
+package jce.jce;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.List;
+
+public final class JceUtil {
+    private static final byte[] highDigits;
+    private static final int iConstant = 37;
+    private static final int iTotal = 17;
+    private static final byte[] lowDigits;
+
+    static {
+        byte[] var1 = new byte[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70};
+        byte[] var2 = new byte[256];
+        byte[] var3 = new byte[256];
+
+        for (int var0 = 0; var0 < 256; ++var0) {
+            var2[var0] = var1[var0 >>> 4];
+            var3[var0] = var1[var0 & 15];
+        }
+
+        highDigits = var2;
+        lowDigits = var3;
+    }
+
+    public static int compareTo(byte var0, byte var1) {
+        if (var0 < var1) {
+            return -1;
+        } else {
+            return var0 > var1 ? 1 : 0;
+        }
+    }
+
+    public static int compareTo(char var0, char var1) {
+        if (var0 < var1) {
+            return -1;
+        } else {
+            return var0 > var1 ? 1 : 0;
+        }
+    }
+
+    public static int compareTo(double var0, double var2) {
+        if (var0 < var2) {
+            return -1;
+        } else {
+            return var0 > var2 ? 1 : 0;
+        }
+    }
+
+    public static int compareTo(float var0, float var1) {
+        if (var0 < var1) {
+            return -1;
+        } else {
+            return var0 > var1 ? 1 : 0;
+        }
+    }
+
+    public static int compareTo(int var0, int var1) {
+        if (var0 < var1) {
+            return -1;
+        } else {
+            return var0 > var1 ? 1 : 0;
+        }
+    }
+
+    public static int compareTo(long var0, long var2) {
+        if (var0 < var2) {
+            return -1;
+        } else {
+            return var0 > var2 ? 1 : 0;
+        }
+    }
+
+    public static <T extends Comparable<T>> int compareTo(T var0, T var1) {
+        return var0.compareTo(var1);
+    }
+
+    public static <T extends Comparable<T>> int compareTo(List<T> var0, List<T> var1) {
+        Iterator var3 = var0.iterator();
+        Iterator var4 = var1.iterator();
+
+        while (var3.hasNext() && var4.hasNext()) {
+            int var2 = ((Comparable) var3.next()).compareTo(var4.next());
+            if (var2 != 0) {
+                return var2;
+            }
+        }
+
+        return compareTo(var3.hasNext(), var4.hasNext());
+    }
+
+    public static int compareTo(short var0, short var1) {
+        if (var0 < var1) {
+            return -1;
+        } else {
+            return var0 > var1 ? 1 : 0;
+        }
+    }
+
+    public static int compareTo(boolean var0, boolean var1) {
+        byte var3 = 1;
+        byte var2;
+        if (var0) {
+            var2 = 1;
+        } else {
+            var2 = 0;
+        }
+
+        if (!var1) {
+            var3 = 0;
+        }
+
+        return var2 - var3;
+    }
+
+    public static int compareTo(byte[] var0, byte[] var1) {
+        int var3 = 0;
+
+        for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) {
+            int var4 = compareTo(var0[var2], var1[var3]);
+            if (var4 != 0) {
+                return var4;
+            }
+
+            ++var3;
+        }
+
+        return compareTo(var0.length, var1.length);
+    }
+
+    public static int compareTo(char[] var0, char[] var1) {
+        int var3 = 0;
+
+        for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) {
+            int var4 = compareTo(var0[var2], var1[var3]);
+            if (var4 != 0) {
+                return var4;
+            }
+
+            ++var3;
+        }
+
+        return compareTo(var0.length, var1.length);
+    }
+
+    public static int compareTo(double[] var0, double[] var1) {
+        int var3 = 0;
+
+        for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) {
+            int var4 = compareTo(var0[var2], var1[var3]);
+            if (var4 != 0) {
+                return var4;
+            }
+
+            ++var3;
+        }
+
+        return compareTo(var0.length, var1.length);
+    }
+
+    public static int compareTo(float[] var0, float[] var1) {
+        int var3 = 0;
+
+        for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) {
+            int var4 = compareTo(var0[var2], var1[var3]);
+            if (var4 != 0) {
+                return var4;
+            }
+
+            ++var3;
+        }
+
+        return compareTo(var0.length, var1.length);
+    }
+
+    public static int compareTo(int[] var0, int[] var1) {
+        int var3 = 0;
+
+        for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) {
+            int var4 = compareTo(var0[var2], var1[var3]);
+            if (var4 != 0) {
+                return var4;
+            }
+
+            ++var3;
+        }
+
+        return compareTo(var0.length, var1.length);
+    }
+
+    public static int compareTo(long[] var0, long[] var1) {
+        int var3 = 0;
+
+        for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) {
+            int var4 = compareTo(var0[var2], var1[var3]);
+            if (var4 != 0) {
+                return var4;
+            }
+
+            ++var3;
+        }
+
+        return compareTo(var0.length, var1.length);
+    }
+
+    public static <T extends Comparable<T>> int compareTo(T[] var0, T[] var1) {
+        int var3 = 0;
+
+        for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) {
+            int var4 = var0[var2].compareTo(var1[var3]);
+            if (var4 != 0) {
+                return var4;
+            }
+
+            ++var3;
+        }
+
+        return compareTo(var0.length, var1.length);
+    }
+
+    public static int compareTo(short[] var0, short[] var1) {
+        int var3 = 0;
+
+        for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) {
+            int var4 = compareTo(var0[var2], var1[var3]);
+            if (var4 != 0) {
+                return var4;
+            }
+
+            ++var3;
+        }
+
+        return compareTo(var0.length, var1.length);
+    }
+
+    public static int compareTo(boolean[] var0, boolean[] var1) {
+        int var3 = 0;
+
+        for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) {
+            int var4 = compareTo(var0[var2], var1[var3]);
+            if (var4 != 0) {
+                return var4;
+            }
+
+            ++var3;
+        }
+
+        return compareTo(var0.length, var1.length);
+    }
+
+    public static boolean equals(byte var0, byte var1) {
+        return var0 == var1;
+    }
+
+    public static boolean equals(char var0, char var1) {
+        return var0 == var1;
+    }
+
+    public static boolean equals(double var0, double var2) {
+        return var0 == var2;
+    }
+
+    public static boolean equals(float var0, float var1) {
+        return var0 == var1;
+    }
+
+    public static boolean equals(int var0, int var1) {
+        return var0 == var1;
+    }
+
+    public static boolean equals(long var0, long var2) {
+        return var0 == var2;
+    }
+
+    public static boolean equals(Object var0, Object var1) {
+        return var0.equals(var1);
+    }
+
+    public static boolean equals(short var0, short var1) {
+        return var0 == var1;
+    }
+
+    public static boolean equals(boolean var0, boolean var1) {
+        return var0 == var1;
+    }
+
+    public static String getHexdump(ByteBuffer var0) {
+        int var1 = var0.remaining();
+        if (var1 == 0) {
+            return "empty";
+        } else {
+            StringBuffer var4 = new StringBuffer(var0.remaining() * 3 - 1);
+            int var2 = var0.position();
+            int var3 = var0.get() & 255;
+            var4.append((char) highDigits[var3]);
+            var4.append((char) lowDigits[var3]);
+            --var1;
+
+            while (var1 > 0) {
+                var4.append(' ');
+                var3 = var0.get() & 255;
+                var4.append((char) highDigits[var3]);
+                var4.append((char) lowDigits[var3]);
+                --var1;
+            }
+
+            var0.position(var2);
+            return var4.toString();
+        }
+    }
+
+    public static String getHexdump(byte[] var0) {
+        return getHexdump(ByteBuffer.wrap(var0));
+    }
+
+    public static byte[] getJceBufArray(ByteBuffer var0) {
+        byte[] var1 = new byte[var0.position()];
+        System.arraycopy(var0.array(), 0, var1, 0, var1.length);
+        return var1;
+    }
+
+    public static int hashCode(byte var0) {
+        return var0 + 629;
+    }
+
+    public static int hashCode(char var0) {
+        return var0 + 629;
+    }
+
+    public static int hashCode(double var0) {
+        return hashCode(Double.doubleToLongBits(var0));
+    }
+
+    public static int hashCode(float var0) {
+        return Float.floatToIntBits(var0) + 629;
+    }
+
+    public static int hashCode(int var0) {
+        return var0 + 629;
+    }
+
+    public static int hashCode(long var0) {
+        return (int) (var0 >> 32 ^ var0) + 629;
+    }
+
+    public static int hashCode(Object var0) {
+        if (var0 == null) {
+            return 629;
+        } else if (var0.getClass().isArray()) {
+            if (var0 instanceof long[]) {
+                return hashCode((long[]) ((long[]) var0));
+            } else if (var0 instanceof int[]) {
+                return hashCode((int[]) ((int[]) var0));
+            } else if (var0 instanceof short[]) {
+                return hashCode((short[]) ((short[]) var0));
+            } else if (var0 instanceof char[]) {
+                return hashCode((char[]) ((char[]) var0));
+            } else if (var0 instanceof byte[]) {
+                return hashCode((byte[]) ((byte[]) var0));
+            } else if (var0 instanceof double[]) {
+                return hashCode((double[]) ((double[]) var0));
+            } else if (var0 instanceof float[]) {
+                return hashCode((float[]) ((float[]) var0));
+            } else if (var0 instanceof boolean[]) {
+                return hashCode((boolean[]) ((boolean[]) var0));
+            } else {
+                return var0 instanceof JceStruct[] ? hashCode((JceStruct[]) ((JceStruct[]) var0)) : hashCode((Object) ((Object[]) ((Object[]) var0)));
+            }
+        } else {
+            return var0 instanceof JceStruct ? var0.hashCode() : var0.hashCode() + 629;
+        }
+    }
+
+    public static int hashCode(short var0) {
+        return var0 + 629;
+    }
+
+    public static int hashCode(boolean var0) {
+        byte var1;
+        if (var0) {
+            var1 = 0;
+        } else {
+            var1 = 1;
+        }
+
+        return var1 + 629;
+    }
+
+    public static int hashCode(byte[] var0) {
+        int var3;
+        if (var0 == null) {
+            var3 = 629;
+        } else {
+            int var1 = 17;
+            int var2 = 0;
+
+            while (true) {
+                var3 = var1;
+                if (var2 >= var0.length) {
+                    break;
+                }
+
+                var1 = var1 * 37 + var0[var2];
+                ++var2;
+            }
+        }
+
+        return var3;
+    }
+
+    public static int hashCode(char[] var0) {
+        int var3;
+        if (var0 == null) {
+            var3 = 629;
+        } else {
+            int var1 = 17;
+            int var2 = 0;
+
+            while (true) {
+                var3 = var1;
+                if (var2 >= var0.length) {
+                    break;
+                }
+
+                var1 = var1 * 37 + var0[var2];
+                ++var2;
+            }
+        }
+
+        return var3;
+    }
+
+    public static int hashCode(double[] var0) {
+        int var3;
+        if (var0 == null) {
+            var3 = 629;
+        } else {
+            int var1 = 17;
+            int var2 = 0;
+
+            while (true) {
+                var3 = var1;
+                if (var2 >= var0.length) {
+                    break;
+                }
+
+                var1 = var1 * 37 + (int) (Double.doubleToLongBits(var0[var2]) ^ Double.doubleToLongBits(var0[var2]) >> 32);
+                ++var2;
+            }
+        }
+
+        return var3;
+    }
+
+    public static int hashCode(float[] var0) {
+        int var3;
+        if (var0 == null) {
+            var3 = 629;
+        } else {
+            int var1 = 17;
+            int var2 = 0;
+
+            while (true) {
+                var3 = var1;
+                if (var2 >= var0.length) {
+                    break;
+                }
+
+                var1 = var1 * 37 + Float.floatToIntBits(var0[var2]);
+                ++var2;
+            }
+        }
+
+        return var3;
+    }
+
+    public static int hashCode(int[] var0) {
+        int var3;
+        if (var0 == null) {
+            var3 = 629;
+        } else {
+            int var1 = 17;
+            int var2 = 0;
+
+            while (true) {
+                var3 = var1;
+                if (var2 >= var0.length) {
+                    break;
+                }
+
+                var1 = var1 * 37 + var0[var2];
+                ++var2;
+            }
+        }
+
+        return var3;
+    }
+
+    public static int hashCode(long[] var0) {
+        int var3;
+        if (var0 == null) {
+            var3 = 629;
+        } else {
+            int var1 = 17;
+            int var2 = 0;
+
+            while (true) {
+                var3 = var1;
+                if (var2 >= var0.length) {
+                    break;
+                }
+
+                var1 = var1 * 37 + (int) (var0[var2] ^ var0[var2] >> 32);
+                ++var2;
+            }
+        }
+
+        return var3;
+    }
+
+    public static int hashCode(JceStruct[] var0) {
+        int var3;
+        if (var0 == null) {
+            var3 = 629;
+        } else {
+            int var1 = 17;
+            int var2 = 0;
+
+            while (true) {
+                var3 = var1;
+                if (var2 >= var0.length) {
+                    break;
+                }
+
+                var1 = var1 * 37 + var0[var2].hashCode();
+                ++var2;
+            }
+        }
+
+        return var3;
+    }
+
+    public static int hashCode(short[] var0) {
+        int var3;
+        if (var0 == null) {
+            var3 = 629;
+        } else {
+            int var1 = 17;
+            int var2 = 0;
+
+            while (true) {
+                var3 = var1;
+                if (var2 >= var0.length) {
+                    break;
+                }
+
+                var1 = var1 * 37 + var0[var2];
+                ++var2;
+            }
+        }
+
+        return var3;
+    }
+
+    public static int hashCode(boolean[] var0) {
+        int var3;
+        if (var0 == null) {
+            var3 = 629;
+        } else {
+            int var1 = 17;
+            int var2 = 0;
+
+            while (true) {
+                var3 = var1;
+                if (var2 >= var0.length) {
+                    break;
+                }
+
+                byte var4;
+                if (var0[var2]) {
+                    var4 = 0;
+                } else {
+                    var4 = 1;
+                }
+
+                var1 = var4 + var1 * 37;
+                ++var2;
+            }
+        }
+
+        return var3;
+    }
+}
diff --git a/mirai-debug/src/test/java/jce/jce/OnIllegalArgumentException.java b/mirai-debug/src/test/java/jce/jce/OnIllegalArgumentException.java
new file mode 100644
index 000000000..af7eb6cee
--- /dev/null
+++ b/mirai-debug/src/test/java/jce/jce/OnIllegalArgumentException.java
@@ -0,0 +1,7 @@
+package jce.jce;
+
+import java.nio.ByteBuffer;
+
+public interface OnIllegalArgumentException {
+   void onException(IllegalArgumentException var1, ByteBuffer var2, int var3, int var4);
+}
diff --git a/mirai-debug/src/test/kotlin/jceTest/jceTest.kt b/mirai-debug/src/test/kotlin/jceTest/jceTest.kt
new file mode 100644
index 000000000..30116c4b8
--- /dev/null
+++ b/mirai-debug/src/test/kotlin/jceTest/jceTest.kt
@@ -0,0 +1,293 @@
+package jceTest
+
+import io.ktor.util.InternalAPI
+import jce.jce.JceInputStream
+import jce.jce.JceOutputStream
+import jce.jce.JceStruct
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.readBytes
+import net.mamoe.mirai.qqandroid.network.io.JceInput
+import net.mamoe.mirai.qqandroid.network.io.JceOutput
+import net.mamoe.mirai.qqandroid.network.io.buildJcePacket
+import net.mamoe.mirai.utils.io.toUHexString
+import org.junit.Test
+
+private infix fun ByteReadPacket.shouldEqualTo(another: ByteArray) {
+    this.readBytes().let {
+        check(it.contentEquals(another)) {
+            """actual:   ${it.toUHexString()}
+              |required: ${another.toUHexString()} 
+        """.trimMargin()
+        }
+    }
+}
+
+@UseExperimental(InternalAPI::class)
+private fun qqJce(block: JceOutputStream.() -> Unit): ByteArray {
+    return JceOutputStream().apply(block).toByteArray()
+}
+
+internal class JceOutputTest {
+
+    @Test
+    fun writeByte() {
+        buildJcePacket {
+            writeByte(1, 1)
+            writeByte(-128, 2)
+        } shouldEqualTo qqJce {
+            write(1.toByte(), 1)
+            write((-128).toByte(), 2)
+        }
+    }
+
+    @Test
+    fun writeDouble() {
+        buildJcePacket {
+            writeDouble(1.0, 1)
+            writeDouble(-128.0, 2)
+        } shouldEqualTo qqJce {
+            write(1.toDouble(), 1)
+            write((-128).toDouble(), 2)
+        }
+    }
+
+    @Test
+    fun writeFloat() {
+        buildJcePacket {
+            writeFloat(1.0f, 1)
+            writeFloat(-128.0f, 2)
+        } shouldEqualTo qqJce {
+            write(1.toFloat(), 1)
+            write((-128).toFloat(), 2)
+        }
+    }
+
+    @Test
+    fun writeFully() {
+        buildJcePacket {
+            writeFully(byteArrayOf(1, 2), 1)
+            writeFully(byteArrayOf(1, 2), 2)
+        } shouldEqualTo qqJce {
+            write(byteArrayOf(1, 2), 1)
+            write(byteArrayOf(1, 2), 2)
+        }
+    }
+
+    @Test
+    fun testWriteFully() {
+        buildJcePacket {
+            writeFully(intArrayOf(1, 2), 1)
+            writeFully(intArrayOf(1, 2), 2)
+        } shouldEqualTo qqJce {
+            write(intArrayOf(1, 2), 1)
+            write(intArrayOf(1, 2), 2)
+        }
+    }
+
+    @Test
+    fun testWriteFully1() {
+        buildJcePacket {
+            writeFully(shortArrayOf(1, 2), 1)
+            writeFully(shortArrayOf(1, 2), 2)
+        } shouldEqualTo qqJce {
+            write(shortArrayOf(1, 2), 1)
+            write(shortArrayOf(1, 2), 2)
+        }
+    }
+
+    @Test
+    fun testWriteFully2() {
+        buildJcePacket {
+            writeFully(booleanArrayOf(true, false), 1)
+            writeFully(booleanArrayOf(true, false), 2)
+        } shouldEqualTo qqJce {
+            write(booleanArrayOf(true, false), 1)
+            write(booleanArrayOf(true, false), 2)
+        }
+    }
+
+    @Test
+    fun testWriteFully3() {
+        buildJcePacket {
+            writeFully(longArrayOf(1, 2), 1)
+            writeFully(longArrayOf(1, 2), 2)
+        } shouldEqualTo qqJce {
+            write(longArrayOf(1, 2), 1)
+            write(longArrayOf(1, 2), 2)
+        }
+    }
+
+    @Test
+    fun testWriteFully4() {
+        buildJcePacket {
+            writeFully(floatArrayOf(1f, 2f), 1)
+            writeFully(floatArrayOf(1f, 2f), 2)
+        } shouldEqualTo qqJce {
+            write(floatArrayOf(1f, 2f), 1)
+            write(floatArrayOf(1f, 2f), 2)
+        }
+    }
+
+    @Test
+    fun testWriteFully5() {
+        buildJcePacket {
+            writeFully(doubleArrayOf(1.0, 2.0), 1)
+            writeFully(doubleArrayOf(1.0, 2.0), 2)
+        } shouldEqualTo qqJce {
+            write(doubleArrayOf(1.0, 2.0), 1)
+            write(doubleArrayOf(1.0, 2.0), 2)
+        }
+    }
+
+    @Test
+    fun testWriteFully6() {
+        buildJcePacket {
+            writeFully(arrayOf("123", "哈哈"), 1)
+            writeFully(arrayOf("123", "哈哈"), 2)
+        } shouldEqualTo qqJce {
+            write(arrayOf("123", "哈哈"), 1)
+            write(arrayOf("123", "哈哈"), 2)
+        }
+    }
+
+    @Test
+    fun writeInt() {
+        buildJcePacket {
+            writeInt(1, 1)
+            writeInt(-128, 2)
+        } shouldEqualTo qqJce {
+            write(1, 1)
+            write(-128, 2)
+        }
+    }
+
+    @Test
+    fun writeLong() {
+        buildJcePacket {
+            writeLong(1, 1)
+            writeLong(-128, 2)
+        } shouldEqualTo qqJce {
+            write(1L, 1)
+            write(-128L, 2)
+        }
+    }
+
+    @Test
+    fun writeShort() {
+        buildJcePacket {
+            writeShort(1, 1)
+            writeShort(-128, 2)
+        } shouldEqualTo qqJce {
+            write(1.toShort(), 1)
+            write((-128).toShort(), 2)
+        }
+    }
+
+    @Test
+    fun writeBoolean() {
+        buildJcePacket {
+            writeBoolean(true, 1)
+            writeBoolean(false, 2)
+        } shouldEqualTo qqJce {
+            write(true, 1)
+            write(false, 2)
+        }
+    }
+
+    @Test
+    fun writeString() {
+        buildJcePacket {
+            writeString("1", 1)
+            writeString("哈啊", 2)
+        } shouldEqualTo qqJce {
+            write("1", 1)
+            write("哈啊", 2)
+        }
+    }
+
+    @Test
+    fun writeMap() {
+        buildJcePacket {
+            writeMap(mapOf("" to ""), 1)
+            writeMap(mapOf("" to 123), 2)
+            writeMap(mapOf(123.0 to "Hello"), 3)
+        } shouldEqualTo qqJce {
+            write(mapOf("" to ""), 1)
+            write(mapOf("" to 123), 2)
+            write(mapOf(123.0 to "Hello"), 3)
+        }
+    }
+
+    @Test
+    fun writeCollection() {
+        buildJcePacket {
+            writeMap(mapOf("" to ""), 1)
+            writeMap(mapOf("" to 123), 2)
+            writeMap(mapOf(123.0 to "Hello"), 3)
+        } shouldEqualTo qqJce {
+            write(mapOf("" to ""), 1)
+            write(mapOf("" to 123), 2)
+            write(mapOf(123.0 to "Hello"), 3)
+        }
+    }
+
+    data class TestMiraiStruct(
+        val message: String
+    ) : net.mamoe.mirai.qqandroid.network.io.JceStruct() {
+        override fun writeTo(builder: JceOutput) {
+            builder.writeString(message, 0)
+        }
+
+        companion object : Factory<TestMiraiStruct> {
+            override fun newInstanceFrom(input: JceInput): TestMiraiStruct {
+                return TestMiraiStruct(input.readString(0))
+            }
+        }
+    }
+
+    class TestQQStruct(
+        private var message: String
+    ) : JceStruct() {
+        override fun readFrom(var1: JceInputStream) {
+            message = var1.read("", 0, true)
+        }
+
+        override fun writeTo(var1: JceOutputStream) {
+            var1.write(message, 0)
+        }
+    }
+
+    @Test
+    fun writeJceStruct() {
+        buildJcePacket {
+            writeJceStruct(TestMiraiStruct("Hello"), 0)
+            writeJceStruct(TestMiraiStruct("嗨"), 1)
+        } shouldEqualTo qqJce {
+            write(TestQQStruct("Hello"), 0)
+            write(TestQQStruct("嗨"), 1)
+        }
+    }
+
+    @Test
+    fun writeObject() {
+        buildJcePacket {
+            writeObject(0.toByte(), 1)
+            writeObject(0.toShort(), 2)
+            writeObject(0, 3)
+            writeObject(0L, 4)
+            writeObject(0f, 5)
+            writeObject(0.0, 6)
+            writeObject("hello", 7)
+            writeObject(TestMiraiStruct("Hello"), 8)
+        } shouldEqualTo qqJce {
+            write(0.toByte(), 1)
+            write(0.toShort(), 2)
+            write(0, 3)
+            write(0L, 4)
+            write(0f, 5)
+            write(0.0, 6)
+            write("hello", 7)
+            write(TestQQStruct("Hello"), 8)
+        }
+    }
+}
\ No newline at end of file