1
0
mirror of synced 2024-11-15 02:17:36 +01:00
bemaniutils/bemani/common/card.py

541 lines
20 KiB
Python
Raw Normal View History

from typing import List
class CardCipherException(Exception):
pass
class CardCipher:
"""
Algorithm for converting between the Card ID as stored in an
eAmusement card and the 16 character card string as shown on
the back of a card and in-game. All of this was kindly RE'd by
Tau and converted ham-fistedly to Python.
"""
KEY = [
0x20d0d03c, 0x868ecb41, 0xbcd89c84, 0x4c0e0d0d,
0x84fc30ac, 0x4cc1890e, 0xfc5418a4, 0x02c50f44,
0x68acb4e0, 0x06cd4a4e, 0xcc28906c, 0x4f0c8ac0,
0xb03ca468, 0x884ac7c4, 0x389490d8, 0xcf80c6c2,
0x58d87404, 0xc48ec444, 0xb4e83c50, 0x498d0147,
0x64f454c0, 0x4c4701c8, 0xec302cc4, 0xc6c949c1,
0xc84c00f0, 0xcdcc49cc, 0x883c5cf4, 0x8b0fcb80,
0x703cc0b0, 0xcb820a8d, 0x78804c8c, 0x4fca830e,
0x80d0f03c, 0x8ec84f8c, 0x98c89c4c, 0xc80d878f,
0x54bc949c, 0xc801c5ce, 0x749078dc, 0xc3c80d46,
0x2c8070f0, 0x0cce4dcf, 0x8c3874e4, 0x8d448ac3,
0x987cac70, 0xc0c20ac5, 0x288cfc78, 0xc28543c8,
0x4c8c7434, 0xc50e4f8d, 0x8468f4b4, 0xcb4a0307,
0x2854dc98, 0x48430b45, 0x6858fce8, 0x4681cd49,
0xd04808ec, 0x458d0fcb, 0xe0a48ce4, 0x880f8fce,
0x7434b8fc, 0xce080a8e, 0x5860fc6c, 0x46c886cc,
0xd01098a4, 0xce090b8c, 0x1044cc2c, 0x86898e0f,
0xd0809c3c, 0x4a05860f, 0x54b4f80c, 0x4008870e,
0x1480b88c, 0x0ac8854f, 0x1c9034cc, 0x08444c4e,
0x0cb83c64, 0x41c08cc6, 0x1c083460, 0xc0c603ce,
0x2ca0645c, 0x818246cb, 0x0408e454, 0xc5464487,
0x88607c18, 0xc1424187, 0x284c7c90, 0xc1030509,
0x40486c94, 0x4603494b, 0xe0404ce4, 0x4109094d,
0x60443ce4, 0x4c0b8b8d, 0xe054e8bc, 0x02008e89,
]
LUT_A0 = [
0x02080008, 0x02082000, 0x00002008, 0x00000000,
0x02002000, 0x00080008, 0x02080000, 0x02082008,
0x00000008, 0x02000000, 0x00082000, 0x00002008,
0x00082008, 0x02002008, 0x02000008, 0x02080000,
0x00002000, 0x00082008, 0x00080008, 0x02002000,
0x02082008, 0x02000008, 0x00000000, 0x00082000,
0x02000000, 0x00080000, 0x02002008, 0x02080008,
0x00080000, 0x00002000, 0x02082000, 0x00000008,
0x00080000, 0x00002000, 0x02000008, 0x02082008,
0x00002008, 0x02000000, 0x00000000, 0x00082000,
0x02080008, 0x02002008, 0x02002000, 0x00080008,
0x02082000, 0x00000008, 0x00080008, 0x02002000,
0x02082008, 0x00080000, 0x02080000, 0x02000008,
0x00082000, 0x00002008, 0x02002008, 0x02080000,
0x00000008, 0x02082000, 0x00082008, 0x00000000,
0x02000000, 0x02080008, 0x00002000, 0x00082008,
]
LUT_A1 = [
0x08000004, 0x00020004, 0x00000000, 0x08020200,
0x00020004, 0x00000200, 0x08000204, 0x00020000,
0x00000204, 0x08020204, 0x00020200, 0x08000000,
0x08000200, 0x08000004, 0x08020000, 0x00020204,
0x00020000, 0x08000204, 0x08020004, 0x00000000,
0x00000200, 0x00000004, 0x08020200, 0x08020004,
0x08020204, 0x08020000, 0x08000000, 0x00000204,
0x00000004, 0x00020200, 0x00020204, 0x08000200,
0x00000204, 0x08000000, 0x08000200, 0x00020204,
0x08020200, 0x00020004, 0x00000000, 0x08000200,
0x08000000, 0x00000200, 0x08020004, 0x00020000,
0x00020004, 0x08020204, 0x00020200, 0x00000004,
0x08020204, 0x00020200, 0x00020000, 0x08000204,
0x08000004, 0x08020000, 0x00020204, 0x00000000,
0x00000200, 0x08000004, 0x08000204, 0x08020200,
0x08020000, 0x00000204, 0x00000004, 0x08020004,
]
LUT_A2 = [
0x80040100, 0x01000100, 0x80000000, 0x81040100,
0x00000000, 0x01040000, 0x81000100, 0x80040000,
0x01040100, 0x81000000, 0x01000000, 0x80000100,
0x81000000, 0x80040100, 0x00040000, 0x01000000,
0x81040000, 0x00040100, 0x00000100, 0x80000000,
0x00040100, 0x81000100, 0x01040000, 0x00000100,
0x80000100, 0x00000000, 0x80040000, 0x01040100,
0x01000100, 0x81040000, 0x81040100, 0x00040000,
0x81040000, 0x80000100, 0x00040000, 0x81000000,
0x00040100, 0x01000100, 0x80000000, 0x01040000,
0x81000100, 0x00000000, 0x00000100, 0x80040000,
0x00000000, 0x81040000, 0x01040100, 0x00000100,
0x01000000, 0x81040100, 0x80040100, 0x00040000,
0x81040100, 0x80000000, 0x01000100, 0x80040100,
0x80040000, 0x00040100, 0x01040000, 0x81000100,
0x80000100, 0x01000000, 0x81000000, 0x01040100,
]
LUT_A3 = [
0x04010801, 0x00000000, 0x00010800, 0x04010000,
0x04000001, 0x00000801, 0x04000800, 0x00010800,
0x00000800, 0x04010001, 0x00000001, 0x04000800,
0x00010001, 0x04010800, 0x04010000, 0x00000001,
0x00010000, 0x04000801, 0x04010001, 0x00000800,
0x00010801, 0x04000000, 0x00000000, 0x00010001,
0x04000801, 0x00010801, 0x04010800, 0x04000001,
0x04000000, 0x00010000, 0x00000801, 0x04010801,
0x00010001, 0x04010800, 0x04000800, 0x00010801,
0x04010801, 0x00010001, 0x04000001, 0x00000000,
0x04000000, 0x00000801, 0x00010000, 0x04010001,
0x00000800, 0x04000000, 0x00010801, 0x04000801,
0x04010800, 0x00000800, 0x00000000, 0x04000001,
0x00000001, 0x04010801, 0x00010800, 0x04010000,
0x04010001, 0x00010000, 0x00000801, 0x04000800,
0x04000801, 0x00000001, 0x04010000, 0x00010800,
]
LUT_B0 = [
0x00000400, 0x00000020, 0x00100020, 0x40100000,
0x40100420, 0x40000400, 0x00000420, 0x00000000,
0x00100000, 0x40100020, 0x40000020, 0x00100400,
0x40000000, 0x00100420, 0x00100400, 0x40000020,
0x40100020, 0x00000400, 0x40000400, 0x40100420,
0x00000000, 0x00100020, 0x40100000, 0x00000420,
0x40100400, 0x40000420, 0x00100420, 0x40000000,
0x40000420, 0x40100400, 0x00000020, 0x00100000,
0x40000420, 0x00100400, 0x40100400, 0x40000020,
0x00000400, 0x00000020, 0x00100000, 0x40100400,
0x40100020, 0x40000420, 0x00000420, 0x00000000,
0x00000020, 0x40100000, 0x40000000, 0x00100020,
0x00000000, 0x40100020, 0x00100020, 0x00000420,
0x40000020, 0x00000400, 0x40100420, 0x00100000,
0x00100420, 0x40000000, 0x40000400, 0x40100420,
0x40100000, 0x00100420, 0x00100400, 0x40000400,
]
LUT_B1 = [
0x00800000, 0x00001000, 0x00000040, 0x00801042,
0x00801002, 0x00800040, 0x00001042, 0x00801000,
0x00001000, 0x00000002, 0x00800002, 0x00001040,
0x00800042, 0x00801002, 0x00801040, 0x00000000,
0x00001040, 0x00800000, 0x00001002, 0x00000042,
0x00800040, 0x00001042, 0x00000000, 0x00800002,
0x00000002, 0x00800042, 0x00801042, 0x00001002,
0x00801000, 0x00000040, 0x00000042, 0x00801040,
0x00801040, 0x00800042, 0x00001002, 0x00801000,
0x00001000, 0x00000002, 0x00800002, 0x00800040,
0x00800000, 0x00001040, 0x00801042, 0x00000000,
0x00001042, 0x00800000, 0x00000040, 0x00001002,
0x00800042, 0x00000040, 0x00000000, 0x00801042,
0x00801002, 0x00801040, 0x00000042, 0x00001000,
0x00001040, 0x00801002, 0x00800040, 0x00000042,
0x00000002, 0x00001042, 0x00801000, 0x00800002,
]
LUT_B2 = [
0x10400000, 0x00404010, 0x00000010, 0x10400010,
0x10004000, 0x00400000, 0x10400010, 0x00004010,
0x00400010, 0x00004000, 0x00404000, 0x10000000,
0x10404010, 0x10000010, 0x10000000, 0x10404000,
0x00000000, 0x10004000, 0x00404010, 0x00000010,
0x10000010, 0x10404010, 0x00004000, 0x10400000,
0x10404000, 0x00400010, 0x10004010, 0x00404000,
0x00004010, 0x00000000, 0x00400000, 0x10004010,
0x00404010, 0x00000010, 0x10000000, 0x00004000,
0x10000010, 0x10004000, 0x00404000, 0x10400010,
0x00000000, 0x00404010, 0x00004010, 0x10404000,
0x10004000, 0x00400000, 0x10404010, 0x10000000,
0x10004010, 0x10400000, 0x00400000, 0x10404010,
0x00004000, 0x00400010, 0x10400010, 0x00004010,
0x00400010, 0x00000000, 0x10404000, 0x10000010,
0x10400000, 0x10004010, 0x00000010, 0x00404000,
]
LUT_B3 = [
0x00208080, 0x00008000, 0x20200000, 0x20208080,
0x00200000, 0x20008080, 0x20008000, 0x20200000,
0x20008080, 0x00208080, 0x00208000, 0x20000080,
0x20200080, 0x00200000, 0x00000000, 0x20008000,
0x00008000, 0x20000000, 0x00200080, 0x00008080,
0x20208080, 0x00208000, 0x20000080, 0x00200080,
0x20000000, 0x00000080, 0x00008080, 0x20208000,
0x00000080, 0x20200080, 0x20208000, 0x00000000,
0x00000000, 0x20208080, 0x00200080, 0x20008000,
0x00208080, 0x00008000, 0x20000080, 0x00200080,
0x20208000, 0x00000080, 0x00008080, 0x20200000,
0x20008080, 0x20000000, 0x20200000, 0x00208000,
0x20208080, 0x00008080, 0x00208000, 0x20200080,
0x00200000, 0x20000080, 0x20008000, 0x00000000,
0x00008000, 0x00200000, 0x20200080, 0x00208080,
0x20000000, 0x20208000, 0x00000080, 0x20008080,
]
VALID_CHARS = "0123456789ABCDEFGHJKLMNPRSTUWXYZ"
CONV_CHARS = {
"I": "1",
"O": "0",
}
@staticmethod
def __type_from_cardid(cardid: str) -> int:
if cardid[:2].upper() == 'E0':
return 1
if cardid[:2].upper() == '01':
return 2
raise CardCipherException('Unrecognized card type')
@staticmethod
def encode(cardid: str) -> str:
"""
Given a card ID as stored on a card (Usually starting with E004), convert
it to the card string as shown on the back of the card.
Parameters:
cardid - 16 digit card ID (hex values stored as string).
Returns:
String representation of the card string.
"""
if len(cardid) != 16:
raise CardCipherException(
f'Expected 16-character card ID, got {len(cardid)}',
)
cardint = [int(cardid[i:(i + 2)], 16) for i in range(0, len(cardid), 2)]
# Reverse bytes
reverse = [0] * 8
for i in range(0, 8):
reverse[7 - i] = cardint[i]
# Encipher
ciphered = CardCipher.__encode(bytes(reverse))
# Convert 8 x 8 bit bytes into 13 x 5 bit groups (sort of)
bits = [0] * 65
for i in range(0, 64):
bits[i] = (ciphered[i >> 3] >> (~i & 7)) & 1
groups = [0] * 16
for i in range(0, 13):
groups[i] = (
(bits[i * 5 + 0] << 4) |
(bits[i * 5 + 1] << 3) |
(bits[i * 5 + 2] << 2) |
(bits[i * 5 + 3] << 1) |
(bits[i * 5 + 4] << 0)
)
# Smear 13 groups out into 14 groups
groups[13] = 1
groups[0] ^= CardCipher.__type_from_cardid(cardid)
for i in range(0, 14):
groups[i] ^= groups[i - 1]
# Scheme field is 1 for old-style, 2 for felica cards
groups[14] = CardCipher.__type_from_cardid(cardid)
groups[15] = CardCipher.__checksum(groups)
# Convert to chars and return
return ''.join([CardCipher.VALID_CHARS[i] for i in groups])
@staticmethod
def decode(cardid: str) -> str:
"""
Given a card string as shown on the back of the card, return the card ID
as stored on the card itself. Does some sanitization to remove dashes,
spaces and convert confusing characters (1, L and 0, O) before converting.
Parameters:
cardid - String representation of the card string.
Returns:
16 digit card ID (hex values stored as string).
"""
# First sanitize the input
cardid = cardid.replace(' ', '')
cardid = cardid.replace('-', '')
cardid = cardid.upper()
for c in CardCipher.CONV_CHARS:
cardid = cardid.replace(c, CardCipher.CONV_CHARS[c])
if len(cardid) != 16:
raise CardCipherException(
f'Expected 16-character card ID, got {len(cardid)}',
)
for c in cardid:
if c not in CardCipher.VALID_CHARS:
raise CardCipherException(
f'Got unexpected character {c} in card ID',
)
# Convert chars to groups
groups = [0] * 16
for i in range(0, 16):
for j in range(0, 32):
if cardid[i] == CardCipher.VALID_CHARS[j]:
groups[i] = j
break
# Verify scheme and checksum
if (groups[14] != 1 and groups[14] != 2):
raise CardCipherException(
"Unrecognized card type"
)
if groups[15] != CardCipher.__checksum(groups):
raise CardCipherException(
"Bad card number"
)
# Un-smear 14 fields back into 13
for i in range(13, 0, -1):
groups[i] ^= groups[i - 1]
groups[0] ^= groups[14]
# Explode groups into bits
bits = [0] * 64
for i in range(0, 64):
bits[i] = (groups[int(i / 5)] >> (4 - (i % 5))) & 1
# Re-pack bits into eight bytes
ciphered = [0] * 8
for i in range(0, 64):
ciphered[int(i / 8)] |= bits[i] << (~i & 7)
# Decipher and reverse
deciphered = CardCipher.__decode(bytes(ciphered))
reverse = [0] * 8
for i in range(0, 8):
reverse[i] = deciphered[7 - i]
def tohex(x: int) -> str:
h = hex(x)[2:]
while len(h) < 2:
h = '0' + h
return h.upper()
# Convert to a string, verify we have the same type
finalvalue = ''.join([tohex(v) for v in reverse])
if groups[14] != CardCipher.__type_from_cardid(finalvalue):
raise CardCipherException(
"Card type mismatch"
)
return finalvalue
@staticmethod
def __checksum(data: List[int]) -> int:
checksum = 0
for i in range(0, 15):
checksum += (i % 3 + 1) * data[i]
while checksum >= 0x20:
checksum = (checksum & 0x1F) + (checksum >> 5)
return checksum
@staticmethod
def __encode(inbytes: bytes) -> bytes:
if len(inbytes) != 8:
raise CardCipherException(
f'Expected 8-byte input, got {len(inbytes)}',
)
inp = [b for b in inbytes]
out = [0] * 8
CardCipher.__from_int(out, CardCipher.__operatorA(0x00, CardCipher.__to_int(inp)))
CardCipher.__from_int(out, CardCipher.__operatorB(0x20, CardCipher.__to_int(out)))
CardCipher.__from_int(out, CardCipher.__operatorA(0x40, CardCipher.__to_int(out)))
return bytes(out)
@staticmethod
def __decode(inbytes: bytes) -> bytes:
if len(inbytes) != 8:
raise CardCipherException(
f'Expected 8-byte input, got {len(inbytes)}',
)
inp = [b for b in inbytes]
out = [0] * 8
CardCipher.__from_int(out, CardCipher.__operatorB(0x40, CardCipher.__to_int(inp)))
CardCipher.__from_int(out, CardCipher.__operatorA(0x20, CardCipher.__to_int(out)))
CardCipher.__from_int(out, CardCipher.__operatorB(0x00, CardCipher.__to_int(out)))
return bytes(out)
@staticmethod
def __to_int(data: List[int]) -> int:
inX = (
(data[0] & 0xFF) |
((data[1] & 0xFF) << 8) |
((data[2] & 0xFF) << 16) |
((data[3] & 0xFF) << 24)
)
inY = (
(data[4] & 0xFF) |
((data[5] & 0xFF) << 8) |
((data[6] & 0xFF) << 16) |
((data[7] & 0xFF) << 24)
)
v7 = ((((inX ^ (inY >> 4)) & 0xF0F0F0F) << 4) ^ inY) & 0xFFFFFFFF
v8 = (((inX ^ (inY >> 4)) & 0xF0F0F0F) ^ inX) & 0xFFFFFFFF
v9 = ((v7 ^ (v8 >> 16))) & 0x0000FFFF
v10 = (((v7 ^ (v8 >> 16)) << 16) ^ v8) & 0xFFFFFFFF
v11 = (v9 ^ v7) & 0xFFFFFFFF
v12 = (v10 ^ (v11 >> 2)) & 0x33333333
v13 = (v11 ^ (v12 << 2)) & 0xFFFFFFFF
v14 = (v12 ^ v10) & 0xFFFFFFFF
v15 = (v13 ^ (v14 >> 8)) & 0x00FF00FF
v16 = (v14 ^ (v15 << 8)) & 0xFFFFFFFF
v17 = CardCipher.__ror(v15 ^ v13, 1)
v18 = (v16 ^ v17) & 0x55555555
v3 = CardCipher.__ror(v18 ^ v16, 1)
v4 = (v18 ^ v17) & 0xFFFFFFFF
return ((v3 & 0xFFFFFFFF) << 32) | (v4 & 0xFFFFFFFF)
@staticmethod
def __from_int(data: List[int], state: int) -> None:
v3 = (state >> 32) & 0xFFFFFFFF
v4 = state & 0xFFFFFFFF
v22 = CardCipher.__ror(v4, 31)
v23 = (v3 ^ v22) & 0x55555555
v24 = (v23 ^ v22) & 0xFFFFFFFF
v25 = CardCipher.__ror(v23 ^ v3, 31)
v26 = (v25 ^ (v24 >> 8)) & 0x00FF00FF
v27 = (v24 ^ (v26 << 8)) & 0xFFFFFFFF
v28 = (v26 ^ v25) & 0xFFFFFFFF
v29 = ((v28 >> 2) ^ v27) & 0x33333333
v30 = ((v29 << 2) ^ v28) & 0xFFFFFFFF
v31 = (v29 ^ v27) & 0xFFFFFFFF
v32 = (v30 ^ (v31 >> 16)) & 0x0000FFFF
v33 = (v31 ^ (v32 << 16)) & 0xFFFFFFFF
v34 = (v32 ^ v30) & 0xFFFFFFFF
v35 = (v33 ^ (v34 >> 4)) & 0xF0F0F0F
outY = ((v35 << 4) ^ v34) & 0xFFFFFFFF
outX = (v35 ^ v33) & 0xFFFFFFFF
data[0] = outX & 0xFF
data[1] = (outX >> 8) & 0xFF
data[2] = (outX >> 16) & 0xFF
data[3] = (outX >> 24) & 0xFF
data[4] = outY & 0xFF
data[5] = (outY >> 8) & 0xFF
data[6] = (outY >> 16) & 0xFF
data[7] = (outY >> 24) & 0xFF
@staticmethod
def __operatorA(off: int, state: int) -> int:
v3 = (state >> 32) & 0xFFFFFFFF
v4 = state & 0xFFFFFFFF
for i in range(0, 32, 4):
v20 = CardCipher.__ror(v3 ^ CardCipher.KEY[off + i + 1], 28)
v4 ^= (
CardCipher.LUT_B0[(v20 >> 26) & 0x3F] ^
CardCipher.LUT_B1[(v20 >> 18) & 0x3F] ^
CardCipher.LUT_B2[(v20 >> 10) & 0x3F] ^
CardCipher.LUT_B3[(v20 >> 2) & 0x3F] ^
CardCipher.LUT_A0[((v3 ^ CardCipher.KEY[off + i]) >> 26) & 0x3F] ^
CardCipher.LUT_A1[((v3 ^ CardCipher.KEY[off + i]) >> 18) & 0x3F] ^
CardCipher.LUT_A2[((v3 ^ CardCipher.KEY[off + i]) >> 10) & 0x3F] ^
CardCipher.LUT_A3[((v3 ^ CardCipher.KEY[off + i]) >> 2) & 0x3F]
)
v21 = CardCipher.__ror(v4 ^ CardCipher.KEY[off + i + 3], 28)
v3 ^= (
CardCipher.LUT_B0[(v21 >> 26) & 0x3F] ^
CardCipher.LUT_B1[(v21 >> 18) & 0x3F] ^
CardCipher.LUT_B2[(v21 >> 10) & 0x3F] ^
CardCipher.LUT_B3[(v21 >> 2) & 0x3F] ^
CardCipher.LUT_A0[((v4 ^ CardCipher.KEY[off + i + 2]) >> 26) & 0x3F] ^
CardCipher.LUT_A1[((v4 ^ CardCipher.KEY[off + i + 2]) >> 18) & 0x3F] ^
CardCipher.LUT_A2[((v4 ^ CardCipher.KEY[off + i + 2]) >> 10) & 0x3F] ^
CardCipher.LUT_A3[((v4 ^ CardCipher.KEY[off + i + 2]) >> 2) & 0x3F]
)
return ((v3 & 0xFFFFFFFF) << 32) | (v4 & 0xFFFFFFFF)
@staticmethod
def __operatorB(off: int, state: int) -> int:
v3 = (state >> 32) & 0xFFFFFFFF
v4 = state & 0xFFFFFFFF
for i in range(0, 32, 4):
v20 = CardCipher.__ror(v3 ^ CardCipher.KEY[off + 31 - i], 28)
v4 ^= (
CardCipher.LUT_A0[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 26) & 0x3F] ^
CardCipher.LUT_A1[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 18) & 0x3F] ^
CardCipher.LUT_A2[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 10) & 0x3F] ^
CardCipher.LUT_A3[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 2) & 0x3F] ^
CardCipher.LUT_B0[(v20 >> 26) & 0x3F] ^
CardCipher.LUT_B1[(v20 >> 18) & 0x3F] ^
CardCipher.LUT_B2[(v20 >> 10) & 0x3F] ^
CardCipher.LUT_B3[(v20 >> 2) & 0x3F]
)
v21 = CardCipher.__ror(v4 ^ CardCipher.KEY[off + 29 - i], 28)
v3 ^= (
CardCipher.LUT_A0[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 26) & 0x3F] ^
CardCipher.LUT_A1[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 18) & 0x3F] ^
CardCipher.LUT_A2[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 10) & 0x3F] ^
CardCipher.LUT_A3[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 2) & 0x3F] ^
CardCipher.LUT_B0[(v21 >> 26) & 0x3F] ^
CardCipher.LUT_B1[(v21 >> 18) & 0x3F] ^
CardCipher.LUT_B2[(v21 >> 10) & 0x3F] ^
CardCipher.LUT_B3[(v21 >> 2) & 0x3F]
)
return ((v3 & 0xFFFFFFFF) << 32) | (v4 & 0xFFFFFFFF)
@staticmethod
def __ror(val: int, amount: int) -> int:
return ((val << (32 - amount)) & 0xFFFFFFFF) | ((val >> amount) & 0xFFFFFFFF)