Config / DB now its own package

This commit is contained in:
stratic-dev
2024-10-12 22:37:42 +01:00
parent 44692e986e
commit 5f975c97b5
76 changed files with 3249 additions and 2359 deletions

318
server/sign/dbutils.go Normal file
View File

@@ -0,0 +1,318 @@
package sign
import (
"database/sql"
"errors"
"erupe-ce/config"
"erupe-ce/utils/db"
"erupe-ce/utils/mhfcourse"
"erupe-ce/utils/token"
"fmt"
"strings"
"time"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
)
func (server *SignServer) newUserChara(uid uint32) error {
var numNewChars int
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
err = database.QueryRow("SELECT COUNT(*) FROM characters WHERE user_id = $1 AND is_new_character = true", uid).Scan(&numNewChars)
if err != nil {
return err
}
// prevent users with an uninitialised character from creating more
if numNewChars >= 1 {
return err
}
_, err = database.Exec(`
INSERT INTO characters (
user_id, is_female, is_new_character, name, unk_desc_string,
hr, gr, weapon_type, last_login)
VALUES($1, False, True, '', '', 0, 0, 0, $2)`,
uid,
uint32(time.Now().Unix()),
)
if err != nil {
return err
}
return nil
}
func (server *SignServer) registerDBAccount(username string, password string) (uint32, error) {
var uid uint32
server.logger.Info("Creating user", zap.String("User", username))
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
// Create salted hash of user password
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return 0, err
}
err = database.QueryRow("INSERT INTO users (username, password, return_expires) VALUES ($1, $2, $3) RETURNING id", username, string(passwordHash), time.Now().Add(time.Hour*24*30)).Scan(&uid)
if err != nil {
return 0, err
}
return uid, nil
}
type character struct {
ID uint32 `db:"id"`
IsFemale bool `db:"is_female"`
IsNewCharacter bool `db:"is_new_character"`
Name string `db:"name"`
UnkDescString string `db:"unk_desc_string"`
HR uint16 `db:"hr"`
GR uint16 `db:"gr"`
WeaponType uint16 `db:"weapon_type"`
LastLogin uint32 `db:"last_login"`
}
func (server *SignServer) getCharactersForUser(uid uint32) ([]character, error) {
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
characters := make([]character, 0)
err = database.Select(&characters, "SELECT id, is_female, is_new_character, name, unk_desc_string, hr, gr, weapon_type, last_login FROM characters WHERE user_id = $1 AND deleted = false ORDER BY id", uid)
if err != nil {
return nil, err
}
return characters, nil
}
func (server *SignServer) getReturnExpiry(uid uint32) time.Time {
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
var returnExpiry, lastLogin time.Time
database.Get(&lastLogin, "SELECT COALESCE(last_login, now()) FROM users WHERE id=$1", uid)
if time.Now().Add((time.Hour * 24) * -90).After(lastLogin) {
returnExpiry = time.Now().Add(time.Hour * 24 * 30)
database.Exec("UPDATE users SET return_expires=$1 WHERE id=$2", returnExpiry, uid)
} else {
err := database.Get(&returnExpiry, "SELECT return_expires FROM users WHERE id=$1", uid)
if err != nil {
returnExpiry = time.Now()
database.Exec("UPDATE users SET return_expires=$1 WHERE id=$2", returnExpiry, uid)
}
}
database.Exec("UPDATE users SET last_login=$1 WHERE id=$2", time.Now(), uid)
return returnExpiry
}
func (server *SignServer) getLastCID(uid uint32) uint32 {
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
var lastPlayed uint32
_ = database.QueryRow("SELECT last_character FROM users WHERE id=$1", uid).Scan(&lastPlayed)
return lastPlayed
}
func (server *SignServer) getUserRights(uid uint32) uint32 {
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
var rights uint32
if uid != 0 {
_ = database.QueryRow("SELECT rights FROM users WHERE id=$1", uid).Scan(&rights)
_, rights = mhfcourse.GetCourseStruct(rights)
}
return rights
}
type members struct {
CID uint32 // Local character ID
ID uint32 `db:"id"`
Name string `db:"name"`
}
func (server *SignServer) getFriendsForCharacters(chars []character) []members {
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
friends := make([]members, 0)
for _, char := range chars {
friendsCSV := ""
err := database.QueryRow("SELECT friends FROM characters WHERE id=$1", char.ID).Scan(&friendsCSV)
friendsSlice := strings.Split(friendsCSV, ",")
friendQuery := "SELECT id, name FROM characters WHERE id="
for i := 0; i < len(friendsSlice); i++ {
friendQuery += friendsSlice[i]
if i+1 != len(friendsSlice) {
friendQuery += " OR id="
}
}
charFriends := make([]members, 0)
err = database.Select(&charFriends, friendQuery)
if err != nil {
continue
}
for i := range charFriends {
charFriends[i].CID = char.ID
}
friends = append(friends, charFriends...)
}
return friends
}
func (server *SignServer) getGuildmatesForCharacters(chars []character) []members {
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
guildmates := make([]members, 0)
for _, char := range chars {
var inGuild int
_ = database.QueryRow("SELECT count(*) FROM guild_characters WHERE character_id=$1", char.ID).Scan(&inGuild)
if inGuild > 0 {
var guildID int
err := database.QueryRow("SELECT guild_id FROM guild_characters WHERE character_id=$1", char.ID).Scan(&guildID)
if err != nil {
continue
}
charGuildmates := make([]members, 0)
err = database.Select(&charGuildmates, "SELECT character_id AS id, c.name FROM guild_characters gc JOIN characters c ON c.id = gc.character_id WHERE guild_id=$1 AND character_id!=$2", guildID, char.ID)
if err != nil {
continue
}
for i := range charGuildmates {
charGuildmates[i].CID = char.ID
}
guildmates = append(guildmates, charGuildmates...)
}
}
return guildmates
}
func (server *SignServer) deleteCharacter(cid int, token string, tokenID uint32) error {
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
if !server.validateToken(token, tokenID) {
return errors.New("invalid token")
}
var isNew bool
err = database.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", cid).Scan(&isNew)
if isNew {
_, err = database.Exec("DELETE FROM characters WHERE id = $1", cid)
} else {
_, err = database.Exec("UPDATE characters SET deleted = true WHERE id = $1", cid)
}
if err != nil {
return err
}
return nil
}
// Unused
func (server *SignServer) checkToken(uid uint32) (bool, error) {
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
var exists int
err = database.QueryRow("SELECT count(*) FROM sign_sessions WHERE user_id = $1", uid).Scan(&exists)
if err != nil {
return false, err
}
if exists > 0 {
return true, nil
}
return false, nil
}
func (server *SignServer) registerUidToken(uid uint32) (uint32, string, error) {
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
_token := token.Generate(16)
var tid uint32
err = database.QueryRow(`INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id`, uid, _token).Scan(&tid)
return tid, _token, err
}
func (server *SignServer) registerPsnToken(psn string) (uint32, string, error) {
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
_token := token.Generate(16)
var tid uint32
err = database.QueryRow(`INSERT INTO sign_sessions (psn_id, token) VALUES ($1, $2) RETURNING id`, psn, _token).Scan(&tid)
return tid, _token, err
}
func (server *SignServer) validateToken(token string, tokenID uint32) bool {
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
query := `SELECT count(*) FROM sign_sessions WHERE token = $1`
if tokenID > 0 {
query += ` AND id = $2`
}
var exists int
err = database.QueryRow(query, token, tokenID).Scan(&exists)
if err != nil || exists == 0 {
return false
}
return true
}
func (server *SignServer) validateLogin(user string, pass string) (uint32, RespID) {
database, err := db.GetDB() // Capture both return values
if err != nil {
server.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
var uid uint32
var passDB string
err = database.QueryRow(`SELECT id, password FROM users WHERE username = $1`, user).Scan(&uid, &passDB)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
server.logger.Info("User not found", zap.String("User", user))
if config.GetConfig().AutoCreateAccount {
uid, err = server.registerDBAccount(user, pass)
if err == nil {
return uid, SIGN_SUCCESS
} else {
return 0, SIGN_EABORT
}
}
return 0, SIGN_EAUTH
}
return 0, SIGN_EABORT
} else {
if bcrypt.CompareHashAndPassword([]byte(passDB), []byte(pass)) == nil {
var bans int
err = database.QueryRow(`SELECT count(*) FROM bans WHERE user_id=$1 AND expires IS NULL`, uid).Scan(&bans)
if err == nil && bans > 0 {
return uid, SIGN_EELIMINATE
}
err = database.QueryRow(`SELECT count(*) FROM bans WHERE user_id=$1 AND expires > now()`, uid).Scan(&bans)
if err == nil && bans > 0 {
return uid, SIGN_ESUSPEND
}
return uid, SIGN_SUCCESS
}
return 0, SIGN_EPASS
}
}

