- Habbo Development Wiki - Communication - Encoding

FUSE 0.2.0 makes use of two number encoding methods:

The first one makes use of ASCII characters in a Base64 format.

The second one combines the use of Base4 with Base64, also represented in ASCII.



Base64

Base64 is an encoding scheme which makes use of radix-64 values.

It works the same way as radix-10 numbers (0-9), but instead makes use of ASCII characters values to go from 0 to 63.

Its encoding length must be previously known, hence Base64-encoded numbers use @ (0 in Base4, 64 in ASCII due to its +64 offset) as padding.


Decoding

ASCII characters numeric values have an offset of +64, so in order to obtain the true value of a single byte, 64 must be substracted to it. The rest of it works the same way as radix-10 numbers do.

Java example:

public static int decode(char[] input) {
    int output = 0;
    int exp = input.length - 1;
   
    for (int i = 0; i < input.length; i++) {
        output += ((int)input[i] % 64) * (int)Math.pow(64, exp);
        exp--;
    }
   
    return output;
}


Encoding

Opposite of encoding, 64 must be added to each byte to get its ASCII counterpart.

The following axample encodes from a given radix-10 number to Base64 in a fixed length of 2 bytes, using @ as padding.

Java example:

public static String encode(int input) {
    String output = Character.toString((char)(input / 64 + 64)) + (char)(input % 64 + 64);
    return output;
}


VL64 (Wire)

VL64, also known as Wire, is a variable length encoding scheme which makes use of a mix of radix-4 and radix-64 values.

Base64 numbers must be understood in order to comprehend VL64.


Obtaining length

Unlike Base64, VL64 length is not known in advance. The first byte is read to know the total size in bytes/characters of the encoded number, including this first byte in the length.

Chars Length Min value Max value
@ A B C 0 0 0
D E F G 0 0 0
H I J K 1 0 3
L M N O 1 -3 0
P Q R S 2 4 255
T U V W 2 -255 -4
X Y Z [ 3 256 16383
\ ] ^ _ 3 -16383 -256
` a b c 4 16384 1048575
d e f g 4 -1048575 -16384
h i j k 5 1048576 67108863
l m n o 5 -67108863 -1048576
p q r s 6 67108864 4294967295
t u v w 6 -4294967295 -67108864
x y z { 7 4294967296 274877906944
| } ~ DEL 7 -274877906944 -4294967296

Even rows represent positive numbers and odd rows are negative, as indicated by the min and max values.

The following method gets first byte in the VL64 as parameter, substracts 64 from it to get obtain Base64 value and then divides by 8 to get its length, like indicated in the table above.

Java example:

public static int getLength(byte input) {
    return (input - 64) / 8;
}


Decoding

The first byte is not only used to indicate length, but is also a Base4-encoded number being part of the total value, being the value of each number in the first column of the table above 0, 1, 2 and 3, respectively, also applying ASCII +64 offset.

However, at the time of calculating the total value of the encoded number, this character does not act at the front of the value as it might seem logical, but it gets moved to the end of the encoded number.

Java example:

public static int decode(byte[] input) {
    int output = input[0] & 3;
    int length = (input[0] - 64) / 8;
   
    for (int i = 0; i < length; i++) {
        output += ((int)input[i] - 64) * ((int)Math.pow(64, i) / 16);
    }
   
    return (input[0] & 7) < 4 ? output : -output;
}


Encoding

Encoding process makes the opposite of decoding; the first Base4 number is calculated at the start, then followed by the Base64 numbers.

Finally, it gets cropped to its appropiate length depending on the number of calculated Base64 numbers.

Java example:

public static byte[] encode(int input) {
    int posInput = Math.abs(input);
    int length = 1;
    byte[] output = new byte[6];
   
    output[0] = (byte)(posInput & 3 + (input >= 0 ? 64 : 68));
    posInput /= 4;
   
    for (int i = 1; i <= 5; i++) {
        output[i] = (byte)(posInput % 64 + 64);
        posInput /= 64;
        if (output[i] != 64) length = i + 1;
    }
   
    output[0] += (byte)(length * 8);
    return new String(Arrays.copyOf(output, length)).getBytes();
}