Change project dir structure

This commit is contained in:
Andrew Gutekanst
2020-01-13 17:32:49 -05:00
parent e5257eb6ed
commit 30219b8bcf
16 changed files with 4 additions and 4 deletions

View File

@@ -0,0 +1,137 @@
package signserver
import "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 uint8PascalString(bf *byteframe.ByteFrame, x string) {
bf.WriteUint8(uint8(len(x) + 1))
bf.WriteNullTerminatedBytes([]byte(x))
}
func uint16PascalString(bf *byteframe.ByteFrame, x string) {
bf.WriteUint16(uint16(len(x) + 1))
bf.WriteNullTerminatedBytes([]byte(x))
}
func makeSignInFailureResp(respID RespID) []byte {
bf := byteframe.NewByteFrame()
bf.WriteUint8(uint8(respID))
return bf.Data()
}
func (session *Session) makeSignInResp(username string) []byte {
bf := byteframe.NewByteFrame()
// delete me:
//bf.WriteUint8(8)
//return bf.Data()
bf.WriteUint8(1) // resp_code
bf.WriteUint8(0) // file/patch server count
bf.WriteUint8(4) // entrance server count
bf.WriteUint8(1) // character count
bf.WriteUint32(0xFFFFFFFF) // login_token_number
bf.WriteBytes(paddedString("logintokenstrng", 16)) // login_token (16 byte padded string)
bf.WriteUint32(1576761190)
// file patch server PascalStrings here
// Array(this.entrance_server_count, PascalString(Byte, "utf8")),
uint8PascalString(bf, "localhost:53310")
uint8PascalString(bf, "")
uint8PascalString(bf, "")
uint8PascalString(bf, "mhf-n.capcom.com.tw")
///////////////////////////
// Characters:
/*
tab = '123456789ABCDEFGHJKLMNPQRTUVWXYZ'
def make_uid_str(cid):
out = ''
for i in range(6):
v = (cid>>5*i)
out += tab[v&0x1f]
return out
def make_cid_int(uid):
v = 0
for c in uid[::-1]:
idx = tab.find(c)
if idx == -1:
raise Exception("not in tab")
v |= idx
v = v<<5
return v>>5
*/
bf.WriteUint32(469153291) // character ID 469153291
bf.WriteUint16(999) // Exp, HR[x] is split by 0, 1, 30, 50, 99, 299, 998, 999
//44.204
/*
0=大劍/Big sword
1=重弩/Heavy crossbow
2=大錘/Sledgehammer
3=長槍/Spear
4=單手劍/One-handed sword
5=輕弩/Light crossbow
6=雙劍/Double sword
7=太刀/Tadao
8=狩獵笛/Hunting flute
9=銃槍/Shotgun
10=弓/bow
11=穿龍棍/Wear a dragon stick
12=斬擊斧F/Chopping Axe F
13=---
default=不明/unknown
*/
bf.WriteUint16(7) // Weapon, 0-13.
bf.WriteUint32(1576761172) // Last login date, unix timestamp in seconds.
bf.WriteUint8(1) // Sex, 0=male, 1=female.
bf.WriteUint8(0) // Is new character, 1 replaces character name with ?????.
grMode := uint8(0)
bf.WriteUint8(1) // GR level if grMode == 0
bf.WriteUint8(grMode) // GR mode.
bf.WriteBytes(paddedString(username, 16)) // Character name
bf.WriteBytes(paddedString("0", 32)) // unk str
if grMode == 1 {
bf.WriteUint16(55) // GR level override.
bf.WriteUint8(0) // unk
bf.WriteUint8(0) // unk
}
//////////////////////////
bf.WriteUint8(0) // friends_list_count
bf.WriteUint8(0) // guild_members_count
bf.WriteUint8(0) // notice_count
bf.WriteUint32(0xDEADBEEF) // some_last_played_character_id
bf.WriteUint32(14) // unk_flags
uint8PascalString(bf, "") // unk_data_blob PascalString
bf.WriteUint16(51728)
bf.WriteUint16(20000)
uint16PascalString(bf, "1000672925")
bf.WriteUint8(0)
bf.WriteUint16(51729)
bf.WriteUint16(1)
bf.WriteUint16(20000)
uint16PascalString(bf, "203.191.249.36:8080")
bf.WriteUint32(1578905116)
bf.WriteUint32(0)
return bf.Data()
}

View File

@@ -0,0 +1,51 @@
package signserver
//revive:disable
type RespID uint16
//go:generate stringer -type=RespID
const (
SIGN_UNKNOWN RespID = iota
SIGN_SUCCESS
SIGN_EFAILED // Authentication server communication failed
SIGN_EILLEGAL // Incorrect input, authentication has been suspended
SIGN_EALERT // Authentication server process error
SIGN_EABORT // The internal procedure of the authentication server ended abnormally
SIGN_ERESPONSE // Procedure terminated due to abnormal certification report
SIGN_EDATABASE // Database connection failed
SIGN_EABSENCE
SIGN_ERESIGN
SIGN_ESUSPEND_D
SIGN_ELOCK
SIGN_EPASS
SIGN_ERIGHT
SIGN_EAUTH
SIGN_ESUSPEND // This account is temporarily suspended. Please contact customer service for details
SIGN_EELIMINATE // This account is permanently suspended. Please contact customer service for details
SIGN_ECLOSE
SIGN_ECLOSE_EX // Login process is congested. <br> Please try to sign in again later
SIGN_EINTERVAL
SIGN_EMOVED
SIGN_ENOTREADY
SIGN_EALREADY
SIGN_EIPADDR // Region block because of IP address.
SIGN_EHANGAME
SIGN_UPD_ONLY
SIGN_EMBID
SIGN_ECOGCODE
SIGN_ETOKEN
SIGN_ECOGLINK
SIGN_EMAINTE
SIGN_EMAINTE_NOUPDATE
// Couldn't find names for the following:
UNK_32
UNK_33
UNK_34
UNK_35
SIGN_XBRESPONSE
SIGN_EPSI
SIGN_EMBID_PSI
)