400
server/sign/dsgn_resp.go Normal file
View File

@@ -0,0 +1,400 @@
package sign
import (
"erupe-ce/utils/byteframe"
"erupe-ce/utils/db"
"erupe-ce/utils/gametime"
ps "erupe-ce/utils/pascalstring"
"erupe-ce/utils/stringsupport"
"erupe-ce/config"
"fmt"
"strings"
"time"
"go.uber.org/zap"
)
func (s *Session) makeSignResponse(uid uint32) []byte {
// Get the characters from the DB.
chars, err := s.server.getCharactersForUser(uid)
if len(chars) == 0 && uid != 0 {
err = s.server.newUserChara(uid)
if err == nil {
chars, err = s.server.getCharactersForUser(uid)
}
}
if err != nil {
s.logger.Warn("Error getting characters from DB", zap.Error(err))
}
bf := byteframe.NewByteFrame()
var tokenID uint32
var sessToken string
if uid == 0 && s.psn != "" {
tokenID, sessToken, err = s.server.registerPsnToken(s.psn)
} else {
tokenID, sessToken, err = s.server.registerUidToken(uid)
}
if err != nil {
bf.WriteUint8(uint8(SIGN_EABORT))
return bf.Data()
}
if s.client == PS3 && (config.GetConfig().PatchServerFile == "" || config.GetConfig().PatchServerManifest == "") {
bf.WriteUint8(uint8(SIGN_EABORT))
return bf.Data()
}
bf.WriteUint8(uint8(SIGN_SUCCESS))
bf.WriteUint8(2) // patch server count
bf.WriteUint8(1) // entrance server count
bf.WriteUint8(uint8(len(chars)))
bf.WriteUint32(tokenID)
bf.WriteBytes([]byte(sessToken))
bf.WriteUint32(uint32(gametime.TimeAdjusted().Unix()))
if s.client == PS3 {
ps.Uint8(bf, fmt.Sprintf("%s/ps3", config.GetConfig().PatchServerManifest), false)
ps.Uint8(bf, fmt.Sprintf("%s/ps3", config.GetConfig().PatchServerFile), false)
} else {
ps.Uint8(bf, config.GetConfig().PatchServerManifest, false)
ps.Uint8(bf, config.GetConfig().PatchServerFile, false)
}
if strings.Split(s.rawConn.RemoteAddr().String(), ":")[0] == "127.0.0.1" {
ps.Uint8(bf, fmt.Sprintf("127.0.0.1:%d", config.GetConfig().Entrance.Port), false)
} else {
ps.Uint8(bf, fmt.Sprintf("%s:%d", config.GetConfig().Host, config.GetConfig().Entrance.Port), false)
}
lastPlayed := uint32(0)
for _, char := range chars {
if lastPlayed == 0 {
lastPlayed = char.ID
}
bf.WriteUint32(char.ID)
if config.GetConfig().DebugOptions.MaxLauncherHR {
bf.WriteUint16(999)
} else {
bf.WriteUint16(char.HR)
}
bf.WriteUint16(char.WeaponType) // Weapon, 0-13.
bf.WriteUint32(char.LastLogin) // Last login date, unix timestamp in seconds.
bf.WriteBool(char.IsFemale) // Sex, 0=male, 1=female.
bf.WriteBool(char.IsNewCharacter) // Is new character, 1 replaces character name with ?????.
bf.WriteUint8(0) // Old GR
bf.WriteBool(true) // Use uint16 GR, no reason not to
bf.WriteBytes(stringsupport.PaddedString(char.Name, 16, true)) // Character name
bf.WriteBytes(stringsupport.PaddedString(char.UnkDescString, 32, false)) // unk str
if config.GetConfig().ClientID >= config.G7 {
bf.WriteUint16(char.GR)
bf.WriteUint8(0) // Unk
bf.WriteUint8(0) // Unk
}
}
friends := s.server.getFriendsForCharacters(chars)
if len(friends) == 0 {
bf.WriteUint8(0)
} else {
if len(friends) > 255 {
bf.WriteUint8(255)
bf.WriteUint16(uint16(len(friends)))
} else {
bf.WriteUint8(uint8(len(friends)))
}
for _, friend := range friends {
bf.WriteUint32(friend.CID)
bf.WriteUint32(friend.ID)
ps.Uint8(bf, friend.Name, true)
}
}
guildmates := s.server.getGuildmatesForCharacters(chars)
if len(guildmates) == 0 {
bf.WriteUint8(0)
} else {
if len(guildmates) > 255 {
bf.WriteUint8(255)
bf.WriteUint16(uint16(len(guildmates)))
} else {
bf.WriteUint8(uint8(len(guildmates)))
}
for _, guildmate := range guildmates {
bf.WriteUint32(guildmate.CID)
bf.WriteUint32(guildmate.ID)
ps.Uint8(bf, guildmate.Name, true)
}
}
if config.GetConfig().HideLoginNotice {
bf.WriteBool(false)
} else {
bf.WriteBool(true)
bf.WriteUint8(0)
bf.WriteUint8(0)
ps.Uint16(bf, strings.Join(config.GetConfig().LoginNotices[:], "<PAGE>"), true)
}
bf.WriteUint32(s.server.getLastCID(uid))
bf.WriteUint32(s.server.getUserRights(uid))
namNGWords := []string{}
msgNGWords := []string{}
filters := byteframe.NewByteFrame()
filters.SetLE()
filters.WriteNullTerminatedBytes([]byte("smc"))
smc := byteframe.NewByteFrame()
smc.SetLE()
smcData := []struct {
charGroup [][]rune
}{
{[][]rune{{'='}, {''}}},
{[][]rune{{')'}, {''}}},
{[][]rune{{'('}, {''}}},
{[][]rune{{'!'}, {''}}},
{[][]rune{{'/'}, {''}}},
{[][]rune{{'+'}, {''}}},
{[][]rune{{'&'}, {''}}},
{[][]rune{{'ぼ'}, {'ボ'}, {'ホ', '゙'}, {'ほ', '゙'}, {'ホ', '゙'}, {'ほ', '゛'}, {'ホ', '゛'}, {'ホ', '゛'}}},
{[][]rune{{'べ'}, {'ベ'}, {'ヘ', '゙'}, {'へ', '゙'}, {'ヘ', '゙'}, {'へ', '゛'}, {'ヘ', '゛'}, {'ヘ', '゛'}}},
{[][]rune{{'で'}, {'デ'}, {'テ', '゙'}, {'て', '゙'}, {'テ', '゙'}, {'て', '゛'}, {'テ', '゛'}, {'テ', '゛'}, {'〒', '゛'}, {'〒', '゙'}, {'乙', '゙'}, {'乙', '゛'}}},
{[][]rune{{'び'}, {'ビ'}, {'ヒ', '゙'}, {'ひ', '゙'}, {'ヒ', '゙'}, {'ひ', '゛'}, {'ヒ', '゛'}, {'ヒ', '゛'}}},
{[][]rune{{'ど'}, {'ド'}, {'ト', '゙'}, {'と', '゙'}, {'ト', '゙'}, {'と', '゛'}, {'ト', '゛'}, {'ト', '゛'}, {'┣', '゙'}, {'┣', '゛'}, {'├', '゙'}, {'├', '゛'}}},
{[][]rune{{'ば'}, {'バ'}, {'ハ', '゙'}, {'は', '゙'}, {'ハ', '゙'}, {'八', '゙'}, {'は', '゛'}, {'ハ', '゛'}, {'ハ', '゛'}, {'八', '゛'}}},
{[][]rune{{'つ', '゙'}, {'ヅ'}, {'ツ', '゙'}, {'つ', '゛'}, {'ツ', '゛'}, {'ツ', '゙'}, {'ツ', '゛'}, {'づ'}, {'っ', '゙'}, {'ッ', '゙'}, {'ッ', '゙'}, {'っ', '゛'}, {'ッ', '゛'}, {'ッ', '゛'}}},
{[][]rune{{'ぶ'}, {'ブ'}, {'フ', '゙'}, {'ヴ'}, {'ウ', '゙'}, {'う', '゛'}, {'う', '゙'}, {'ウ', '゙'}, {'ゥ', '゙'}, {'ぅ', '゙'}, {'ふ', '゙'}, {'フ', '゙'}, {'フ', '゛'}}},
{[][]rune{{'ぢ'}, {'ヂ'}, {'チ', '゙'}, {'ち', '゙'}, {'チ', '゙'}, {'ち', '゛'}, {'チ', '゛'}, {'チ', '゛'}, {'千', '゛'}, {'千', '゙'}}},
{[][]rune{{'だ'}, {'ダ'}, {'タ', '゙'}, {'た', '゙'}, {'タ', '゙'}, {'夕', '゙'}, {'た', '゛'}, {'タ', '゛'}, {'タ', '゛'}, {'夕', '゛'}}},
{[][]rune{{'ぞ'}, {'ゾ'}, {'ソ', '゙'}, {'そ', '゙'}, {'ソ', '゙'}, {'そ', '゛'}, {'ソ', '゛'}, {'ソ', '゛'}, {'ン', '゙'}, {'ン', '゛'}, {'ン', '゛'}, {'ン', '゙'}, {'リ', '゙'}, {'リ', '゙'}, {'リ', '゛'}, {'リ', '゛'}}},
{[][]rune{{'ぜ'}, {'セ', '゙'}, {'せ', '゙'}, {'セ', '゙'}, {'せ', '゛'}, {'セ', '゛'}, {'セ', '゛'}, {'ゼ'}}},
{[][]rune{{'ず'}, {'ズ'}, {'ス', '゙'}, {'す', '゙'}, {'ス', '゙'}, {'す', '゛'}, {'ス', '゛'}, {'ス', '゛'}}},
{[][]rune{{'じ'}, {'ジ'}, {'シ', '゙'}, {'し', '゙'}, {'シ', '゙'}, {'し', '゛'}, {'シ', '゛'}, {'シ', '゛'}}},
{[][]rune{{'ざ'}, {'ザ'}, {'サ', '゙'}, {'さ', '゙'}, {'サ', '゙'}, {'さ', '゛'}, {'サ', '゛'}, {'サ', '゛'}}},
{[][]rune{{'ご'}, {'ゴ'}, {'コ', '゙'}, {'こ', '゙'}, {'コ', '゙'}, {'こ', '゛'}, {'コ', '゛'}, {'コ', '゛'}}},
{[][]rune{{'げ'}, {'ゲ'}, {'ケ', '゙'}, {'け', '゙'}, {'ケ', '゙'}, {'け', '゛'}, {'ケ', '゛'}, {'ケ', '゛'}, {'ヶ', '゙'}, {'ヶ', '゛'}}},
{[][]rune{{'ぐ'}, {'グ'}, {'ク', '゙'}, {'く', '゙'}, {'ク', '゙'}, {'く', '゛'}, {'ク', '゛'}, {'ク', '゛'}}},
{[][]rune{{'ぎ'}, {'ギ'}, {'キ', '゙'}, {'き', '゙'}, {'キ', '゙'}, {'き', '゛'}, {'キ', '゛'}, {'キ', '゛'}}},
{[][]rune{{'が'}, {'ガ'}, {'カ', '゙'}, {'ヵ', '゙'}, {'カ', '゙'}, {'か', '゙'}, {'力', '゙'}, {'ヵ', '゛'}, {'カ', '゛'}, {'か', '゛'}, {'力', '゛'}, {'カ', '゛'}}},
{[][]rune{{'を'}, {'ヲ'}, {'ヲ'}}},
{[][]rune{{'わ'}, {'ワ'}, {'ワ'}, {'ヮ'}}},
{[][]rune{{'ろ'}, {'ロ'}, {'ロ'}, {'□'}, {'口'}}},
{[][]rune{{'れ'}, {'レ'}, {'レ'}}},
{[][]rune{{'る'}, {'ル'}, {'ル'}}},
{[][]rune{{'り'}, {'リ'}, {'リ'}}},
{[][]rune{{'ら'}, {'ラ'}, {'ラ'}}},
{[][]rune{{'よ'}, {'ヨ'}, {'ヨ'}, {'ョ'}, {'ょ'}, {'ョ'}}},
{[][]rune{{'ゆ'}, {'ユ'}, {'ユ'}, {'ュ'}, {'ゅ'}, {'ュ'}}},
{[][]rune{{'や'}, {'ヤ'}, {'ヤ'}, {'ャ'}, {'ゃ'}, {'ャ'}}},
{[][]rune{{'も'}, {'モ'}, {'モ'}}},
{[][]rune{{'め'}, {'メ'}, {'メ'}, {'M', 'E'}}},
{[][]rune{{'む'}, {'ム'}, {'ム'}}},
{[][]rune{{'み'}, {'ミ'}, {'ミ'}}},
{[][]rune{{'ま'}, {'マ'}, {'マ'}}},
{[][]rune{{'ほ'}, {'ホ'}, {'ホ'}}},
{[][]rune{{'へ'}, {'ヘ'}, {'ヘ'}}},
{[][]rune{{'ふ'}, {'フ'}, {'フ'}}},
{[][]rune{{'ひ'}, {'ヒ'}, {'ヒ'}}},
{[][]rune{{'は'}, {'ハ'}, {'ハ'}, {'八'}}},
{[][]rune{{'の'}, {''}, {'ノ'}}},
{[][]rune{{'ね'}, {'ネ'}, {'ネ'}}},
{[][]rune{{'ぬ'}, {'ヌ'}, {'ヌ'}}},
{[][]rune{{'に'}, {'ニ'}, {'ニ'}, {'二'}}},
{[][]rune{{'な'}, {'ナ'}, {'ナ'}}},
{[][]rune{{'と'}, {'ト'}, {'ト'}, {'┣'}, {'├'}}},
{[][]rune{{'て'}, {'テ'}, {'テ'}, {'〒'}, {'乙'}}},
{[][]rune{{'つ'}, {'ツ'}, {'ツ'}, {'っ'}, {'ッ'}, {'ッ'}}},
{[][]rune{{'ち'}, {'チ'}, {'チ'}, {'千'}}},
{[][]rune{{'た'}, {'タ'}, {'タ'}, {'夕'}}},
{[][]rune{{'そ'}, {'ソ'}, {'ソ'}}},
{[][]rune{{'せ'}, {'セ'}, {'セ'}}},
{[][]rune{{'す'}, {'ス'}, {'ス'}}},
{[][]rune{{'し'}, {'シ'}, {'シ'}}},
{[][]rune{{'さ'}, {'サ'}, {'サ'}}},
{[][]rune{{'こ'}, {'コ'}, {'コ'}}},
{[][]rune{{'け'}, {'ケ'}, {'ケ'}, {'ヶ'}}},
{[][]rune{{'く'}, {'ク'}, {'ク'}}},
{[][]rune{{'き'}, {'キ'}, {'キ'}}},
{[][]rune{{'か'}, {'カ'}, {'カ'}, {'ヵ'}, {'力'}}},
{[][]rune{{'お'}, {'オ'}, {'オ'}, {'ォ'}, {'ぉ'}, {'ォ'}}},
{[][]rune{{'え'}, {'エ'}, {'エ'}, {'ェ'}, {'ぇ'}, {'ェ'}, {'工'}}},
{[][]rune{{'う'}, {'ウ'}, {'ウ'}, {'ゥ'}, {'ぅ'}, {'ゥ'}}},
{[][]rune{{'い'}, {'イ'}, {'イ'}, {'ィ'}, {'ぃ'}, {'ィ'}}},
{[][]rune{{'あ'}, {'ア'}, {'ァ'}, {'ア'}, {'ぁ'}, {'ァ'}}},
{[][]rune{{'ー'}, {'―'}, {''}, {'-'}, {''}, {'ー'}, {'一'}}},
{[][]rune{{'9'}, {''}}},
{[][]rune{{'8'}, {''}}},
{[][]rune{{'7'}, {''}}},
{[][]rune{{'6'}, {''}}},
{[][]rune{{'5'}, {''}}},
{[][]rune{{'4'}, {''}}},
{[][]rune{{'3'}, {''}}},
{[][]rune{{'2'}, {''}}},
{[][]rune{{'1'}, {''}}},
{[][]rune{{'ぽ'}, {'ポ'}, {'ホ', '゚'}, {'ほ', '゚'}, {'ホ', '゚'}, {'ホ', '°'}, {'ほ', '°'}, {'ホ', '°'}}},
{[][]rune{{'ぺ'}, {'ペ'}, {'ヘ', '゚'}, {'へ', '゚'}, {'ヘ', '゚'}, {'ヘ', '°'}, {'へ', '°'}, {'ヘ', '°'}}},
{[][]rune{{'ぷ'}, {'プ'}, {'フ', '゚'}, {'ふ', '゚'}, {'フ', '゚'}, {'フ', '°'}, {'ふ', '°'}, {'フ', '°'}}},
{[][]rune{{'ぴ'}, {'ピ'}, {'ヒ', '゚'}, {'ひ', '゚'}, {'ヒ', '゚'}, {'ヒ', '°'}, {'ひ', '°'}, {'ヒ', '°'}}},
{[][]rune{{'ぱ'}, {'パ'}, {'ハ', '゚'}, {'は', '゚'}, {'ハ', '゚'}, {'ハ', '°'}, {'は', '°'}, {'ハ', '°'}, {'八', '゚'}, {'八', '゜'}}},
{[][]rune{{'z'}, {''}, {'Z'}, {''}, {'Ζ'}}},
{[][]rune{{'y'}, {''}, {'Y'}, {''}, {'Υ'}, {'У'}, {'у'}}},
{[][]rune{{'x'}, {''}, {'X'}, {''}, {'Χ'}, {'χ'}, {'Х'}, {'×'}, {'х'}}},
{[][]rune{{'w'}, {''}, {'W'}, {''}, {'ω'}, {'Ш'}, {'ш'}, {'щ'}}},
{[][]rune{{'v'}, {''}, {'V'}, {''}, {'ν'}, {'υ'}}},
{[][]rune{{'u'}, {''}, {'U'}, {''}, {'μ'}, {''}}},
{[][]rune{{'t'}, {''}, {'T'}, {''}, {'Τ'}, {'τ'}, {'Т'}, {'т'}}},
{[][]rune{{'s'}, {''}, {'S'}, {''}, {'∫'}, {''}, {'$'}}},
{[][]rune{{'r'}, {''}, {'R'}, {''}, {'Я'}, {'я'}}},
{[][]rune{{'q'}, {''}, {'Q'}, {''}}},
{[][]rune{{'p'}, {''}, {'P'}, {''}, {'Ρ'}, {'ρ'}, {'Р'}, {'р'}}},
{[][]rune{{'o'}, {''}, {'O'}, {''}, {'○'}, {'Ο'}, {'ο'}, {'О'}, {'о'}, {'◯'}, {''}, {'0'}, {''}}},
{[][]rune{{'n'}, {''}, {'N'}, {''}, {'Ν'}, {'η'}, {'ン'}, {'ん'}, {'ン'}}},
{[][]rune{{'m'}, {''}, {'M'}, {''}, {'Μ'}, {'М'}, {'м'}}},
{[][]rune{{'l'}, {''}, {'L'}, {''}, {'|'}}},
{[][]rune{{'k'}, {''}, {'K'}, {''}, {'Κ'}, {'κ'}, {'К'}, {'к'}}},
{[][]rune{{'j'}, {''}, {'J'}, {''}}},
{[][]rune{{'i'}, {''}, {'I'}, {''}, {'Ι'}}},
{[][]rune{{'h'}, {''}, {'H'}, {''}, {'Η'}, {'Н'}, {'н'}}},
{[][]rune{{'f'}, {''}, {'F'}, {''}}},
{[][]rune{{'g'}, {''}, {'G'}, {''}}},
{[][]rune{{'e'}, {''}, {'E'}, {''}, {'Ε'}, {'ε'}, {'Е'}, {'Ё'}, {'е'}, {'ё'}, {'∈'}}},
{[][]rune{{'d'}, {''}, {'D'}, {''}}},
{[][]rune{{'c'}, {''}, {'C'}, {'С'}, {'с'}, {''}, {'℃'}}},
{[][]rune{{'b'}, {''}, {''}, {'B'}, {'β'}, {'Β'}, {'В'}, {'в'}, {'ъ'}, {'ь'}, {'♭'}}},
{[][]rune{{'\''}, {''}}},
{[][]rune{{'a'}, {''}, {''}, {'A'}, {'α'}, {'@'}, {''}, {'а'}, {'Å'}, {'А'}, {'Α'}}},
{[][]rune{{'"'}, {'”'}}},
{[][]rune{{'%'}, {''}}},
}
for _, smcGroup := range smcData {
for _, smcPair := range smcGroup.charGroup {
smc.WriteUint16(stringsupport.ToNGWord(string(smcPair[0]))[0])
if len(smcPair) > 1 {
smc.WriteUint16(stringsupport.ToNGWord(string(smcPair[1]))[0])
} else {
smc.WriteUint16(0)
}
}
smc.WriteUint32(0)
}
filters.WriteUint32(uint32(len(smc.Data())))
filters.WriteBytes(smc.Data())
filters.WriteNullTerminatedBytes([]byte("nam"))
nam := byteframe.NewByteFrame()
nam.SetLE()
for _, word := range namNGWords {
parts := stringsupport.ToNGWord(word)
nam.WriteUint32(uint32(len(parts)))
for _, part := range parts {
nam.WriteUint16(part)
var i int16
j := int16(-1)
for _, smcGroup := range smcData {
if rune(part) == rune(stringsupport.ToNGWord(string(smcGroup.charGroup[0][0]))[0]) {
j = i
break
}
i += int16(len(smcGroup.charGroup) + 1)
}
nam.WriteInt16(j)
}
nam.WriteUint16(0)
nam.WriteInt16(-1)
}
filters.WriteUint32(uint32(len(nam.Data())))
filters.WriteBytes(nam.Data())
filters.WriteNullTerminatedBytes([]byte("msg"))
msg := byteframe.NewByteFrame()
msg.SetLE()
for _, word := range msgNGWords {
parts := stringsupport.ToNGWord(word)
msg.WriteUint32(uint32(len(parts)))
for _, part := range parts {
msg.WriteUint16(part)
var i int16
j := int16(-1)
for _, smcGroup := range smcData {
if rune(part) == rune(stringsupport.ToNGWord(string(smcGroup.charGroup[0][0]))[0]) {
j = i
break
}
i += int16(len(smcGroup.charGroup) + 1)
}
msg.WriteInt16(j)
}
msg.WriteUint16(0)
msg.WriteInt16(-1)
}
filters.WriteUint32(uint32(len(msg.Data())))
filters.WriteBytes(msg.Data())
bf.WriteUint16(uint16(len(filters.Data())))
bf.WriteBytes(filters.Data())
database, err := db.GetDB() // Capture both return values
if err != nil {
s.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
if s.client == VITA || s.client == PS3 || s.client == PS4 {
var psnUser string
database.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser)
bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true))
}
bf.WriteUint16(config.GetConfig().DebugOptions.CapLink.Values[0])
if config.GetConfig().DebugOptions.CapLink.Values[0] == 51728 {
bf.WriteUint16(config.GetConfig().DebugOptions.CapLink.Values[1])
if config.GetConfig().DebugOptions.CapLink.Values[1] == 20000 || config.GetConfig().DebugOptions.CapLink.Values[1] == 20002 {
ps.Uint16(bf, config.GetConfig().DebugOptions.CapLink.Key, false)
}
}
caStruct := []struct {
Unk0 uint8
Unk1 uint32
Unk2 string
}{}
bf.WriteUint8(uint8(len(caStruct)))
for i := range caStruct {
bf.WriteUint8(caStruct[i].Unk0)
bf.WriteUint32(caStruct[i].Unk1)
ps.Uint8(bf, caStruct[i].Unk2, false)
}
bf.WriteUint16(config.GetConfig().DebugOptions.CapLink.Values[2])
bf.WriteUint16(config.GetConfig().DebugOptions.CapLink.Values[3])
bf.WriteUint16(config.GetConfig().DebugOptions.CapLink.Values[4])
if config.GetConfig().DebugOptions.CapLink.Values[2] == 51729 && config.GetConfig().DebugOptions.CapLink.Values[3] == 1 && config.GetConfig().DebugOptions.CapLink.Values[4] == 20000 {
ps.Uint16(bf, fmt.Sprintf(`%s:%d`, config.GetConfig().DebugOptions.CapLink.Host, config.GetConfig().DebugOptions.CapLink.Port), false)
}
bf.WriteUint32(uint32(s.server.getReturnExpiry(uid).Unix()))
bf.WriteUint32(0)
tickets := []uint32{
config.GetConfig().GameplayOptions.MezFesSoloTickets,
config.GetConfig().GameplayOptions.MezFesGroupTickets,
}
stalls := []uint8{
10, 3, 6, 9, 4, 8, 5, 7,
}
if config.GetConfig().GameplayOptions.MezFesSwitchMinigame {
stalls[4] = 2
}
// We can just use the start timestamp as the event ID
bf.WriteUint32(uint32(gametime.TimeWeekStart().Unix()))
// Start time
bf.WriteUint32(uint32(gametime.TimeWeekNext().Add(-time.Duration(config.GetConfig().GameplayOptions.MezFesDuration) * time.Second).Unix()))
// End time
bf.WriteUint32(uint32(gametime.TimeWeekNext().Unix()))
bf.WriteUint8(uint8(len(tickets)))
for i := range tickets {
bf.WriteUint32(tickets[i])
}
bf.WriteUint8(uint8(len(stalls)))
for i := range stalls {
bf.WriteUint8(stalls[i])
}
return bf.Data()
}

