Port entrance server to Go

This commit is contained in:
Andrew Gutekanst
2020-01-01 08:46:36 +09:00
parent caf9fc91ed
commit 1c6b7ef257
4 changed files with 151 additions and 235 deletions

View File

@@ -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)

119
entranceserver/make_resp.go Normal file
View File

@@ -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()
}

View File

@@ -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{

225
test.py
View File

@@ -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")
"""