I've translated the war3 cd key decoder from mbncsutil into vb.net. But I can't seem to get correct results. The compiled library works fine, so I know I've made an error somewhere. I don't have access to a C# interpreter so I can't see what values should be half way through the computation. This is a learning experience for me, so I'd like to figure out what I'm doing wrong. It may be a blind oversight on my part, or some obscure difference between vb and c#.
I did manage to totally rewrite the decodeKeyTable method into something legible, so that's something.
I'm not quite sure what my question is, I just want general help. If someone could tell me the true values of srcData before and after the //mult block for a known cd key, that would be very helpful.
I am not going to pretend to know exactly how to help you, but you will need to show some code and how you are testing it to receive any help.
Sure thing, I just didn't know if it was appropriate to post so much code so soon.
Here is the relevant code:
'in a module
'easy ways to create arrays
Public Function bunch(Of x)(ByVal ParamArray xx As x()) As x()
Return xx
End Function
Public Function bytes(ByVal ParamArray bb As Byte()) As Byte()
Return bb
End Function
'in the decoding class
'30 permutations of 0-15 (still the same map, just divided in chunks of 16)
Private Shared w3TranslateMap As Byte()() = bunch( _
bytes(&H9, &H4, &H7, &HF, &HD, &HA, &H3, &HB, &H1, &H2, &HC, &H8, &H6, &HE, &H5, &H0), _
bytes(&H9, &HB, &H5, &H4, &H8, &HF, &H1, &HE, &H7, &H0, &H3, &H2, &HA, &H6, &HD, &HC), _
bytes(&HC, &HE, &H1, &H4, &H9, &HF, &HA, &HB, &HD, &H6, &H0, &H8, &H7, &H2, &H5, &H3), _
bytes(&HB, &H2, &H5, &HE, &HD, &H3, &H9, &H0, &H1, &HF, &H7, &HC, &HA, &H6, &H4, &H8), _
bytes(&H6, &H2, &H4, &H5, &HB, &H8, &HC, &HE, &HD, &HF, &H7, &H1, &HA, &H0, &H3, &H9), _
bytes(&H5, &H4, &HE, &HC, &H7, &H6, &HD, &HA, &HF, &H2, &H9, &H1, &H0, &HB, &H8, &H3), _
bytes(&HC, &H7, &H8, &HF, &HB, &H0, &H5, &H9, &HD, &HA, &H6, &HE, &H2, &H4, &H3, &H1), _
bytes(&H3, &HA, &HE, &H8, &H1, &HB, &H5, &H4, &H2, &HF, &HD, &HC, &H6, &H7, &H9, &H0), _
bytes(&HC, &HD, &H1, &HF, &H8, &HE, &H5, &HB, &H3, &HA, &H9, &H0, &H7, &H2, &H4, &H6), _
bytes(&HD, &HA, &H7, &HE, &H1, &H6, &HB, &H8, &HF, &HC, &H5, &H2, &H3, &H0, &H4, &H9), _
bytes(&H3, &HE, &H7, &H5, &HB, &HF, &H8, &HC, &H1, &HA, &H4, &HD, &H0, &H6, &H9, &H2), _
bytes(&HB, &H6, &H9, &H4, &H1, &H8, &HA, &HD, &H7, &HE, &H0, &HC, &HF, &H2, &H3, &H5), _
bytes(&HC, &H7, &H8, &HD, &H3, &HB, &H0, &HE, &H6, &HF, &H9, &H4, &HA, &H1, &H5, &H2), _
bytes(&HC, &H6, &HD, &H9, &HB, &H0, &H1, &H2, &HF, &H7, &H3, &H4, &HA, &HE, &H8, &H5), _
bytes(&H3, &H6, &H1, &H5, &HB, &HC, &H8, &H0, &HF, &HE, &H9, &H4, &H7, &HA, &HD, &H2), _
bytes(&HA, &H7, &HB, &HF, &H2, &H8, &H0, &HD, &HE, &HC, &H1, &H6, &H9, &H3, &H5, &H4), _
bytes(&HA, &HB, &HD, &H4, &H3, &H8, &H5, &H9, &H1, &H0, &HF, &HC, &H7, &HE, &H2, &H6), _
bytes(&HB, &H4, &HD, &HF, &H1, &H6, &H3, &HE, &H7, &HA, &HC, &H8, &H9, &H2, &H5, &H0), _
bytes(&H9, &H6, &H7, &H0, &H1, &HA, &HD, &H2, &H3, &HE, &HF, &HC, &H5, &HB, &H4, &H8), _
bytes(&HD, &HE, &H5, &H6, &H1, &H9, &H8, &HC, &H2, &HF, &H3, &H7, &HB, &H4, &H0, &HA), _
bytes(&H9, &HF, &H4, &H0, &H1, &H6, &HA, &HE, &H2, &H3, &H7, &HD, &H5, &HB, &H8, &HC), _
bytes(&H3, &HE, &H1, &HA, &H2, &HC, &H8, &H4, &HB, &H7, &HD, &H0, &HF, &H6, &H9, &H5), _
bytes(&H7, &H2, &HC, &H6, &HA, &H8, &HB, &H0, &HF, &H4, &H3, &HE, &H9, &H1, &HD, &H5), _
bytes(&HC, &H4, &H5, &H9, &HA, &H2, &H8, &HD, &H3, &HF, &H1, &HE, &H6, &H7, &HB, &H0), _
bytes(&HA, &H8, &HE, &HD, &H9, &HF, &H3, &H0, &H4, &H6, &H1, &HC, &H7, &HB, &H2, &H5), _
bytes(&H3, &HC, &H4, &HA, &H2, &HF, &HD, &HE, &H7, &H0, &H5, &H8, &H1, &H6, &HB, &H9), _
bytes(&HA, &HC, &H1, &H0, &H9, &HE, &HD, &HB, &H3, &H7, &HF, &H8, &H5, &H2, &H4, &H6), _
bytes(&HE, &HA, &H1, &H8, &H7, &H6, &H5, &HC, &H2, &HF, &H0, &HD, &H3, &HB, &H4, &H9), _
bytes(&H3, &H8, &HE, &H0, &H7, &H9, &HF, &HC, &H1, &H6, &HD, &H2, &H5, &HA, &HB, &H4), _
bytes(&H3, &HA, &HC, &H4, &HD, &HB, &H9, &HE, &HF, &H6, &H1, &H7, &H2, &H0, &H5, &H8) _
)
'Returns the product, public and private keys of a cd key
Public Shared Function processWC3Key(ByVal key As String) As Byte()()
Const W3_KEYLEN As Integer = 26
Const W3_BUFLEN As Integer = W3_KEYLEN * 2
If key.Length <> W3_KEYLEN Then Throw New Exception("Invalid cd key length.")
Dim table(0 To W3_BUFLEN - 1) As Byte
Dim cdkey As Char() = key.ToUpper().ToCharArray()
'Fill table with permuted cd key
Dim a As Integer = 0, b As Integer = &H21
For i As Integer = 0 To key.Length - 1
a = (b + &H7B5) Mod W3_BUFLEN
b = (a + &H7B5) Mod W3_BUFLEN
Dim c As Byte = w3KeyMap(Asc(cdkey(i))) '0 to 25
If c = 255 Then Throw New Exception("Invalid cd key character.")
table(a) = CByte(c / 5)
table(b) = CByte(c Mod 5)
Next i
'Compute table values
'(this section is very likely incorrect)
Dim values(0 To 3) As UInteger
Dim n As BigNum = New BigNum(0)
For i As Integer = W3_BUFLEN - 1 To 0 Step -1
n += table(i)
n *= 5
ReDim Preserve n.bytes(0 To 15)
Next i
Dim bbb()() As Byte = chopBytes(n.bytes, 4, 4, 4, 4)
For i As Integer = 0 To 3
values(i) = CUInt(unpackLong(bbb(i)))
Next i
decodeKeyTable(values)
Dim bbProduct() As Byte = packLong(CLng(values(0) >> &HA))
Dim bbPublic() As Byte = reverseBytes(packLong(values(0) And &HFFFFFF00))
Dim bbPrivate() As Byte = packBytes(packLong(values(1) And &HFFFFFF, 3), packLong(values(2)), packLong(values(3) >> 2, 3))
Return bunch(bbProduct, bbPublic, bbPrivate)
End Function
'Almost totally rewritten. Equivalent to an almost word-for-word translation
Private Shared Sub decodeKeyTable(ByVal keyTable As UInteger())
Dim keys(0 To 29) As Integer, shifts(0 To 29) As Integer
'Pre-compute key indices and shifts for each row for the first pass
For r As Integer = 0 To 29
'note that 0-29 use only 5 bits
keys(r) = 3 - (r >> 3) '0 to 3; negation of the 2 high bits of row
shifts(r) = (r And &H7) << 2 '0 to 28; 4x the 3 low bits of row
Next r
'First pass
For r As Integer = 29 To 0 Step -1
Dim perm() As Byte = w3TranslateMap(r)
Dim c As Integer = CInt((keyTable(keys(r)) >> shifts(r)) And &HF) '0 to 15; pick 4 adjacent bits of keyTable(keys(r))
'Jump around the w3TranslateMap columns a bit
For r2 As Integer = 29 To 0 Step -1
If r = r2 Then Continue For
Dim c2 As Integer = CInt((keyTable(keys(r2)) >> shifts(r2)) And &HF) '0 to 15; pick 4 adjacent bits
c = perm(c2 Xor perm(c)) '0 to 15; permute column
Next r2
'Replace the 4 adjacent bits we picked in keyTable(keys(r)) with the 4 bits of perm(c)
keyTable(keys(r)) = CUInt(keyTable(keys(r)) And Not (&HF << shifts(r)))
keyTable(keys(r)) = CUInt(keyTable(keys(r)) Or (CUInt(perm(c)) << shifts(r)))
Next r
'Store the keyTable so far
Dim keyTableFirstPass(0 To 3) As UInteger
keyTable.CopyTo(keyTableFirstPass, 0)
'Second pass
For i As Integer = 0 To 119
'note that 0-119 use only 7 bits
Dim j As Integer = (i * 11) Mod 120
Dim iKey As Integer = 3 - (i >> 5) '0 to 3; the 2 high bits of i inverted
Dim jKey As Integer = 3 - (j >> 5) '0 to 3; the 2 high bits of j inverted
Dim iShift As Integer = i And &H1F '0 to 31; the 5 low bits of i
Dim jShift As Integer = j And &H1F '0 to 31; the 5 low bits of j
'replace the iShift'th bit of keyTable(iKey) with the jShift'th bit of keyTableFirstPass(jKey)
If ((keyTableFirstPass(jKey) >> jShift) And &H1) = 0 Then
keyTable(iKey) = CUInt(keyTable(iKey) And Not (CUInt(&H1) << iShift)) 'unset
Else
keyTable(iKey) = CUInt(keyTable(iKey) Or (CUInt(&H1) << iShift)) 'set
End If
Next i
End Sub
The pack and unpack functions just convert numbers to and from arrays of bytes. I'm testing it by running it in the visual studio interpreter and comparing the public/product keys to the known values I sniffed using wireshark.
I've solved the problem. I needed to switch endian-ness before the multiplication step, among other small things. Now I can see that the CD Key is shuffled into a base 5 number, then converted to base 2, then some cryptography stuff, then the keys come right out.
As another update, I've massively improved on the code originally provided. It is now trivial to reverse the process and generate a cd key from given public/private/product keys (of course such keys won't necessarily work on BNET because not all public/private key pairs are valid).
''' <summary>Map from cd key characters to their values (invalid characters go to 0xFF)</summary>
''' <remarks>Only 25 characters from the possible 36</remarks>
Private Shared ReadOnly w3KeyMap As Byte() = { _
&HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, _
&HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, _
&HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, _
&HFF, &HFF, &H0, &HFF, &H1, &HFF, &H2, &H3, &H4, &H5, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, _
&HFF, &HFF, &H6, &H7, &H8, &H9, &HA, &HB, &HC, &HFF, &HD, &HE, &HFF, &HF, &H10, &HFF, _
&H11, &HFF, &H12, &HFF, &H13, &HFF, &H14, &H15, &H16, &H17, &H18, &HFF, &HFF, &HFF, &HFF, &HFF, _
&HFF, &HFF, &H6, &H7, &H8, &H9, &HA, &HB, &HC, &HFF, &HD, &HE, &HFF, &HF, &H10, &HFF, _
&H11, &HFF, &H12, &HFF, &H13, &HFF, &H14, &H15, &H16, &H17, &H18, &HFF, &HFF, &HFF, &HFF, &HFF, _
&HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, _
&HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, _
&HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, _
&HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, _
&HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, _
&HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, _
&HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, _
&HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF, &HFF _
}
''' <summary>30 permutations of 0-15</summary>
Private Shared ReadOnly w3TranslateMap As Byte()() = { _
array(Of Byte)(&H9, &H4, &H7, &HF, &HD, &HA, &H3, &HB, &H1, &H2, &HC, &H8, &H6, &HE, &H5, &H0), _
array(Of Byte)(&H9, &HB, &H5, &H4, &H8, &HF, &H1, &HE, &H7, &H0, &H3, &H2, &HA, &H6, &HD, &HC), _
array(Of Byte)(&HC, &HE, &H1, &H4, &H9, &HF, &HA, &HB, &HD, &H6, &H0, &H8, &H7, &H2, &H5, &H3), _
array(Of Byte)(&HB, &H2, &H5, &HE, &HD, &H3, &H9, &H0, &H1, &HF, &H7, &HC, &HA, &H6, &H4, &H8), _
array(Of Byte)(&H6, &H2, &H4, &H5, &HB, &H8, &HC, &HE, &HD, &HF, &H7, &H1, &HA, &H0, &H3, &H9), _
array(Of Byte)(&H5, &H4, &HE, &HC, &H7, &H6, &HD, &HA, &HF, &H2, &H9, &H1, &H0, &HB, &H8, &H3), _
array(Of Byte)(&HC, &H7, &H8, &HF, &HB, &H0, &H5, &H9, &HD, &HA, &H6, &HE, &H2, &H4, &H3, &H1), _
array(Of Byte)(&H3, &HA, &HE, &H8, &H1, &HB, &H5, &H4, &H2, &HF, &HD, &HC, &H6, &H7, &H9, &H0), _
array(Of Byte)(&HC, &HD, &H1, &HF, &H8, &HE, &H5, &HB, &H3, &HA, &H9, &H0, &H7, &H2, &H4, &H6), _
array(Of Byte)(&HD, &HA, &H7, &HE, &H1, &H6, &HB, &H8, &HF, &HC, &H5, &H2, &H3, &H0, &H4, &H9), _
array(Of Byte)(&H3, &HE, &H7, &H5, &HB, &HF, &H8, &HC, &H1, &HA, &H4, &HD, &H0, &H6, &H9, &H2), _
array(Of Byte)(&HB, &H6, &H9, &H4, &H1, &H8, &HA, &HD, &H7, &HE, &H0, &HC, &HF, &H2, &H3, &H5), _
array(Of Byte)(&HC, &H7, &H8, &HD, &H3, &HB, &H0, &HE, &H6, &HF, &H9, &H4, &HA, &H1, &H5, &H2), _
array(Of Byte)(&HC, &H6, &HD, &H9, &HB, &H0, &H1, &H2, &HF, &H7, &H3, &H4, &HA, &HE, &H8, &H5), _
array(Of Byte)(&H3, &H6, &H1, &H5, &HB, &HC, &H8, &H0, &HF, &HE, &H9, &H4, &H7, &HA, &HD, &H2), _
array(Of Byte)(&HA, &H7, &HB, &HF, &H2, &H8, &H0, &HD, &HE, &HC, &H1, &H6, &H9, &H3, &H5, &H4), _
array(Of Byte)(&HA, &HB, &HD, &H4, &H3, &H8, &H5, &H9, &H1, &H0, &HF, &HC, &H7, &HE, &H2, &H6), _
array(Of Byte)(&HB, &H4, &HD, &HF, &H1, &H6, &H3, &HE, &H7, &HA, &HC, &H8, &H9, &H2, &H5, &H0), _
array(Of Byte)(&H9, &H6, &H7, &H0, &H1, &HA, &HD, &H2, &H3, &HE, &HF, &HC, &H5, &HB, &H4, &H8), _
array(Of Byte)(&HD, &HE, &H5, &H6, &H1, &H9, &H8, &HC, &H2, &HF, &H3, &H7, &HB, &H4, &H0, &HA), _
array(Of Byte)(&H9, &HF, &H4, &H0, &H1, &H6, &HA, &HE, &H2, &H3, &H7, &HD, &H5, &HB, &H8, &HC), _
array(Of Byte)(&H3, &HE, &H1, &HA, &H2, &HC, &H8, &H4, &HB, &H7, &HD, &H0, &HF, &H6, &H9, &H5), _
array(Of Byte)(&H7, &H2, &HC, &H6, &HA, &H8, &HB, &H0, &HF, &H4, &H3, &HE, &H9, &H1, &HD, &H5), _
array(Of Byte)(&HC, &H4, &H5, &H9, &HA, &H2, &H8, &HD, &H3, &HF, &H1, &HE, &H6, &H7, &HB, &H0), _
array(Of Byte)(&HA, &H8, &HE, &HD, &H9, &HF, &H3, &H0, &H4, &H6, &H1, &HC, &H7, &HB, &H2, &H5), _
array(Of Byte)(&H3, &HC, &H4, &HA, &H2, &HF, &HD, &HE, &H7, &H0, &H5, &H8, &H1, &H6, &HB, &H9), _
array(Of Byte)(&HA, &HC, &H1, &H0, &H9, &HE, &HD, &HB, &H3, &H7, &HF, &H8, &H5, &H2, &H4, &H6), _
array(Of Byte)(&HE, &HA, &H1, &H8, &H7, &H6, &H5, &HC, &H2, &HF, &H0, &HD, &H3, &HB, &H4, &H9), _
array(Of Byte)(&H3, &H8, &HE, &H0, &H7, &H9, &HF, &HC, &H1, &H6, &HD, &H2, &H5, &HA, &HB, &H4), _
array(Of Byte)(&H3, &HA, &HC, &H4, &HD, &HB, &H9, &HE, &HF, &H6, &H1, &H7, &H2, &H0, &H5, &H8) _
}
''' <summary>Extracts the product, public and private keys from a wc3 cd key.</summary>
''' <param name="key">The cd key. Dashes and lower case characters are allowed.</param>
''' <returns>Byte arrays for each key: return.v1 = product key, return.v2 = public key, return.v3 = private key</returns>
''' <remarks>All arrays and numbers used are considered as big endian</remarks>
Public Shared Function decodeWC3Key(ByVal key As String) As Tuple(Of Byte(), Byte(), Byte())
Const W3_KEYLEN As Integer = 26
Const W3_BUFLEN As Integer = W3_KEYLEN * 2
If key.Length <> W3_KEYLEN Then Throw New ArgumentException("Invalid cd key length.")
Dim cdkey As Char() = key.ToUpper().Replace("-", "").ToCharArray()
'Shuffle the cd key into the digits of a base 5 number
Dim d As Integer = 33
Dim n_digitsBase5(0 To W3_BUFLEN - 1) As Byte
For i As Integer = 0 To W3_KEYLEN - 1
Dim c As Byte = w3KeyMap(Asc(cdkey(i))) 'will be from 0 to 24, or invalid (0xFF)
If c = &HFF Then Throw New ArgumentException("Invalid cd key character: " + cdkey(i))
'get the two base 5 digits of c
d = (d + 49) Mod W3_BUFLEN
n_digitsBase5(d) = CByte(c \ 5)
d = (d + 49) Mod W3_BUFLEN
n_digitsBase5(d) = CByte(c Mod 5)
Next i
'Convert the shuffled number to base 2
'Note that a 52 digit base 5 number needs at most 121 bits
Dim n As BigNum = 0
For i As Integer = W3_BUFLEN - 1 To 0 Step -1
n *= 5
n += n_digitsBase5(i)
Next i
Dim nbytes() As Byte = n.getBytes(16)
'Convert from bytes to nibbles
Dim nibbles(0 To 31) As Byte
For i As Integer = 0 To 15
For j As Integer = 0 To 1
nibbles((i << 1) + j) = CByte((nbytes(i) >> (j << 2)) And CUInt(&HF))
Next j
Next i
'Permute the nibbles using the translation map
For r As Integer = 29 To 0 Step -1
Dim perm() As Byte = w3TranslateMap(r)
Dim c As Byte = nibbles(r)
'Permute
For r2 As Integer = 29 To 0 Step -1
If r = r2 Then Continue For
c = perm(nibbles(r2) Xor perm(c))
Next r2
nibbles(r) = perm(c)
Next r
'Convert from nibbles to bits
Dim bits(0 To 127) As Boolean
For i As Integer = 0 To 31
For j As Integer = 0 To 3
bits(i * 4 + j) = CBool(((nibbles(i) >> j) And &H1) <> 0)
Next j
Next i
'Swap the bits with their *11 counterpart (mod 120)
'Note that x*11*11 = x*121 = x*1 = x (mod 120)
For i As Integer = 0 To 119
Dim j As Integer = (i * 11) Mod 120
If j <= i Then Continue For 'don't swap the same bits twice
'swap bits i and j
Dim b As Boolean = bits(i)
bits(i) = bits(j)
bits(j) = b
Next i
'Convert from bits to bytes
Dim bb(0 To 15) As Byte
For i As Integer = 0 To 15
For j As Integer = 0 To 7
If bits((i << 3) + j) Then bb(i) = bb(i) Or CByte(&H1 << j)
Next j
Next i
'Extract keys
Dim bbProduct() As Byte, bbPublic() As Byte, bbPrivate() As Byte
bbProduct = array(Of Byte)(bb(13) >> &H2, 0, 0, 0)
bbPublic = array(Of Byte)(bb(10), bb(11), bb(12), 0)
bbPrivate = array(Of Byte)(bb(8), bb(9), bb(4), bb(5), bb(6), bb(7), bb(0), bb(1), bb(2), bb(3))
Return New Tuple(Of Byte(), Byte(), Byte())(bbProduct, bbPublic, bbPrivate)
End Function