vL64 Decoder Diary.
Tiny intro:
vL64 is an encoding language made and used by Sulake on their MMORPG HabboHotel, it stands for (we assume) vLevel64 and works to encode numbers for easier reading.
I would like to thank: Nillus (Who although didn't help me directly but I used his decoder to work out all my codes), Dinnerbone, Dan, Jordy and everyone who has used and continues to use my decoder.)
Here we go...
So I started off with a list of a few basic values I got from Nillus' decoder:
-7 = TB
-6 = WA
-5 = VA
-4 = UA
-3 = TA
-3 = O
-2 = N
-1 = M
0 = H
1 = I
2 = J
3 = K
4 = PA
5 = QA
6 = RA
7 = SA
8 = PB
9 = QB
10 = RB
11 = SB
12 = PC
13 = QC
I already knew that it looped P, Q, R, S and incremented the second letter on each loop so:
04=PA, 08=PB, 12=PC,
05=QA, 09=QB, 13=QC,
06=RA, 10=RB, 14=RC,
07=SA, 11=SB, 15=SC... etc etc.
- At this point I assumed I'd have to do a var for each letter -
With this knowledge I added 26*4 (26 numbers in the alphabet*4letters in the loop) which is equal to 104
Then added it to the original number (04) to make 108
Then (Just to check) I did 108-2 = 106 (This should therefore be (Loop3)Z if my logic was right) it equalled RZ (Aka (Loop3)Z) GO ME!
And 107=SZ so... what was next? Well (Loop1)[ was next (Aka "P[") followed by (Loop2)[ etc etc...
I did this with each 4 numbers cheating this list:
108 = P[
112 = P\
116 = P]
120 = P^
124 = P_
128 = P`
132 = Pa
136 = Pb
(I took a guess that this'd go through the alphabet in lower case... which indeed it did...)
232 = Pz
236 = P{
240 = P|
244 = P}
248 = P~
252 = P
- At this point a var for each letter looked REALLY painful... -
Now at this point I gave a blank look at the last number which at first site I assumed was "P" which didn't really make sense
It was also followed by:
253 = Q
254 = R
255 = S
Which had me stumped until I copy and pasted the data into notepad, Wha-La! It came up as "P " meaning there was an Invisible Chr!!!
Anyway, I ignored this (It later becomes REALLY important) and carried on.
256 = [email protected] (Now... WTF?! I thought)
257 = [email protected]
258 = [email protected]
259 = [@A
260 = XAA
261 = YAA
262 = ZAA
263 = [AA
264 = XBA
265 = YBA
So... now vaguely seeing the change: (Loop4)(Increment1)(Increment2) - I assumed it'd be Increment2 at least, and I was right.
I made a few jumps:
1000 = XzC
10000 = XDg
100000 = `hFF
1000000 = `PB}
10000000 = h`VbI
100000000 = [email protected]_A
1000000000 = [email protected]
At this point I noticed adding another "0" at the end of the string created an error... so the coding has an end number!
So I tried "2000000000", Still worked
Tried "3000000000", didn't work...
I know the first number is "2" so...
Does "2100000000" work? Yes.
Does "2200000000" work? No.
etc, etc...
Finishing number was "2147483647" - A bloody bizarre number.
So I made this list:
2147483643 = s~ _
2147483644 = p _
2147483645 = q _
2147483646 = r _
2147483647 = s _
Right, the next step I made was to bother Dinnerbone and Dan about what the Chr(?) that came up as blank was...
I also (As it was a side project at the time) knew B64 used the same numbers in the same order...
Anyway, Dan suggested Chr(0) aka NULL which didn't work,
Dinner suggested Chr(64) aka @ which... didn't work *Gives a blank look at Dinner*
He also suggested Chr(128) which... isn't an ASCii character... *Gives him another blank look*
He then linked me to an ASCii table which proved his accidental-brilliance!
Why?
Well I noticed:
Chr(123) = {
Chr(124) = |
Chr(125) = }
Chr(126) = ~
Chr(127) = DEL (A blank Chr)
And back in my string the last five Chrs were:
236 = P{
240 = P|
244 = P}
248 = P~
252 = P
A quick test confirmed that "P " was in fact == "P+Chr(127)" (aka PDEL)
YAY! And knowing that it increments in ASCii values means no hundreds of Vars!!!
SO! Time to write my first Structured English example of some code (I am writing to decrypt and am also assuming the user put in a string of VL64 code):
If (next letter == H) {Return 0}
If (next letter == I) {Return 1}
If (next letter == J) {Return 2}
If (next letter == K) {Return 3}
If (next letter == P) {1 + (4 * ASCii(Next Letter) - 257}
If (next letter == Q) {2 + (4 * ASCii(Next Letter) - 257}
If (next letter == R) {3 + (4 * ASCii(Next Letter) - 257}
If (next letter == S) {4 + (4 * ASCii(Next Letter) - 257}
Then upgraded it slightly to loose the pluses:
If (next letter == H) {Return 0}
If (next letter == I) {Return 1}
If (next letter == J) {Return 2}
If (next letter == K) {Return 3}
If (next letter == P) {(4 * ASCii(Next Letter) - 256}
If (next letter == Q) {(4 * ASCii(Next Letter) - 255}
If (next letter == R) {(4 * ASCii(Next Letter) - 254}
If (next letter == S) {(4 * ASCii(Next Letter) - 253}
Here's a dry run of the code:
Input "HPARSIJ"
If (next letter == H) {Return 0}
Run: H = 0
Result: 0 & PARSIJ
If (next letter == P) {(4 * ASCii(Next Letter) - 256}
Run: (4*ASCii(A aka 65) = 260 - 256 = 4
Result: 0 & 4 & RSIJ
If (next letter == R) {(4 * ASCii(Next Letter) - 254}
Run: (4*ASCii(S aka 83) = 332 - 254 = 78
Result: 0 & 4 & 78 & IJ
If (next letter == I) {Return 1}
Run: I = 1
Result: 0 & 4 & 78 & 1 & J
If (next letter == J) {Return 2}
Run: J = 2
Result: 0 & 4 & 78 & 1 & 2
Final Result: 0, 4, 78, 1, 2
Then I reformed the code to be:
If(Next Letter = P or Q or R or S)
Return ((ASCii of P/Q/R/S) + 4 * ASCii(Next Letter) - 256 - 80)
So... now how do I deal with numbers over 255 (SDEL) ?
Well first I took a step back and tried to figure out why the Loop letters are what they are...
This one took some thinking till a suddenly light flickered why I looked at it this way:
Loop[0]
H == Chr(72)
I == Chr(73)
J == Chr(74)
K == Chr(75)
Loop[1]
P == Chr(80)
Q == Chr(81)
R == Chr(82)
S == Chr(83)
Loop[2]
X == Chr(88)
Y == Chr(89)
Z == Chr(90)
[ == Chr(91)
So... I was looking at those and thought to myself "Hmm... what the differences... well... +5 leaps between ASCii numbers... wait... WAIT! WHAT?!"
Okay... so it's missing four characters (I'm proud at myself with my next thought)
"Those can't be... the... negative numbers can they?"
Loop[-1]
<>
M == Chr(77)
N == Chr(78)
O == Chr(79)
Loop[-2]
T == Chr(84)
U == Chr(85)
V == Chr(86)
W == Chr(87)
OMFG I'm a genius, I know, I know... although that'd make "L" == "0" and "L" doesn't exist... in fact... "L" is invalid which means it automatically is equal to "0"
So... apart from their being two "0"s it's utterly sound.
So! Time for my next bit of program-lang! (Note: I'm not going to do the whole bloody thing yet, just a revised version!)
If (Next Letter = H or I or J or K) {Return ASCii Of It - 72}
If (Next Letter = L or M or N or O) {Return 76 - ASCii Of It}
If (Next Letter = P or Q or R or S) {Return (ASCii Of It + (4 * ASCii Following Letter) - 256 - 80)} // Following Letter being next in string
If (Next Letter = T or U or V or W) {Return (84 + 256 - (4 * ASCii Following Letter) + ASCii Of It)}
So... The next step is adding a kind of +4 -4 system to the LoopChr.
***
I then took a few days break to do some actual work...
***
The next time I thought about the code was when Jordy (JorZs) alerted me to the fact "TFHA X" had a VL64 decoder/encoder in it, he sent me the following code:
VLD (VL64 Decoder):
VL64 (VL64 Encoder):
After a short look, I decided it shouldn't work... I asked him to do a few test numbers:
--2147483649
-2147483648
-2147483647
-2147483646
2147483647
2147483648
To see how it coped - answer - it didn't.
Next I got him to test more simple numbers:
255
256
Its answer for 255 was "S" aka S+Chr(127) which is correct...
Its answer for 256 was "P�" aka P+Chr(128) which is UNICODE as opposed to ASCii code (Which VL64 works in)
So... basically, it doesn't work with the switch from the first loop (P,Q,R,S) To the second loop (X,Y,Z,[) ... and I'm gona assume not for the negative Loops either.
So, next up let's make a table of all the first Chr values and the number they start at:
HIJK = 0 1 2 3
LMNO = 0 -1 -2 -3
PQRS = 4 5 6 7
TUVW = -4 -5 -6 -7
XYZ[ = 256 257 258 259
\]^_ = -256 -257 -258 -259
`abc = 16384 16385 16386 16387
defg = -16384 -16385 -16386 -16387
hijk = 1048576 1048577 1048578 1048579
lmno = -1048576 -1048577 -1048578 -1048579
pqrs = 67108864 67108865 67108866 67108867
tuvw = -67108864 -67108865 -67108866 -67108867
Right, now I can make a fully working VL64 converter...
First I did it to work with the first few chrs (From Chr(72) till Chr(87)) This is what I made:
What it does is checks where the first letter is, if it's a H/I/J/K/L/M/N/O it'll just do Chr-72/76-Chr (Depending on if it's a negative or positive number)
And if it's a P/Q/R/S/T/U/V/W it will check the next number in the string (i+1) and then do Chr-256-80/(256+84)-Chr
Then it adds 1 to i (i = counter) and restarts the loop.
This obviously isn't very effective as it repeats the second number of the string, for example "PAHI" would become: "4 & A & 0 & 1" because it'll check the A twice...
Anyway... I'm not interested in the first -255 to 255 chrs, I want them all to work!
So... I first devised a way to not have two different if's for +/- by making the script have a simple if >= number make negative...
Tada... so anyway... on with making lots and lots a code structures...
Oh also... I noticed the Loops do this:
Loop1 // This loop goes from chr(72) to chr(127)
Loop2 // This loop goes from chr(65) to chr(127)
Loop3+ // This loop goes from chr(64) to chr(127)
but it does this:
(L1)(L2)
(L1)(L3)(L2)
(L1)(L3)(L4)(L2)
(L1)(L3)(L4)(L5)(L2)
etc...
so... cher... makes it more annoying slightly... so, I added some more thought into my code...
(I soon realised after that it was only "A" because "@" was equal to 0 so [email protected] and [email protected]@ etc would just be equal to 0 so they do (probably) actually exsist as codes.)
I added a "LetterAdjuster" this basically sets each Loop back down to zero as for example:
PA: P = 0 A = 4 so PA = 4
QA: Q = 1 A = 4 so QA = 5
[email protected]: X = 0 @ = 0 A = 256 so [email protected] = 256
XAA: X = 0 A = 4 A = 256 so [email protected] = 260
Get the idea? It relates to the table I gave earlier...
ANYWAY! I also decided to add it into a function to be neater and (because I forgot to last time D;) added a Position Counter that increments by use of ++ (inputStringPosition)... basically this is the code I made:
Which happens to work perfectly... (A good way to check if a vL64 decoder works is by using the string "IRBPYXzCXDg`hFF`PB}h`[email protected][email protected]", it should output "1 10 100 1000 10000 100000 1000000 10000000 100000000 1000000000")
Now... as far as I know of, everyone else stops at this point, I, however decided to carry on and make it recursive...
This is my final code, the one that I released earlier this month (19/03/09):
Now, how does this work?
Well, basically back to simplistics...
I know that Loop1 (using my cog-analogy) Loops four characters,
I know that Loop2 Loops 127-64 characters (I know it starts at A but this is only because @ = 0 and [email protected] would them be = 0 so it probably does start at [email protected] just it won't show up as H is the main 0, like L doesn't show up either.)
I know that Loop3 Loops 127-64 characters
I know that Loop4 Loops 127-64 characters
etc...
Well, let's work out a simple rule for them to start off with:
Loop1 = ASCii - 72 (ie. 64 - 8)
Loop2 = ASCii - 80 (ie. 64 - 8 - 8)
Loop3 = ASCii - 88 (ie. 64 - 8 - 8 - 8)
See a pattern? So... let's create an nth term!
Loop1 - 64 - (Length * 8)
(Length is the Length of the code snippet like [email protected] is PA(Length:2)& QA(Length:2)& [email protected](Length:3)
And then the other loops revolve around the number 16:
Loop1 4*(1) (So I = 1)
Loop2 4*(16[1]) (So PA should = 16 but for some reason it's 4 instead... which I'm sure is significant because 4*4 is of course 16...)
Loop3 4*(16[2]) (So [email protected] = 256)
Loop4 4*(16[3]*4) (So `@@A = 16384)
Loop5 4*(16[4]*4*4) (So [email protected]@@A = 1048576)
Loop6 4*(16[5]*4*4*4) (so [email protected]@@@A = 67108864)
So... basically I looked at that blankly and then made my code based on it...
By that I mean I know it all looks logical... and it all revolves around the numbers 4, 16, 64, 256... (4, miss out 8, 16, miss out 32, 64, miss out 126, 256...) But... I have no idea of the REAL logic behind it...
But... my method works so what the hell? We'll leave it at that...
Oh... By the way, as I write this there is NO! public recursion methods for VL64 decoding, even Nillus' method stops at 2147483647 so I assume it uses my original long hand method... my method has NO limit...
Oh... and also, I realised as I wrote this out there is very likely to be TWO simpler ways, one that's like mine but the negative/positive in one line...
The other, which is possibly MUCH simpler and more effective (heart breaking for me D;) would use loops...
And that'd be my vL64 diary... I doubt anyone will ever read this but it's nice to have a record of what I've achieved and how... Sorry it was late by the way, I was trying to work out the logic behind it and then just decided I couldn't and put it to one side... Hah! Anyway, till next time!
UPDATE: So I did actually work out the loop way and update everything as it appears simpler and more efficient - this is it's code
It's pretty simple to understand... I think?... Anyway, no more vL64 decoding for me! I've done it to death!
- Alex (Shenk).