View File

@@ -0,0 +1,120 @@
package signserver
import (
"database/sql"
"encoding/hex"
"net"
"sync"
"github.com/Andoryuuta/Erupe/network"
"github.com/Andoryuuta/byteframe"
"go.uber.org/zap"
)
// Session holds state for the sign server connection.
type Session struct {
sync.Mutex
logger *zap.Logger
sid int
server *Server
rawConn *net.Conn
cryptConn *network.CryptConn
}
func (s *Session) fail() {
s.server.Lock()
delete(s.server.sessions, s.sid)
s.server.Unlock()
}
func (s *Session) work() {
for {
pkt, err := s.cryptConn.ReadPacket()
if err != nil {
s.fail()
return
}
err = s.handlePacket(pkt)
if err != nil {
s.fail()
return
}
}
}
func (s *Session) handlePacket(pkt []byte) error {
sugar := s.logger.Sugar()
bf := byteframe.NewByteFrameFromBytes(pkt)
reqType := string(bf.ReadNullTerminatedBytes())
switch reqType {
case "DLTSKEYSIGN:100":
fallthrough
case "DSGN:100":
err := s.handleDSGNRequest(bf)
if err != nil {
return nil
}
case "DELETE:100":
loginTokenString := string(bf.ReadNullTerminatedBytes())
_ = loginTokenString
characterID := bf.ReadUint32()
sugar.Infof("Got delete request for character ID: %v\n", characterID)
sugar.Infof("remaining unknown data:\n%s\n", hex.Dump(bf.DataFromCurrent()))
default:
sugar.Infof("Got unknown request type %s, data:\n%s\n", reqType, hex.Dump(bf.DataFromCurrent()))
}
return nil
}
func (s *Session) handleDSGNRequest(bf *byteframe.ByteFrame) error {
reqUsername := string(bf.ReadNullTerminatedBytes())
reqPassword := string(bf.ReadNullTerminatedBytes())
reqUnk := string(bf.ReadNullTerminatedBytes())
s.server.logger.Info(
"Got sign in request",
zap.String("reqUsername", reqUsername),
zap.String("reqPassword", reqPassword),
zap.String("reqUnk", reqUnk),
)
// TODO(Andoryuuta): remove plaintext password storage if this ever becomes more than a toy project.
var (
id int
password string
)
err := s.server.db.QueryRow("SELECT id, password FROM users WHERE username = $1", reqUsername).Scan(&id, &password)
var serverRespBytes []byte
switch {
case err == sql.ErrNoRows:
s.logger.Info("Account not found", zap.String("reqUsername", reqUsername))
serverRespBytes = makeSignInFailureResp(SIGN_EAUTH)
break
case err != nil:
serverRespBytes = makeSignInFailureResp(SIGN_EABORT)
s.logger.Warn("Got error on SQL query", zap.Error(err))
break
default:
if reqPassword == password {
s.logger.Info("Passwords match!")
serverRespBytes = s.makeSignInResp(reqUsername)
} else {
s.logger.Info("Passwords don't match!")
serverRespBytes = makeSignInFailureResp(SIGN_EPASS)
}
}
err = s.cryptConn.SendPacket(serverRespBytes)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,119 @@
package signserver
import (
"database/sql"
"fmt"
"io"
"net"
"sync"
"github.com/Andoryuuta/Erupe/config"
"github.com/Andoryuuta/Erupe/network"
"go.uber.org/zap"
)
// Config struct allows configuring the server.
type Config struct {
Logger *zap.Logger
DB *sql.DB
ErupeConfig *config.Config
}
// Server is a MHF sign server.
type Server struct {
sync.Mutex
logger *zap.Logger
erupeConfig *config.Config
sid int
sessions map[int]*Session
db *sql.DB
listener net.Listener
isShuttingDown bool
}
// NewServer creates a new Server type.
func NewServer(config *Config) *Server {
s := &Server{
logger: config.Logger,
erupeConfig: config.ErupeConfig,
sid: 0,
sessions: make(map[int]*Session),
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.Sign.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()
}
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 {
panic(err)
}
}
go s.handleConnection(s.sid, conn)
s.sid++
}
}
func (s *Server) handleConnection(sid int, conn net.Conn) {
s.logger.Info("Got connection to sign server", zap.String("remoteaddr", conn.RemoteAddr().String()))
// Client initalizes the connection with a one-time buffer of 8 NULL bytes.
nullInit := make([]byte, 8)
_, err := io.ReadFull(conn, nullInit)
if err != nil {
fmt.Println(err)
conn.Close()
return
}
// Create a new session.
session := &Session{
logger: s.logger,
server: s,
rawConn: &conn,
cryptConn: network.NewCryptConn(conn),
}
// Add the session to the server's sessions map.
s.Lock()
s.sessions[sid] = session
s.Unlock()
// Do the session's work.
session.work()
}