diff --git a/.gitignore b/.gitignore index 13eb89e..2b604ce 100644 --- a/.gitignore +++ b/.gitignore @@ -221,4 +221,5 @@ pip-log.txt .idea/vcs.xml .idea/workspace.xml gpcm.db -.idea/dictionaries/Nagato.xml \ No newline at end of file +.idea/dictionaries/Nagato.xml +.idea/modules.xml \ No newline at end of file diff --git a/other/utils.py b/other/utils.py index 4fcd5ec..94a7ee7 100644 --- a/other/utils.py +++ b/other/utils.py @@ -67,4 +67,40 @@ def base32_decode(str, reverse = False): # For server logging def print_log(text): print "[%s] %s" % (time.strftime("%c"), text) - print "" \ No newline at end of file + print "" + +# Number routines +# I'm not sure what the pythonic way to do this is, so I'm making the code explicit by giving myself functions to +# convert the bytes to ints. +def get_num_from_bytes(data, idx, bytes, bigEndian = False): + # Get only the bytes we want to work with + data = data[idx:idx+bytes] + + if bigEndian == True: + data = data[::-1] + + num = 0 + i = 0 + while i < bytes and i < len(data): + num |= (ord(data[i]) << (8 * i)) + i += 1 + + return num + +# Instead of passing slices, pass the buffer and index so we can calculate the length automatically. +def get_short(data, idx): + return get_num_from_bytes(data, idx, 2, False) + +def get_short_be(data, idx): + return get_num_from_bytes(data, idx, 2, True) + +def get_int(data, idx): + return get_num_from_bytes(data, idx, 4, False) + +def get_int_be(data, idx): + return get_num_from_bytes(data, idx, 4, True) + +def get_string(data, idx): + data = data[idx:] + end = data.index('\0') + return data[:end] diff --git a/server_browser.py b/server_browser.py index e463860..7bca3e2 100644 --- a/server_browser.py +++ b/server_browser.py @@ -24,11 +24,106 @@ s.listen(backlog) utils.print_log("Server is now listening on %s:%s..." % (address[0], address[1])) while 1: - client, address = s.accept() + client, addr = s.accept() utils.print_log("Received connection from %s:%s" % (address[0], address[1])) - - while 1: + + receive_data = True + while receive_data: + utils.print_log("Waiting for data...") + accept_connection = True - data = client.recv(size).rstrip() - utils.print_log("RESPONSE: %s" % data) + data = client.recv(size) + + # Didn't get a valid command. + # Player disconnected? + # Close connection + if not data: + break + + # First 2 bytes are the packet size. + # + # Third byte is the command byte. + # According to Openspy-Core: + # 0x00 - Server list request + # 0x01 - Server info request + # 0x02 - Send message request + # 0x03 - Keep alive reply + # 0x04 - Map loop request (?) + # 0x05 - Player search request + # + # For Tetris DS, at the very least 0x00 and 0x02 need to be implemented. + if data[2] == '\x00': # Server list request + utils.print_log("Received server list request from %s:%s..." % (addr[0], addr[1])) + + # This code is so... not python. The C programmer in me is coming out strong. + # TODO: Rewrite this section later? + idx = 3 + list_version = ord(data[idx]) + idx += 1 + encoding_version = ord(data[idx]) + idx += 1 + game_version = utils.get_int(data, idx) + idx += 4 + + query_game = utils.get_string(data, idx) + idx += len(query_game) + 1 + game_name = utils.get_string(data, idx) + idx += len(game_name) + 1 + + challenge = data[idx:idx+8] + idx += 8 + + filter = utils.get_string(data, idx) + idx += len(filter) + 1 + fields = utils.get_string(data, idx) + idx += len(fields) + 1 + + options = utils.get_int_be(data, idx) + idx += 4 + + source_ip = 0 + max_servers = 0 + + ALTERNATE_SOURCE_IP = 0x08 + LIMIT_RESULT_COUNT = 0x80 + if (options & LIMIT_RESULT_COUNT): + max_servers = utils.get_int(data, idx) + elif (options & ALTERNATE_SOURCE_IP): + source_ip = utils.get_int(data, idx) + + print "%02x %02x %08x" % (list_version, encoding_version, game_version) + print "%s" % query_game + print "%s" % game_name + print "%s" % challenge + print "%s" % filter + print "%s" % fields + + print "%08x" % options + print "%d %08x" % (max_servers, source_ip) + + # TODO: Handle query + + elif data[2] == '\x02': # Send message request + dest_addr = '.'.join(["%d" % x for x in addr[3:7]]) + dest_port = utils.get_short_be(addr, 7) # What's the pythonic way to do this? unpack? + dest = (dest_addr, dest_port) + + # Wait for message data + msg_data = client.recv(size) + + utils.print_log("Received send message request from %s:%s to %s:%d... %s" % (addr[0], addr[1], dest_addr, dest_port, msg_data)) + + # Create new connection to send to other user over UDP. + # Move this code somewhere else after testing has been finished. + user_s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + user_s.bind(dest) + user_s.sendto(msg_data, dest) + + utils.print_log("Sent message to %s:%d... %s" % (dest_addr, dest_port, msg_data)) + + elif data[2] == '\x03': # Keep alive reply + utils.print_log("Received keep alive from %s:%s..." % (addr[0], addr[1])) + + else: + utils.print_log("Received unknown command (%02x) from %s:%s... %s" % (ord(data[2]), addr[0], addr[1], data))