Skip to content

Instantly share code, notes, and snippets.

@jacksonie
Forked from cymruu/tibia.py
Created September 13, 2024 20:35
Show Gist options
  • Save jacksonie/841318004fb0edd7b9398d90f261137e to your computer and use it in GitHub Desktop.
Save jacksonie/841318004fb0edd7b9398d90f261137e to your computer and use it in GitHub Desktop.

Revisions

  1. @cymruu cymruu revised this gist Apr 15, 2017. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions tibia.py
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@
    '''based on volf ram code'''
    import random
    import struct
    import zlib
  2. @cymruu cymruu renamed this gist Apr 15, 2017. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. @cymruu cymruu created this gist Apr 15, 2017.
    148 changes: 148 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,148 @@
    import random
    import struct
    import zlib
    import socket
    OT_RSA = 109120132967399429278860960508995541528237502902798129123468757937266291492576446330739696001110603907230888610072655818825358503429057592827629436413108566029093628212635953836686562675849720620786279431090218017681061521755056710823876476444260558147179707119674283982419152118103759076030616683978566631413
    def rsa_encrypt(m):
    m = sum(x*pow(256, i) for i, x in enumerate(reversed(m)))
    c = pow(m, 65537, OT_RSA)
    return bytes((c >> i) & 255 for i in reversed(range(0, 1024, 8)))
    def rsa_encrypt(m):
    m = sum(x*pow(256, i) for i, x in enumerate(reversed(m)))
    c = pow(m, 65537, OT_RSA)
    return bytes((c >> i) & 255 for i in reversed(range(0, 1024, 8)))

    def xtea_decrypt_block(block, key):
    v0, v1 = struct.unpack('=2I', block)
    k = struct.unpack('=4I', key)
    delta, mask, rounds = 0x9E3779B9, 0xFFFFFFFF, 32
    sum = (delta * rounds) & mask
    for round in range(rounds):
    v1 = (v1 - (((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[sum >> 11 & 3]))) & mask
    sum = (sum - delta) & mask
    v0 = (v0 - (((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]))) & mask
    return struct.pack('=2I', v0, v1)
    def xtea_decrypt(data, key):
    return b''.join(xtea_decrypt_block(data[i:i + 8], key) for i in range(0, len(data), 8))
    def make_login_request(xtea_key, acc_name, acc_password):
    login_request = struct.pack('=B16sH%isH%is' % (len(acc_name), len(acc_password)), 0, xtea_key, len(acc_name), acc_name, len(acc_password), acc_password)
    login_request += bytes(random.randint(0, 255) for i in range(len(login_request), 128))
    login_request = struct.pack('=BHHIIIIB', 1, 2, 1098, 1098, 0x4E12DAFF, 0x4E12DB27, 0x4E119CBF, 0) + rsa_encrypt(login_request)
    login_request = struct.pack('=HI', len(login_request) + 4, zlib.adler32(login_request)) + login_request
    return login_request
    def make_entergame_request(session_key, charname, timestamp, randomNumber):
    print('sessionkey', session_key, charname)
    '''
    It's a bit outdated, but still helpful.
    Recent specification can be found here:
    https://github.com/edubart/otclient and here
    https://github.com/otland/forgottenserver

    10 8 packet ID
    1 or 2 16 operating system: linux 1, windows 2
    854 16 version without dot
    RSA 128*8 RSA encrypted block
    {
    0 8 it must be 0
    (random) 128 XTEA key
    0 or 1 8 GM flag: normal character 0, GM 1
    length of accname 16
    "accname" length*8 account name; string in ASCII without null byte
    length of character 16
    "character" length*8 character name; string in ASCII without null byte
    length of pass 16
    "pass" length*8 password; string in ASCII without null byte
    (security bytes) 5*8 security bytes received from server
    (any(?)) ? padding; RSA is a block cipher - it must encrypt 128 bytes or 256 bytes etc.
    }'''
    #RSA encrypted part
    entergame_request = struct.pack('=B16sBH%isH%isIB' % (len(session_key), len(charname)), 0, xtea_key, 0, len(session_key), session_key, len(charname), charname, timestamp, randomNumber)
    entergame_request += bytes(random.randint(0,255) for i in range(len(entergame_request), 128))
    entergame_request = struct.pack('=BHHIHB', 10, 2, 1098, 1098, 65, 0) + rsa_encrypt(entergame_request)
    entergame_request = struct.pack('=HI', len(entergame_request)+4, zlib.adler32(entergame_request)) + entergame_request

    return entergame_request
    def get_string(packet_bytes):
    return bytes(next(packet_bytes) for i in range(next(packet_bytes) + 256*next(packet_bytes)))
    def get_int(packet_bytes, bits):
    return sum(next(packet_bytes)*pow(256, i) for i in range(bits//8))
    def recv_packets(s):
    #decode xtea
    packet = xtea_decrypt(s.recv(4 + struct.unpack('=H', s.recv(2))[0])[4:], xtea_key)
    packet_bytes = iter(packet[2:2 + struct.unpack('=H', packet[:2])[0]])
    for packet_code in packet_bytes:
    if packet_code == 11:#LoginServerErrorNew
    error = get_string(packet_bytes)
    yield 'LoginServerErrorNew', error
    elif packet_code == 20:#LoginServerMotd
    motd = get_string(packet_bytes)
    yield 'LoginServerMotd', motd
    elif packet_code == 30: #LoginServerUpdateNeeded
    yield 'LoginServerUpdateNeeded', 'Clients need update'
    elif packet_code == 12: #LoginServerTokenSuccess
    print('unknown: ', get_int(packet_bytes, 8))
    yield 'LoginServerTokenSuccess', None
    elif packet_code == 100: #Character list
    worlds = {}
    worldsCount = get_int(packet_bytes, 8)
    for world in range(worldsCount):
    worldId = get_int(packet_bytes, 8)
    worlds[worldId] = {}
    worlds[worldId]['name'] = get_string(packet_bytes)
    worlds[worldId]['ip'] = get_string(packet_bytes)
    worlds[worldId]['port'] = get_int(packet_bytes, 16)
    worlds[worldId]['previewState'] = get_int(packet_bytes, 8)
    charactersCount = get_int(packet_bytes, 8)
    characters = {}
    for character in range(charactersCount):
    worldId = get_int(packet_bytes, 8)
    characters[character] = {}
    characters[character]['name'] = get_string(packet_bytes)
    characters[character]['worldName'] = worlds[worldId]['name']
    characters[character]['worldIp'] = worlds[worldId]['ip']
    characters[character]['port'] = worlds[worldId]['port']
    characters[character]['previewState'] = worlds[worldId]['previewState']
    account = {}
    account['premDays'] = get_int(packet_bytes, 16)
    yield 'Characters list', characters
    elif packet_code == 101: #character list extened
    yield 'Character list extened', None
    elif packet_code == 40: #Session key
    session_key = get_string(packet_bytes)
    yield 'Session key', session_key
    else:
    yield ('unknown packet code %d (0x%x)' % (packet_code, packet_code), None)
    def recv_game_packets(s):
    packet = s.recv(4 + struct.unpack('=H', s.recv(2))[0])[4:]
    packet_bytes = iter(packet[2:2 + struct.unpack('=H', packet[:2])[0]])
    for packet_code in packet_bytes:
    if packet_code == 31: #GameServerChallenge
    timestamp = get_int(packet_bytes, 32)
    randomNumber = get_int(packet_bytes, 8)
    print(timestamp, randomNumber)
    yield 'GameServerChallenge', [timestamp, randomNumber]
    else:
    yield ('unknown packet code %d (0x%x)' % (packet_code, packet_code), None)
    xtea_key = bytes(random.randint(0,255) for i in range(16))
    print('xtea_key', xtea_key)
    acc_name = b'bot1xd'
    acc_password = b'dupa123'
    session_key = ''
    with socket.socket() as s:
    s.connect(('144.217.149.144', 7171))
    s.sendall(make_login_request(xtea_key, acc_name, acc_password))
    for packet_name, packet_data in recv_packets(s):
    print(packet_name, packet_data)
    if(packet_name == 'Characters list'):
    chars = packet_data
    print(chars)
    if packet_name == 'Session key':
    session_key = packet_data
    with socket.socket() as c:
    c.connect((chars[0]['worldIp'], chars[0]['port']))
    while True:
    for packet_name, packet_data in recv_game_packets(c):
    print(packet_name, packet_data)
    if(packet_name == 'GameServerChallenge'):
    print('sending enteragame packet')
    c.sendall(make_entergame_request(session_key, chars[0]['name'], packet_data[0], packet_data[1]))