From 1c6b7ef2570911fc8b02d045476ad3021a2bffdd Mon Sep 17 00:00:00 2001 From: Andrew Gutekanst Date: Wed, 1 Jan 2020 08:46:36 +0900 Subject: [PATCH] Port entrance server to Go --- .../entrance_server.go | 38 ++- entranceserver/make_resp.go | 119 +++++++++ main.go | 4 +- test.py | 225 ------------------ 4 files changed, 151 insertions(+), 235 deletions(-) rename entrance_server.go => entranceserver/entrance_server.go (52%) create mode 100644 entranceserver/make_resp.go delete mode 100644 test.py diff --git a/entrance_server.go b/entranceserver/entrance_server.go similarity index 52% rename from entrance_server.go rename to entranceserver/entrance_server.go index 39b7e5d0b..b4687b1e5 100644 --- a/entrance_server.go +++ b/entranceserver/entrance_server.go @@ -1,10 +1,9 @@ -package main +package entranceserver import ( "encoding/hex" "fmt" "io" - "io/ioutil" "net" "github.com/Andoryuuta/Erupe/network" @@ -31,17 +30,40 @@ func handleEntranceServerConnection(conn net.Conn) { fmt.Printf("Got entrance server command:\n%s\n", hex.Dump(pkt)) - data, err := ioutil.ReadFile("custom_entrance_server_resp.bin")//("tw_server_list_resp.bin") - if err != nil { - print(err) - return - } + data := makeResp([]ServerInfo{ + ServerInfo{ + IP: net.ParseIP("127.0.0.1"), + Unk2: 0, + Type: 1, + Season: 0, + Unk6: 3, + Name: "AErupe Server in Go! @localhost", + AllowedClientFlags: 4096, + Channels: []ChannelInfo{ + ChannelInfo{ + Port: 54001, + MaxPlayers: 100, + CurrentPlayers: 3, + Unk4: 0, + Unk5: 0, + Unk6: 0, + Unk7: 0, + Unk8: 0, + Unk9: 0, + Unk10: 319, + Unk11: 248, + Unk12: 159, + Unk13: 12345, + }, + }, + }, + }) cc.SendPacket(data) } } -func doEntranceServer(listenAddr string) { +func DoEntranceServer(listenAddr string) { l, err := net.Listen("tcp", listenAddr) if err != nil { panic(err) diff --git a/entranceserver/make_resp.go b/entranceserver/make_resp.go new file mode 100644 index 000000000..019b2718f --- /dev/null +++ b/entranceserver/make_resp.go @@ -0,0 +1,119 @@ +package entranceserver + +import ( + "encoding/binary" + "net" + + "github.com/Andoryuuta/byteframe" +) + +func paddedString(x string, size uint) []byte { + out := make([]byte, size) + copy(out, x) + + // Null terminate it. + out[len(out)-1] = 0 + return out +} + +// ServerInfo represents an entry in the serverlist. +type ServerInfo struct { + IP net.IP + Unk2 uint16 + Type uint8 // Server type. 0=?, 1=open, 2=cities, 3=newbie, 4=bar + Season uint8 // Server activity. 0 = green, 1 = orange, 2 = blue + Unk6 uint8 // Something to do with server recommendation on 0, 3, and 5. + Name string // Server name, 66 byte null terminated Shift-JIS. + + // 4096(PC, PS3/PS4)?, 8258(PC, PS3/PS4)?, 8192 == nothing? + // THIS ONLY EXISTS IF Binary8Header.type == "SV2", NOT "SVR"! + AllowedClientFlags uint32 + + Channels []ChannelInfo +} + +// ChannelInfo represents an entry in a server's channel list. +type ChannelInfo struct { + Port uint16 + //ChannelIndex uint16 + MaxPlayers uint16 + CurrentPlayers uint16 + Unk4 uint16 + Unk5 uint16 + Unk6 uint16 + Unk7 uint16 + Unk8 uint16 + Unk9 uint16 + Unk10 uint16 + Unk11 uint16 + Unk12 uint16 + Unk13 uint16 +} + +func encodeServerInfo(serverInfos []ServerInfo) []byte { + bf := byteframe.NewByteFrame() + + for serverIdx, si := range serverInfos { + bf.WriteUint32(binary.LittleEndian.Uint32(si.IP)) + bf.WriteUint16(16 + uint16(serverIdx)) + bf.WriteUint16(si.Unk2) + bf.WriteUint16(uint16(len(si.Channels))) + bf.WriteUint8(si.Type) + bf.WriteUint8(si.Season) + bf.WriteUint8(si.Unk6) + bf.WriteBytes(paddedString(si.Name, 66)) + bf.WriteUint32(si.AllowedClientFlags) + + for channelIdx, ci := range si.Channels { + bf.WriteUint16(ci.Port) + bf.WriteUint16(16 + uint16(channelIdx)) + bf.WriteUint16(ci.MaxPlayers) + bf.WriteUint16(ci.CurrentPlayers) + bf.WriteUint16(ci.Unk4) + bf.WriteUint16(ci.Unk5) + bf.WriteUint16(ci.Unk6) + bf.WriteUint16(ci.Unk7) + bf.WriteUint16(ci.Unk8) + bf.WriteUint16(ci.Unk9) + bf.WriteUint16(ci.Unk10) + bf.WriteUint16(ci.Unk11) + bf.WriteUint16(ci.Unk12) + bf.WriteUint16(ci.Unk13) + } + } + + return bf.Data() +} + +func makeHeader(data []byte, respType string, entryCount uint16, key byte) []byte { + bf := byteframe.NewByteFrame() + bf.WriteBytes([]byte(respType)) + bf.WriteUint16(entryCount) + bf.WriteUint16(uint16(len(data))) + if len(data) > 0 { + bf.WriteUint32(CalcSum32(data)) + bf.WriteBytes(data) + } + + dataToEncrypt := bf.Data() + + bf = byteframe.NewByteFrame() + bf.WriteUint8(key) + bf.WriteBytes(EncryptBin8(dataToEncrypt, key)) + return bf.Data() +} + +func makeResp(servers []ServerInfo) []byte { + rawServerData := encodeServerInfo(servers) + + bf := byteframe.NewByteFrame() + bf.WriteBytes(makeHeader(rawServerData, "SV2", uint16(len(servers)), 0x00)) + + // TODO(Andoryuuta): Figure out what this user data is. + // Is it for the friends list at the world selection screen? + // If so, how does it work without the entrance server connection being authenticated? + bf.WriteBytes(makeHeader([]byte{}, "USR", 0, 0x00)) + + return bf.Data() + +} diff --git a/main.go b/main.go index 4bedbc5fa..ed6d0d43c 100644 --- a/main.go +++ b/main.go @@ -6,8 +6,8 @@ import ( "time" "github.com/Andoryuuta/Erupe/channelserver" - _ "github.com/Andoryuuta/Erupe/network/mhfpacket" "github.com/Andoryuuta/Erupe/signserver" + "github.com/Andoryuuta/Erupe/entranceserver" _ "github.com/lib/pq" ) @@ -31,7 +31,7 @@ func main() { // Finally start our server(s). go serveLauncherHTML(":80", false) - go doEntranceServer(":53310") + go entranceserver.DoEntranceServer(":53310") signServer := signserver.NewServer( &signserver.Config{ diff --git a/test.py b/test.py deleted file mode 100644 index 839841015..000000000 --- a/test.py +++ /dev/null @@ -1,225 +0,0 @@ -from hexdump import hexdump -import io -import sys - -from construct import * - -Binary8Header = Struct( - "server_type" / Bytes(3), - "entry_count" / Int16ub, - "body_size" / Int16ub, - "checksum" / Int32ub, -) - -EntranceListComplete = Struct( - Embedded(Binary8Header), - "servers" / Array(this.entry_count, - Struct( - "host_ip_4byte" / Int32ub, - "unk_1" / Int16ub, # Server ID maybe? - "unk_2" / Int16ub, - "channel_count" / Int16ub, - "server_type" / Byte, # Server type. 0=?, 1=open, 2=cities, 3=newbie, 4=bar - "color" / Byte, # Server activity. 0 = green, 1 = orange, 2 = blue - "unk_6" / Byte, # Something to do with server recommendation on 0, 3, and 5. - "name" / Bytes(66), # Shift-JIS. - - # 4096(PC, PS3/PS4)?, 8258(PC, PS3/PS4)?, 8192 == nothing? - # THIS ONLY EXISTS IF Binary8Header.type == "SV2", NOT "SVR"! - "allowed_client_type_flags" / Int32ub, - - "channels" / Array(this.channel_count, - Struct( - "port" / Int16ub, - "unk_1" / Int16ub, # Channel ID maybe? - "max_players" / Int16ub, - "current_players" / Int16ub, - "unk_4" / Int16ub, - "unk_5" / Int16ub, - "unk_6" / Int16ub, - "unk_7" / Int16ub, - "unk_8" / Int16ub, - "unk_9" / Int16ub, - "unk_10" / Int16ub, - "unk_11" / Int16ub, - "unk_12" / Int16ub, - "unk_13" / Int16ub, - ) - ), - ) - ), -) - - -BINARY8_KEY = bytes([0x01, 0x23, 0x34, 0x45, 0x56, 0xAB, 0xCD, 0xEF]) -def decode_binary8(data, unk_key_byte): - cur_key = ((54323 * unk_key_byte) + 1) & 0xFFFFFFFF - - output_data = bytearray() - for i in range(len(data)): - tmp = (data[i] ^ (cur_key >> 13)) & 0xFF - output_data.append(tmp ^ BINARY8_KEY[i&7]) - cur_key = ((54323 * cur_key) + 1) & 0xFFFFFFFF - - return output_data - -def encode_binary8(data, unk_key_byte): - cur_key = ((54323 * unk_key_byte) + 1) & 0xFFFFFFFF - - output_data = bytearray() - for i in range(len(data)): - output_data.append(data[i] ^ (BINARY8_KEY[i&7] ^ ((cur_key >> 13) & 0xFF))) - cur_key = ((54323 * cur_key) + 1) & 0xFFFFFFFF - - return output_data - - -SUM32_TABLE_0 = bytes([0x35, 0x7A, 0xAA, 0x97, 0x53, 0x66, 0x12]) -SUM32_TABLE_1 = bytes([0x7A, 0xAA, 0x97, 0x53, 0x66, 0x12, 0xDE, 0xDE, 0x35]) -def calc_sum32(data): - length = len(data) - - t0_i = length & 0xFF - t1_i = data[length >> 1] - - out = bytearray(4) - for i in range(len(data)): - t0_i += 1 - t1_i += 1 - - tmp = (SUM32_TABLE_1[t1_i % 9] ^ SUM32_TABLE_0[t0_i % 7]) ^ data[i] - out[i & 3] = (out[i & 3] + tmp) & 0xFF - - return Int32ub.parse(out) - -def read_binary8_part(stream): - # Read the header and decrypt the header first to get the size. - enc_bytes = bytearray(stream.read(12)) - dec_header_bytes = decode_binary8(enc_bytes[1:], enc_bytes[0]) - header = Binary8Header.parse(dec_header_bytes) - - # Then read the body, append to the header, and decrypt the full thing. - body_bytes = stream.read(header.body_size) - enc_bytes.extend(body_bytes) - dec_bytes = decode_binary8(enc_bytes[1:], enc_bytes[0]) - - # Then return the parsed header and just the raw body data. - return (enc_bytes[0], header, dec_bytes[11:], dec_bytes) - -def write_binary8_part(key, server_type, entry_count, payload): - body = Binary8Header.build(dict( - server_type=server_type, - entry_count=entry_count, - body_size=len(payload), - checksum=calc_sum32(payload), - )) - - temp = bytearray() - temp.extend(body) - temp.extend(payload) - - out = bytearray() - out.append(key) - out.extend(encode_binary8(temp, key)) - - return out - - -def pad_bytes_to_len(b, length): - out = bytearray(b) - diff = length-len(out) - out.extend(bytearray(diff)) - return bytes(out) - - - -def make_custom_entrance_server_resp(): - # Get the userinfo_data - with open('tw_server_list_resp.bin', 'rb') as f: - (key, header, data, raw_dec_bytes) = read_binary8_part(f) - userinfo_data = f.read() - hexdump(userinfo_data) - - server_count = 1 - - data = EntranceListComplete.build(dict( - server_type = b'SV2', - entry_count = server_count, - body_size = 0xFFFF, - checksum = 0xFFFFFFFF, - servers = [dict( - host_ip_4byte = 0x0100007F, #0x7F000001,#3377555739, - unk_1 = 16, - unk_2 = 0, - channel_count = 1, - server_type = 1, - color = 0, # Server activity. 0 = green, 1 = orange, 2 = blue - unk_6 = 3, - name = pad_bytes_to_len("AErupe Server @localhost".encode('shift-jis'), 66), - allowed_client_type_flags = 4096, # 4096(PC, PS3/PS4)?, 8192 == nothing? - channels = [dict( - port = 54001, - unk_1 = 16, - max_players = 100, - current_players = 0, - unk_4 = 0, - unk_5 = 0, - unk_6 = 0, - unk_7 = 0, - unk_8 = 0, - unk_9 = 0, - unk_10 = 319, - unk_11 = 248,#254, - unk_12 = 159,#255, - unk_13 = 12345 - )], - )] - )) - - print(data) - - reencoded = write_binary8_part(0, b'SV2', server_count, data[11:]) - with open('custom_entrance_server_resp.bin', 'wb') as f: - f.write(reencoded) - f.write(userinfo_data) - - - with open('custom_entrance_server_resp.bin', 'rb') as f: - (key, header, data, raw_dec_bytes) = read_binary8_part(f) - print(EntranceListComplete.parse(raw_dec_bytes[0:])) - - - - -make_custom_entrance_server_resp() - - -""" -with open('tw_server_list_resp.bin', 'rb') as f: - (key, header, data, raw_dec_bytes) = read_binary8_part(f) - print(EntranceListComplete.parse(raw_dec_bytes[0:])) -""" - -""" -with open('tw_server_list_resp.bin', 'rb') as f: - filedata = f.read() - - rdr = io.BytesIO(filedata) - - (key, header, data, raw_dec_bytes) = read_binary8_part(rdr) - userinfo_data = rdr.read() - - reencoded = write_binary8_part(key, header.server_type, header.entry_count, data) - - - hexdump(reencoded[:16]) - hexdump(filedata[:16]) - -""" - -""" - -with open('dec_bin8_data_dump.bin', 'rb') as f: - print("calc_sum32: {:X}".format(calc_sum32(f.read()))) - print("want: 74EF4928") -""" \ No newline at end of file