diff --git a/channel_server.go b/channel_server.go
index a72c0d0bc..f774e3bd6 100644
--- a/channel_server.go
+++ b/channel_server.go
@@ -3,28 +3,238 @@ package main
import (
"encoding/hex"
"fmt"
+ "io/ioutil"
"net"
"github.com/Andoryuuta/Erupe/network"
"github.com/Andoryuuta/byteframe"
)
+var loadDataCount int
+var getPaperDataCount int
+
+func handlePacket(cc *network.CryptConn, pkt []byte) {
+ defer func() {
+ if r := recover(); r != nil {
+ fmt.Println("Recovered from panic.")
+ }
+ }()
+
+ bf := byteframe.NewByteFrameFromBytes(pkt)
+ opcode := network.PacketID(bf.ReadUint16())
+
+ if opcode == network.MSG_SYS_EXTEND_THRESHOLD {
+ opcode = network.PacketID(bf.ReadUint16())
+ }
+
+ switch opcode {
+ case network.MSG_SYS_PING:
+ ackHandle := bf.ReadUint32()
+ _ = bf.ReadUint16()
+
+ bfw := byteframe.NewByteFrame()
+ bfw.WriteUint16(uint16(network.MSG_SYS_ACK))
+ bfw.WriteUint32(ackHandle)
+ bfw.WriteUint32(0)
+ bfw.WriteUint32(0)
+ cc.SendPacket(bfw.Data())
+ case network.MSG_SYS_TIME:
+ _ = bf.ReadUint8()
+ timestamp := bf.ReadUint32() // unix timestamp, e.g. 1577105879
+
+ bfw := byteframe.NewByteFrame()
+ bfw.WriteUint16(uint16(network.MSG_SYS_TIME))
+ bfw.WriteUint8(0)
+ bfw.WriteUint32(timestamp)
+ cc.SendPacket(bfw.Data())
+ case network.MSG_SYS_LOGIN:
+ ackHandle := bf.ReadUint32()
+ charID0 := bf.ReadUint32()
+ loginTokenNumber := bf.ReadUint32()
+ hardcodedZero0 := bf.ReadUint16()
+ requestVersion := bf.ReadUint16()
+ charID1 := bf.ReadUint32()
+ hardcodedZero1 := bf.ReadUint16()
+ loginTokenLength := bf.ReadUint16() // hardcoded to 0x11
+ loginTokenString := bf.ReadBytes(17)
+
+ _ = ackHandle
+ _ = charID0
+ _ = loginTokenNumber
+ _ = hardcodedZero0
+ _ = requestVersion
+ _ = charID1
+ _ = hardcodedZero1
+ _ = loginTokenLength
+ _ = loginTokenString
+
+ bfw := byteframe.NewByteFrame()
+ bfw.WriteUint16(uint16(network.MSG_SYS_ACK))
+ bfw.WriteUint32(ackHandle)
+ bfw.WriteUint64(0x000000005E00B9C2) // Timestamp?
+ cc.SendPacket(bfw.Data())
+
+ case network.MSG_MHF_ENUMERATE_EVENT:
+ fallthrough
+ case network.MSG_MHF_ENUMERATE_QUEST:
+ fallthrough
+ case network.MSG_MHF_ENUMERATE_RANKING:
+ fallthrough
+ case network.MSG_MHF_READ_MERCENARY_W:
+ fallthrough
+ case network.MSG_MHF_GET_ETC_POINTS:
+ fallthrough
+ case network.MSG_MHF_READ_GUILDCARD:
+ fallthrough
+ case network.MSG_MHF_READ_BEAT_LEVEL:
+ fallthrough
+ case network.MSG_MHF_GET_EARTH_STATUS:
+ fallthrough
+ case network.MSG_MHF_GET_EARTH_VALUE:
+ fallthrough
+ case network.MSG_MHF_GET_WEEKLY_SCHEDULE:
+ fallthrough
+ case network.MSG_MHF_LIST_MEMBER:
+ fallthrough
+ case network.MSG_MHF_LOAD_PLATE_DATA:
+ fallthrough
+ case network.MSG_MHF_LOAD_PLATE_BOX:
+ fallthrough
+ case network.MSG_MHF_LOAD_FAVORITE_QUEST:
+ fallthrough
+ case network.MSG_MHF_LOAD_PARTNER:
+ fallthrough
+ case network.MSG_MHF_GET_TOWER_INFO:
+ fallthrough
+ case network.MSG_MHF_LOAD_OTOMO_AIROU:
+ fallthrough
+ case network.MSG_MHF_LOAD_DECO_MYSET:
+ fallthrough
+ case network.MSG_MHF_LOAD_HUNTER_NAVI:
+ fallthrough
+ case network.MSG_MHF_GET_UD_SCHEDULE:
+ fallthrough
+ case network.MSG_MHF_GET_UD_INFO:
+ fallthrough
+ case network.MSG_MHF_GET_UD_MONSTER_POINT:
+ fallthrough
+ case network.MSG_MHF_GET_RAND_FROM_TABLE:
+ fallthrough
+ case network.MSG_MHF_ACQUIRE_MONTHLY_REWARD:
+ fallthrough
+ case network.MSG_MHF_GET_RENGOKU_RANKING_RANK:
+ fallthrough
+ case network.MSG_MHF_LOAD_PLATE_MYSET:
+ fallthrough
+ case network.MSG_MHF_LOAD_RENGOKU_DATA:
+ fallthrough
+ case network.MSG_MHF_ENUMERATE_SHOP:
+ fallthrough
+ case network.MSG_MHF_LOAD_SCENARIO_DATA:
+ fallthrough
+ case network.MSG_MHF_GET_BOOST_TIME_LIMIT:
+ fallthrough
+ case network.MSG_MHF_GET_BOOST_RIGHT:
+ fallthrough
+ case network.MSG_MHF_GET_REWARD_SONG:
+ fallthrough
+ case network.MSG_MHF_GET_GACHA_POINT:
+ fallthrough
+ case network.MSG_MHF_GET_KOURYOU_POINT:
+ fallthrough
+ case network.MSG_MHF_GET_ENHANCED_MINIDATA:
+
+ ackHandle := bf.ReadUint32()
+
+ data, err := ioutil.ReadFile(fmt.Sprintf("bin_resp/%s_resp.bin", opcode.String()))
+ if err != nil {
+ panic(err)
+ }
+
+ bfw := byteframe.NewByteFrame()
+ bfw.WriteUint16(uint16(network.MSG_SYS_ACK))
+ bfw.WriteUint32(ackHandle)
+ bfw.WriteBytes(data)
+ cc.SendPacket(bfw.Data())
+
+ case network.MSG_MHF_INFO_FESTA:
+ ackHandle := bf.ReadUint32()
+ _ = bf.ReadUint32()
+
+ data, err := ioutil.ReadFile(fmt.Sprintf("bin_resp/%s_resp.bin", opcode.String()))
+ if err != nil {
+ panic(err)
+ }
+
+ bfw := byteframe.NewByteFrame()
+ bfw.WriteUint16(uint16(network.MSG_SYS_ACK))
+ bfw.WriteUint32(ackHandle)
+ bfw.WriteBytes(data)
+ cc.SendPacket(bfw.Data())
+
+ case network.MSG_MHF_LOADDATA:
+ ackHandle := bf.ReadUint32()
+
+ data, err := ioutil.ReadFile(fmt.Sprintf("bin_resp/%s_resp%d.bin", opcode.String(), loadDataCount))
+ if err != nil {
+ panic(err)
+ }
+
+ bfw := byteframe.NewByteFrame()
+ bfw.WriteUint16(uint16(network.MSG_SYS_ACK))
+ bfw.WriteUint32(ackHandle)
+ bfw.WriteBytes(data)
+ cc.SendPacket(bfw.Data())
+
+ loadDataCount++
+ if loadDataCount > 1 {
+ loadDataCount = 0
+ }
+ case network.MSG_MHF_GET_PAPER_DATA:
+ ackHandle := bf.ReadUint32()
+
+ data, err := ioutil.ReadFile(fmt.Sprintf("bin_resp/%s_resp%d.bin", opcode.String(), getPaperDataCount))
+ if err != nil {
+ panic(err)
+ }
+
+ bfw := byteframe.NewByteFrame()
+ bfw.WriteUint16(uint16(network.MSG_SYS_ACK))
+ bfw.WriteUint32(ackHandle)
+ bfw.WriteBytes(data)
+ cc.SendPacket(bfw.Data())
+
+ getPaperDataCount++
+ if getPaperDataCount > 7 {
+ getPaperDataCount = 0
+ }
+ default:
+ break
+ }
+
+ fmt.Printf("Opcode: %s\n", opcode)
+ fmt.Printf("Data:\n%s\n", hex.Dump(pkt))
+
+ remainingData := bf.DataFromCurrent()
+ if len(remainingData) >= 2 && (opcode == network.MSG_SYS_TIME || opcode == network.MSG_MHF_INFO_FESTA) {
+ handlePacket(cc, remainingData)
+ }
+}
+
func handleChannelServerConnection(conn net.Conn) {
fmt.Println("Channel server got connection!")
// Unlike the sign and entrance server,
// the client DOES NOT initalize the channel connection with 8 NULL bytes.
cc := network.NewCryptConn(conn)
+
for {
pkt, err := cc.ReadPacket()
if err != nil {
return
}
- bf := byteframe.NewByteFrameFromBytes(pkt)
- opcode := network.PacketID(bf.ReadUint16())
- fmt.Printf("Opcode: %s\n", opcode)
- fmt.Printf("Data:\n%s\n", hex.Dump(pkt))
+ handlePacket(cc, pkt)
}
}
diff --git a/go.mod b/go.mod
index 7db1bbc26..f8802b520 100644
--- a/go.mod
+++ b/go.mod
@@ -7,5 +7,7 @@ require (
github.com/Andoryuuta/byteframe v0.0.0-20191219124302-41f4085eb4c0
github.com/BurntSushi/toml v0.3.1
github.com/golang-migrate/migrate v3.5.4+incompatible // indirect
+ github.com/gorilla/handlers v1.4.2
github.com/julienschmidt/httprouter v1.3.0
+ github.com/lib/pq v1.3.0
)
diff --git a/go.sum b/go.sum
index e7dcd6eee..a3ff7a999 100644
--- a/go.sum
+++ b/go.sum
@@ -2,9 +2,14 @@ github.com/Andoryuuta/binio v0.0.0-20160731013325-2c89946fb8c3 h1:N8pCiqpJAHyOO8
github.com/Andoryuuta/binio v0.0.0-20160731013325-2c89946fb8c3/go.mod h1:4WK1jUpH8NFdDiv7IJcBfyCIOMqKjZ15kcw5eBKALvc=
github.com/Andoryuuta/byteframe v0.0.0-20191219124302-41f4085eb4c0 h1:2pVgen9rh18IxSWxOa80bObcpyfrS6d5bJtZeCUN7rY=
github.com/Andoryuuta/byteframe v0.0.0-20191219124302-41f4085eb4c0/go.mod h1:koVyx+gN3TfE70rpOidywETVODk87304YpwW69Y27J4=
+github.com/Andoryuuta/erupe v0.0.0-20191219210047-7aef17f7d946 h1:Z20gk8dvCNRZuHEdeEyGVwbMs9IxyGs5gGU4zFN3aTs=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk=
+github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
+github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
+github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
diff --git a/launcher_server.go b/launcher_server.go
index 5a1fe4ff0..1cbe2542a 100644
--- a/launcher_server.go
+++ b/launcher_server.go
@@ -1,8 +1,12 @@
package main
import (
+ "fmt"
"net/http"
+ "net/http/httputil"
+ "os"
+ "github.com/gorilla/handlers"
"github.com/julienschmidt/httprouter"
)
@@ -11,15 +15,27 @@ func g6Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
}
+func serverUniqueName(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+ dump, err := httputil.DumpRequest(r, true)
+ if err != nil {
+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
+ return
+ }
+ fmt.Println(string(dump))
+
+ fmt.Fprintf(w, `OK`)
+}
+
// serveLauncherHTML is responsible for serving the launcher HTML and (HACK) serverlist.xml.
func serveLauncherHTML(listenAddr string) {
// Manually route the folder root to index.html? Is there a better way to do this?
router := httprouter.New()
router.GET("/g6_launcher/", g6Index)
+ router.GET("/server/unique.php", serverUniqueName)
static := httprouter.New()
static.ServeFiles("/*filepath", http.Dir("www"))
router.NotFound = static
- http.ListenAndServe(listenAddr, router)
+ http.ListenAndServe(listenAddr, handlers.LoggingHandler(os.Stdout, router))
}
diff --git a/main.go b/main.go
index cbbc8ce92..839196155 100644
--- a/main.go
+++ b/main.go
@@ -1,16 +1,43 @@
package main
import (
+ "database/sql"
"fmt"
"time"
+
+ "github.com/Andoryuuta/Erupe/signserver"
+ _ "github.com/lib/pq"
)
func main() {
fmt.Println("Starting!")
+ // Load the config.toml configuration.
+ // TODO(Andoryuuta): implement config loading.
+
+ // Create the postgres DB pool.
+ db, err := sql.Open("postgres", "host=localhost port=5432 user=postgres password=admin dbname=erupe sslmode=disable")
+ if err != nil {
+ panic(err)
+ }
+
+ // Test the DB connection.
+ err = db.Ping()
+ if err != nil {
+ panic(err)
+ }
+
+ // Finally start our server(s).
go serveLauncherHTML(":80")
go doEntranceServer(":53310")
- go doSignServer(":53312")
+
+ signServer := signserver.NewServer(
+ &signserver.Config{
+ DB: db,
+ ListenAddr: ":53312",
+ })
+ go signServer.Listen()
+
go doChannelServer(":54001")
for {
diff --git a/sign_server.go b/signserver/dsgn_resp.go
similarity index 68%
rename from sign_server.go
rename to signserver/dsgn_resp.go
index aee31cc32..d0bbedf95 100644
--- a/sign_server.go
+++ b/signserver/dsgn_resp.go
@@ -1,27 +1,6 @@
-package main
+package signserver
-import (
- "fmt"
- "io"
- "net"
-
- "github.com/Andoryuuta/Erupe/network"
- "github.com/Andoryuuta/byteframe"
-)
-
-/*
-var conf *config.Config
-
-func init() {
- c, err := config.LoadConfig("config.toml")
- if err != nil {
- panic(err)
- }
-
- conf = c
-
-}
-*/
+import "github.com/Andoryuuta/byteframe"
func paddedString(x string, size uint) []byte {
out := make([]byte, size)
@@ -42,6 +21,12 @@ func uint16PascalString(bf *byteframe.ByteFrame, x string) {
bf.WriteNullTerminatedBytes([]byte(x))
}
+func makeSignInFailureResp(respID RespID) []byte {
+ bf := byteframe.NewByteFrame()
+ bf.WriteUint8(uint8(respID))
+ return bf.Data()
+}
+
func makeSignInResp(username string) []byte {
bf := byteframe.NewByteFrame()
@@ -51,7 +36,7 @@ func makeSignInResp(username string) []byte {
bf.WriteUint8(1) // resp_code
bf.WriteUint8(0) // file/patch server count
- bf.WriteUint8(1) // entrance 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)
@@ -61,6 +46,9 @@ func makeSignInResp(username string) []byte {
// Array(this.entrance_server_count, PascalString(Byte, "utf8")),
uint8PascalString(bf, "localhost:53310")
+ uint8PascalString(bf, "")
+ uint8PascalString(bf, "")
+ uint8PascalString(bf, "mhf-n.capcom.com.tw")
///////////////////////////
// Characters:
@@ -87,6 +75,8 @@ func makeSignInResp(username string) []byte {
bf.WriteUint32(469153291) // character ID 469153291
bf.WriteUint16(30) // Exp, HR[x] is split by 0, 1, 30, 50, 99, 299, 998, 999
+ //44.204
+
/*
0=大劍/Big sword
1=重弩/Heavy crossbow
@@ -108,7 +98,7 @@ func makeSignInResp(username string) []byte {
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 ?????.
+ bf.WriteUint8(1) // Is new character, 1 replaces character name with ?????.
grMode := uint8(0)
bf.WriteUint8(1) // GR level if grMode == 0
bf.WriteUint8(grMode) // GR mode.
@@ -145,51 +135,3 @@ func makeSignInResp(username string) []byte {
return bf.Data()
}
-
-func handleSignServerConnection(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 {
- fmt.Println(err)
- return
- } else if n != len(nullInit) {
- fmt.Println("io.ReadFull couldn't read the full 8 byte init.")
- return
- }
-
- cc := network.NewCryptConn(conn)
- for {
- pkt, err := cc.ReadPacket()
- if err != nil {
- return
- }
-
- bf := byteframe.NewByteFrameFromBytes(pkt)
- loginType := string(bf.ReadNullTerminatedBytes())
- username := string(bf.ReadNullTerminatedBytes())
- password := string(bf.ReadNullTerminatedBytes())
- unk := string(bf.ReadNullTerminatedBytes())
- fmt.Printf("Got signin, type: %s, username: %s, password %s, unk: %s", loginType, username, password, unk)
-
- resp := makeSignInResp(username)
- cc.SendPacket(resp)
-
- }
-}
-
-func doSignServer(listenAddr string) {
- l, err := net.Listen("tcp", listenAddr)
- if err != nil {
- panic(err)
- }
- defer l.Close()
-
- for {
- conn, err := l.Accept()
- if err != nil {
- panic(err)
- }
- go handleSignServerConnection(conn)
- }
-}
diff --git a/signserver/respid.go b/signserver/respid.go
new file mode 100644
index 000000000..a55aed14a
--- /dev/null
+++ b/signserver/respid.go
@@ -0,0 +1,50 @@
+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.
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
+)
diff --git a/signserver/session.go b/signserver/session.go
new file mode 100644
index 000000000..235b6020b
--- /dev/null
+++ b/signserver/session.go
@@ -0,0 +1,107 @@
+package signserver
+
+import (
+ "database/sql"
+ "encoding/hex"
+ "fmt"
+ "net"
+ "sync"
+
+ "github.com/Andoryuuta/Erupe/network"
+ "github.com/Andoryuuta/byteframe"
+)
+
+// Session holds state for the sign server connection.
+type Session struct {
+ sync.Mutex
+ sid int
+ server *Server
+ rawConn *net.Conn
+ cryptConn *network.CryptConn
+}
+
+func (session *Session) fail() {
+ session.server.Lock()
+ delete(session.server.sessions, session.sid)
+ session.server.Unlock()
+
+}
+
+func (session *Session) work() {
+ for {
+ pkt, err := session.cryptConn.ReadPacket()
+ if err != nil {
+ session.fail()
+ return
+ }
+
+ err = session.handlePacket(pkt)
+ if err != nil {
+ session.fail()
+ return
+ }
+ }
+}
+
+func (session *Session) handlePacket(pkt []byte) error {
+ bf := byteframe.NewByteFrameFromBytes(pkt)
+ reqType := string(bf.ReadNullTerminatedBytes())
+ switch reqType {
+ case "DSGN:100":
+ session.handleDSGNRequest(bf)
+ break
+ case "DELETE:100":
+ loginTokenString := string(bf.ReadNullTerminatedBytes())
+ _ = loginTokenString
+ characterID := bf.ReadUint32()
+
+ fmt.Printf("Got delete request for character ID: %v\n", characterID)
+ fmt.Printf("remaining unknown data:\n%s\n", hex.Dump(bf.DataFromCurrent()))
+ default:
+ fmt.Printf("Got unknown request type %s, data:\n%s\n", reqType, hex.Dump(bf.DataFromCurrent()))
+ }
+
+ return nil
+}
+
+func (session *Session) handleDSGNRequest(bf *byteframe.ByteFrame) error {
+ reqUsername := string(bf.ReadNullTerminatedBytes())
+ reqPassword := string(bf.ReadNullTerminatedBytes())
+ reqUnk := string(bf.ReadNullTerminatedBytes())
+ fmt.Printf("Got sign in request:\n\tUsername: %s\n\tPassword %s\n\tUnk: %s\n", reqUsername, reqPassword, reqUnk)
+
+ // TODO(Andoryuuta): remove plaintext password storage if this ever becomes more than a toy project.
+ var (
+ id int
+ password string
+ )
+ err := session.server.db.QueryRow("SELECT id, password FROM users WHERE username = $1", reqUsername).Scan(&id, &password)
+ var serverRespBytes []byte
+ switch {
+ case err == sql.ErrNoRows:
+ fmt.Printf("No rows for username %s\n", reqUsername)
+ serverRespBytes = makeSignInFailureResp(SIGN_EAUTH)
+ break
+ case err != nil:
+ serverRespBytes = makeSignInFailureResp(SIGN_EABORT)
+ fmt.Println("Got error on SQL query!")
+ fmt.Println(err)
+ break
+ default:
+ if reqPassword == password {
+ fmt.Println("Passwords match!")
+ serverRespBytes = makeSignInResp(reqUsername)
+ } else {
+ fmt.Println("Passwords don't match!")
+ serverRespBytes = makeSignInFailureResp(SIGN_EPASS)
+ }
+
+ }
+
+ err = session.cryptConn.SendPacket(serverRespBytes)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/signserver/sign_server.go b/signserver/sign_server.go
new file mode 100644
index 000000000..17704f6b2
--- /dev/null
+++ b/signserver/sign_server.go
@@ -0,0 +1,79 @@
+package signserver
+
+import (
+ "database/sql"
+ "fmt"
+ "io"
+ "net"
+ "sync"
+
+ "github.com/Andoryuuta/Erupe/network"
+)
+
+// Config struct allows configuring the server.
+type Config struct {
+ DB *sql.DB
+ ListenAddr string
+}
+
+// Server is a MHF sign server.
+type Server struct {
+ sync.Mutex
+ sid int
+ sessions map[int]*Session
+ db *sql.DB
+ listenAddr string
+}
+
+// NewServer creates a new Server type.
+func NewServer(config *Config) *Server {
+ s := &Server{
+ sid: 0,
+ sessions: make(map[int]*Session),
+ db: config.DB,
+ listenAddr: config.ListenAddr,
+ }
+ return s
+}
+
+// Listen listens for new connections and accepts/serves them.
+func (s *Server) Listen() {
+ l, err := net.Listen("tcp", s.listenAddr)
+ if err != nil {
+ panic(err)
+ }
+ defer l.Close()
+
+ for {
+ conn, err := l.Accept()
+ if err != nil {
+ panic(err)
+ }
+
+ go s.handleConnection(s.sid, conn)
+ s.sid++
+ }
+}
+
+func (s *Server) handleConnection(sid int, conn net.Conn) {
+ // 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
+ }
+
+ session := &Session{
+ server: s,
+ rawConn: &conn,
+ cryptConn: network.NewCryptConn(conn),
+ }
+
+ s.Lock()
+ s.sessions[sid] = session
+ s.Unlock()
+
+ session.work()
+}