48
server/sign/respid.go Normal file
View File

@@ -0,0 +1,48 @@
package sign
type RespID uint8
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
)

227
server/sign/session.go Normal file
View File

@@ -0,0 +1,227 @@
package sign
import (
"database/sql"
"encoding/hex"
"erupe-ce/config"
"erupe-ce/utils/db"
"erupe-ce/utils/logger"
"erupe-ce/utils/stringsupport"
"fmt"
"net"
"strings"
"sync"
"erupe-ce/network"
"erupe-ce/utils/byteframe"
"go.uber.org/zap"
)
type client int
const (
PC100 client = iota
VITA
PS3
PS4
WIIU
)
// Session holds state for the sign server connection.
type Session struct {
sync.Mutex
logger logger.Logger
server *SignServer
rawConn net.Conn
cryptConn *network.CryptConn
client client
psn string
}
func (s *Session) work() {
pkt, err := s.cryptConn.ReadPacket()
if config.GetConfig().DebugOptions.LogInboundMessages {
fmt.Printf("\n[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt))
}
if err != nil {
return
}
err = s.handlePacket(pkt)
if err != nil {
return
}
}
func (s *Session) handlePacket(pkt []byte) error {
bf := byteframe.NewByteFrameFromBytes(pkt)
reqType := string(bf.ReadNullTerminatedBytes())
switch reqType[:len(reqType)-3] {
case "DLTSKEYSIGN:", "DSGN:", "SIGN:":
s.handleDSGN(bf)
case "PS4SGN:":
s.client = PS4
s.handlePSSGN(bf)
case "PS3SGN:":
s.client = PS3
s.handlePSSGN(bf)
case "VITASGN:":
s.client = VITA
s.handlePSSGN(bf)
case "WIIUSGN:":
s.client = WIIU
s.handleWIIUSGN(bf)
case "VITACOGLNK:", "COGLNK:":
s.handlePSNLink(bf)
case "DELETE:":
token := string(bf.ReadNullTerminatedBytes())
characterID := int(bf.ReadUint32())
tokenID := bf.ReadUint32()
err := s.server.deleteCharacter(characterID, token, tokenID)
if err == nil {
s.logger.Info("Deleted character", zap.Int("CharacterID", characterID))
s.cryptConn.SendPacket([]byte{0x01}) // DEL_SUCCESS
}
default:
s.logger.Warn("Unknown request", zap.String("reqType", reqType))
if config.GetConfig().DebugOptions.LogInboundMessages {
fmt.Printf("\n[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt))
}
}
return nil
}
func (s *Session) authenticate(username string, password string) {
newCharaReq := false
if username[len(username)-1] == 43 { // '+'
username = username[:len(username)-1]
newCharaReq = true
}
bf := byteframe.NewByteFrame()
uid, resp := s.server.validateLogin(username, password)
switch resp {
case SIGN_SUCCESS:
if newCharaReq {
_ = s.server.newUserChara(uid)
}
bf.WriteBytes(s.makeSignResponse(uid))
default:
bf.WriteUint8(uint8(resp))
}
if config.GetConfig().DebugOptions.LogOutboundMessages {
fmt.Printf("\n[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(bf.Data()), hex.Dump(bf.Data()))
}
_ = s.cryptConn.SendPacket(bf.Data())
}
func (s *Session) handleWIIUSGN(bf *byteframe.ByteFrame) {
_ = bf.ReadBytes(1)
wiiuKey := string(bf.ReadBytes(64))
var uid uint32
database, err := db.GetDB() // Capture both return values
if err != nil {
s.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
err = database.QueryRow(`SELECT id FROM users WHERE wiiu_key = $1`, wiiuKey).Scan(&uid)
if err != nil {
if err == sql.ErrNoRows {
s.logger.Info("Unlinked Wii U attempted to authenticate", zap.String("Key", wiiuKey))
s.sendCode(SIGN_ECOGLINK)
return
}
s.sendCode(SIGN_EABORT)
return
}
s.cryptConn.SendPacket(s.makeSignResponse(uid))
}
func (s *Session) handlePSSGN(bf *byteframe.ByteFrame) {
// Prevent reading malformed request
if s.client != PS4 {
if len(bf.DataFromCurrent()) < 128 {
s.sendCode(SIGN_EABORT)
return
}
_ = bf.ReadNullTerminatedBytes() // VITA = 0000000256, PS3 = 0000000255
_ = bf.ReadBytes(2) // VITA = 1, PS3 = !
_ = bf.ReadBytes(82)
}
s.psn = string(bf.ReadNullTerminatedBytes())
var uid uint32
database, err := db.GetDB() // Capture both return values
if err != nil {
s.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
err = database.QueryRow(`SELECT id FROM users WHERE psn_id = $1`, s.psn).Scan(&uid)
if err != nil {
if err == sql.ErrNoRows {
s.cryptConn.SendPacket(s.makeSignResponse(0))
return
}
s.sendCode(SIGN_EABORT)
return
}
s.cryptConn.SendPacket(s.makeSignResponse(uid))
}
func (s *Session) handlePSNLink(bf *byteframe.ByteFrame) {
_ = bf.ReadNullTerminatedBytes() // Client ID
credentials := strings.Split(stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()), "\n")
token := string(bf.ReadNullTerminatedBytes())
uid, resp := s.server.validateLogin(credentials[0], credentials[1])
database, err := db.GetDB() // Capture both return values
if err != nil {
s.logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
if resp == SIGN_SUCCESS && uid > 0 {
var psn string
err := database.QueryRow(`SELECT psn_id FROM sign_sessions WHERE token = $1`, token).Scan(&psn)
if err != nil {
s.sendCode(SIGN_ECOGLINK)
return
}
// Since we check for the psn_id, this will never run
var exists int
err = database.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, psn).Scan(&exists)
if err != nil {
s.sendCode(SIGN_ECOGLINK)
return
} else if exists > 0 {
s.sendCode(SIGN_EPSI)
return
}
var currentPSN string
err = database.QueryRow(`SELECT COALESCE(psn_id, '') FROM users WHERE username = $1`, credentials[0]).Scan(&currentPSN)
if err != nil {
s.sendCode(SIGN_ECOGLINK)
return
} else if currentPSN != "" {
s.sendCode(SIGN_EMBID)
return
}
_, err = database.Exec(`UPDATE users SET psn_id = $1 WHERE username = $2`, psn, credentials[0])
if err == nil {
s.sendCode(SIGN_SUCCESS)
return
}
}
s.sendCode(SIGN_ECOGLINK)
}
func (s *Session) handleDSGN(bf *byteframe.ByteFrame) {
user := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
pass := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
_ = string(bf.ReadNullTerminatedBytes()) // Unk
s.authenticate(user, pass)
}
func (s *Session) sendCode(id RespID) {
s.cryptConn.SendPacket([]byte{byte(id)})
}

100
server/sign/sign_server.go Normal file
View File

@@ -0,0 +1,100 @@
package sign
import (
"fmt"
"io"
"net"
"sync"
"erupe-ce/config"
"erupe-ce/network"
"erupe-ce/utils/logger"
"go.uber.org/zap"
)
// SignServer is a MHF sign server.
type SignServer struct {
sync.Mutex
logger logger.Logger
sessions map[int]*Session
listener net.Listener
isShuttingDown bool
}
// NewServer creates a new Server type.
func NewServer() *SignServer {
s := &SignServer{
logger: logger.Get().Named("sign"),
}
return s
}
// Start starts the server in a new goroutine.
func (server *SignServer) Start() error {
l, err := net.Listen("tcp", fmt.Sprintf(":%d", config.GetConfig().Sign.Port))
if err != nil {
return err
}
server.listener = l
go server.acceptClients()
return nil
}
// Shutdown exits the server gracefully.
func (server *SignServer) Shutdown() {
server.logger.Debug("Shutting down...")
server.Lock()
server.isShuttingDown = true
server.Unlock()
// This will cause the acceptor goroutine to error and exit gracefully.
server.listener.Close()
}
func (server *SignServer) acceptClients() {
for {
conn, err := server.listener.Accept()
if err != nil {
// Check if we are shutting down and exit gracefully if so.
server.Lock()
shutdown := server.isShuttingDown
server.Unlock()
if shutdown {
break
} else {
panic(err)
}
}
go server.handleConnection(conn)
}
}
func (server *SignServer) handleConnection(conn net.Conn) {
server.logger.Debug("New connection", zap.String("RemoteAddr", conn.RemoteAddr().String()))
defer conn.Close()
// 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 {
server.logger.Error("Error initializing connection", zap.Error(err))
return
}
// Create a new session.
session := &Session{
logger: server.logger,
server: server,
rawConn: conn,
cryptConn: network.NewCryptConn(conn),
}
// Do the session's work.
session.work()
}