mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-02-05 01:32:17 +01:00
Change project dir structure
This commit is contained in:
57
server/entranceserver/crypto.go
Normal file
57
server/entranceserver/crypto.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package entranceserver
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
var (
|
||||
_bin8Key = []byte{0x01, 0x23, 0x34, 0x45, 0x56, 0xAB, 0xCD, 0xEF}
|
||||
_sum32Table0 = []byte{0x7A, 0xAA, 0x97, 0x53, 0x66, 0x12, 0xDE, 0xDE, 0x35}
|
||||
_sum32Table1 = []byte{0x35, 0x7A, 0xAA, 0x97, 0x53, 0x66, 0x12}
|
||||
)
|
||||
|
||||
// CalcSum32 calculates the custom MHF "sum32" checksum of the given data.
|
||||
func CalcSum32(data []byte) uint32 {
|
||||
tableIdx0 := byte(len(data) & 0xFF)
|
||||
tableIdx1 := byte(data[len(data)>>1])
|
||||
|
||||
out := make([]byte, 4)
|
||||
for i := 0; i < len(data); i++ {
|
||||
tableIdx0++
|
||||
tableIdx1++
|
||||
|
||||
tmp := byte((_sum32Table0[tableIdx1%9] ^ _sum32Table1[tableIdx0%7]) ^ data[i])
|
||||
out[i&3] = (out[i&3] + tmp) & 0xFF
|
||||
|
||||
}
|
||||
|
||||
return binary.BigEndian.Uint32(out)
|
||||
}
|
||||
|
||||
// EncryptBin8 encrypts the given data using MHF's "binary8" encryption.
|
||||
func EncryptBin8(data []byte, key byte) []byte {
|
||||
curKey := uint32(((54323 * uint(key)) + 1) & 0xFFFFFFFF)
|
||||
|
||||
var output []byte
|
||||
for i := 0; i < len(data); i++ {
|
||||
tmp := (_bin8Key[i&7] ^ byte((curKey>>13)&0xFF))
|
||||
output = append(output, data[i]^tmp)
|
||||
curKey = uint32(((54323 * uint(curKey)) + 1) & 0xFFFFFFFF)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// DecryptBin8 decrypts the given MHF "binary8" data.
|
||||
func DecryptBin8(data []byte, key byte) []byte {
|
||||
curKey := uint32(((54323 * uint(key)) + 1) & 0xFFFFFFFF)
|
||||
|
||||
var output []byte
|
||||
for i := 0; i < len(data); i++ {
|
||||
tmp := (data[i] ^ byte((curKey>>13)&0xFF))
|
||||
output = append(output, tmp^_bin8Key[i&7])
|
||||
curKey = uint32(((54323 * uint(curKey)) + 1) & 0xFFFFFFFF)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
73
server/entranceserver/crypto_test.go
Normal file
73
server/entranceserver/crypto_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package entranceserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var tests = []struct {
|
||||
data []byte
|
||||
sum uint32
|
||||
encryptedData []byte
|
||||
encryptionKey byte
|
||||
}{
|
||||
{
|
||||
[]byte{0x4C, 0x6F, 0x72, 0x65, 0x6D, 0x20},
|
||||
0xAE6CA2C,
|
||||
[]byte{0x7E, 0x4C, 0x1D, 0x16, 0x9D, 0x46},
|
||||
0x55,
|
||||
},
|
||||
{
|
||||
[]byte{0x69, 0x70, 0x73, 0x75, 0x6D, 0x20, 0x64, 0x6F, 0x6C, 0x6F, 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6D, 0x65, 0x74, 0x2C, 0x20},
|
||||
0xCE5F1E96,
|
||||
[]byte{0x41, 0x65, 0xFF, 0x74, 0x64, 0x45, 0xB8, 0xB1, 0x18, 0xB0, 0x94, 0xA3, 0xF8, 0xD, 0xBF, 0x3C, 0xC8, 0x24, 0xE2, 0xEC, 0x3B, 0xCE},
|
||||
0x7A,
|
||||
},
|
||||
{
|
||||
[]byte{0x63, 0x6F, 0x6E, 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, 0x69, 0x73, 0x63, 0x69, 0x6E, 0x67, 0x20, 0x65, 0x6C, 0x69, 0x74, 0x2C, 0x20},
|
||||
0xF3EECEBB,
|
||||
[]byte{0xE, 0xBB, 0x19, 0xA5, 0xB9, 0x34, 0xFE, 0x51, 0x0, 0x61, 0x2D, 0x38, 0xB2, 0x98, 0xC2, 0xE0, 0x17, 0xDE, 0x6E, 0xE3, 0x6C, 0x1E, 0x19, 0xB6, 0x8C, 0x57, 0x32, 0x32, 0xD8},
|
||||
0xF8,
|
||||
},
|
||||
}
|
||||
|
||||
func TestSum32(t *testing.T) {
|
||||
for k, test := range tests {
|
||||
testname := fmt.Sprintf("sum32_test_%d", k)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
gotSum := CalcSum32(test.data)
|
||||
|
||||
if gotSum != test.sum {
|
||||
t.Errorf("got sum32 0x%X, want 0x%X", gotSum, test.sum)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptBin8(t *testing.T) {
|
||||
for k, test := range tests {
|
||||
testname := fmt.Sprintf("encrypt_bin8_test_%d", k)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
gotEncData := EncryptBin8(test.data, test.encryptionKey)
|
||||
|
||||
if !bytes.Equal(gotEncData, test.encryptedData) {
|
||||
t.Errorf("got\n\t%s\nwant\n\t%s", hex.Dump(gotEncData), hex.Dump(test.encryptedData))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptBin8(t *testing.T) {
|
||||
for k, test := range tests {
|
||||
testname := fmt.Sprintf("decrypt_bin8_test_%d", k)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
gotDecData := DecryptBin8(test.encryptedData, test.encryptionKey)
|
||||
|
||||
if !bytes.Equal(gotDecData, test.data) {
|
||||
t.Errorf("got\n\t%s\nwant\n\t%s", hex.Dump(gotDecData), hex.Dump(test.data))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
120
server/entranceserver/entrance_server.go
Normal file
120
server/entranceserver/entrance_server.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package entranceserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/Andoryuuta/Erupe/config"
|
||||
"github.com/Andoryuuta/Erupe/network"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Server is a MHF entrance server.
|
||||
type Server struct {
|
||||
sync.Mutex
|
||||
logger *zap.Logger
|
||||
erupeConfig *config.Config
|
||||
db *sql.DB
|
||||
listener net.Listener
|
||||
isShuttingDown bool
|
||||
}
|
||||
|
||||
// Config struct allows configuring the server.
|
||||
type Config struct {
|
||||
Logger *zap.Logger
|
||||
DB *sql.DB
|
||||
ErupeConfig *config.Config
|
||||
}
|
||||
|
||||
// NewServer creates a new Server type.
|
||||
func NewServer(config *Config) *Server {
|
||||
s := &Server{
|
||||
logger: config.Logger,
|
||||
erupeConfig: config.ErupeConfig,
|
||||
db: config.DB,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Start starts the server in a new goroutine.
|
||||
func (s *Server) Start() error {
|
||||
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%d", s.erupeConfig.Entrance.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.listener = l
|
||||
|
||||
go s.acceptClients()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown exits the server gracefully.
|
||||
func (s *Server) Shutdown() {
|
||||
s.logger.Debug("Shutting down")
|
||||
|
||||
s.Lock()
|
||||
s.isShuttingDown = true
|
||||
s.Unlock()
|
||||
|
||||
// This will cause the acceptor goroutine to error and exit gracefully.
|
||||
s.listener.Close()
|
||||
}
|
||||
|
||||
//acceptClients handles accepting new clients in a loop.
|
||||
func (s *Server) acceptClients() {
|
||||
for {
|
||||
conn, err := s.listener.Accept()
|
||||
if err != nil {
|
||||
// Check if we are shutting down and exit gracefully if so.
|
||||
s.Lock()
|
||||
shutdown := s.isShuttingDown
|
||||
s.Unlock()
|
||||
|
||||
if shutdown {
|
||||
break
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Start a new goroutine for the connection so that we don't block other incoming connections.
|
||||
go s.handleEntranceServerConnection(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleEntranceServerConnection(conn net.Conn) {
|
||||
// Client initalizes the connection with a one-time buffer of 8 NULL bytes.
|
||||
nullInit := make([]byte, 8)
|
||||
n, err := io.ReadFull(conn, nullInit)
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to read 8 NULL init", zap.Error(err))
|
||||
return
|
||||
} else if n != len(nullInit) {
|
||||
s.logger.Warn("io.ReadFull couldn't read the full 8 byte init.")
|
||||
return
|
||||
}
|
||||
|
||||
// Create a new encrypted connection handler and read a packet from it.
|
||||
cc := network.NewCryptConn(conn)
|
||||
pkt, err := cc.ReadPacket()
|
||||
if err != nil {
|
||||
s.logger.Warn("Error reading packet", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Debug("Got entrance server command:\n", zap.String("raw", hex.Dump(pkt)))
|
||||
|
||||
data := makeResp(s.erupeConfig.Entrance.Entries)
|
||||
cc.SendPacket(data)
|
||||
|
||||
// Close because we only need to send the response once.
|
||||
// Any further requests from the client will come from a new connection.
|
||||
conn.Close()
|
||||
}
|
||||
86
server/entranceserver/make_resp.go
Normal file
86
server/entranceserver/make_resp.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package entranceserver
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
"github.com/Andoryuuta/Erupe/config"
|
||||
"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
|
||||
}
|
||||
|
||||
func encodeServerInfo(serverInfos []config.EntranceServerInfo) []byte {
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
for serverIdx, si := range serverInfos {
|
||||
bf.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(si.IP).To4()))
|
||||
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 []config.EntranceServerInfo) []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()
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user