Merge branch 'main' into feature/diva

# Conflicts:
#	server/channelserver/handlers_quest.go
This commit is contained in:
wish
2022-11-12 21:12:30 +11:00
43 changed files with 726 additions and 728 deletions

View File

@@ -77,7 +77,13 @@ func updateRights(s *Session) {
s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt)
s.courses = mhfpacket.GetCourseStruct(rightsInt)
rights := []mhfpacket.ClientRight{{1, 0, 0}}
var netcafeBitSet bool
for _, course := range s.courses {
if (course.ID == 9 || course.ID == 26) && !netcafeBitSet {
netcafeBitSet = true
rightsInt += 0x40000000 // set netcafe bit
rights = append(rights, mhfpacket.ClientRight{ID: 30})
}
rights = append(rights, mhfpacket.ClientRight{ID: course.ID, Timestamp: 0x70DB59F0})
}
update := &mhfpacket.MsgSysUpdateRight{
@@ -135,6 +141,7 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
s.charID = pkt.CharID0
s.token = pkt.LoginTokenString
s.Unlock()
updateRights(s)
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // Unix timestamp
@@ -213,16 +220,16 @@ func logoutPlayer(s *Session) {
timePlayed += sessionTime
var rpGained int
if s.FindCourse("Netcafe").ID != 0 {
if s.FindCourse("NetCafe").ID != 0 || s.FindCourse("N").ID != 0 {
rpGained = timePlayed / 900
timePlayed = timePlayed % 900
s.server.db.Exec("UPDATE characters SET cafe_time=cafe_time+$1 WHERE id=$2", sessionTime, s.charID)
} else {
rpGained = timePlayed / 1800
timePlayed = timePlayed % 1800
}
s.server.db.Exec("UPDATE characters SET time_played = $1 WHERE id = $2", timePlayed, s.charID)
s.server.db.Exec("UPDATE characters SET cafe_time=cafe_time+$1 WHERE id=$2", sessionTime, s.charID)
treasureHuntUnregister(s)
@@ -1489,10 +1496,6 @@ func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetBbsSnsStatus(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfApplyBbsArticle(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetEtcPoints(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetEtcPoints)

View File

@@ -0,0 +1,41 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/common/token"
"erupe-ce/network/mhfpacket"
)
func handleMsgMhfGetBbsUserStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBbsUserStatus)
bf := byteframe.NewByteFrame()
bf.WriteUint32(200)
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetBbsSnsStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBbsSnsStatus)
bf := byteframe.NewByteFrame()
bf.WriteUint32(200)
bf.WriteUint32(401)
bf.WriteUint32(401)
bf.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfApplyBbsArticle(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfApplyBbsArticle)
bf := byteframe.NewByteFrame()
articleToken := token.Generate(40)
bf.WriteUint32(200)
bf.WriteUint32(80)
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteBytes(stringsupport.PaddedString(articleToken, 64, false))
bf.WriteBytes(stringsupport.PaddedString(s.server.erupeConfig.ScreenshotAPIURL, 64, false))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -74,9 +74,14 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
var cafeReset time.Time
err := s.server.db.QueryRow(`SELECT cafe_reset FROM characters WHERE id=$1`, s.charID).Scan(&cafeReset)
if err != nil {
cafeReset = TimeWeekNext()
s.server.db.Exec(`UPDATE characters SET cafe_reset=$1 WHERE id=$2`, cafeReset, s.charID)
}
if Time_Current_Adjusted().After(cafeReset) {
cafeReset = TimeWeekNext()
s.server.db.Exec(`UPDATE characters SET cafe_time=0, cafe_reset=$1 WHERE id=$2; DELETE FROM cafe_accepted WHERE character_id=$2`, cafeReset, s.charID)
s.server.db.Exec(`UPDATE characters SET cafe_time=0, cafe_reset=$1 WHERE id=$2`, cafeReset, s.charID)
s.server.db.Exec(`DELETE FROM cafe_accepted WHERE character_id=$1`, s.charID)
}
var cafeTime uint32
@@ -84,10 +89,13 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
if err != nil {
panic(err)
}
cafeTime = uint32(Time_Current_Adjusted().Unix()) - uint32(s.sessionStart) + cafeTime
if s.FindCourse("NetCafe").ID != 0 || s.FindCourse("N").ID != 0 {
cafeTime = uint32(Time_Current_Adjusted().Unix()) - uint32(s.sessionStart) + cafeTime
}
bf.WriteUint32(cafeTime) // Total cafe time
bf.WriteUint16(0)
ps.Uint16(bf, fmt.Sprintf("Resets on %s %d", cafeReset.Month().String(), cafeReset.Day()), true)
ps.Uint16(bf, fmt.Sprintf(s.server.dict["cafeReset"], int(cafeReset.Month()), cafeReset.Day()), true)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -324,7 +324,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
for _, alias := range course.Aliases {
if strings.ToLower(name) == strings.ToLower(alias) {
if slices.Contains(s.server.erupeConfig.Courses, config.Course{Name: course.Aliases[0], Enabled: true}) {
if s.FindCourse(name).Value != 0 {
if s.FindCourse(name).ID != 0 {
ei := slices.IndexFunc(s.courses, func(c mhfpacket.Course) bool {
for _, alias := range c.Aliases {
if strings.ToLower(name) == strings.ToLower(alias) {
@@ -335,20 +335,20 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
})
if ei != -1 {
s.courses = append(s.courses[:ei], s.courses[ei+1:]...)
sendServerChatMessage(s, fmt.Sprintf(`%s Course disabled.`, course.Aliases[0]))
sendServerChatMessage(s, fmt.Sprintf(`%s Course disabled`, course.Aliases[0]))
}
} else {
s.courses = append(s.courses, course)
sendServerChatMessage(s, fmt.Sprintf(`%s Course enabled.`, course.Aliases[0]))
sendServerChatMessage(s, fmt.Sprintf(`%s Course enabled`, course.Aliases[0]))
}
var newInt uint32
for _, course := range s.courses {
newInt += course.Value
newInt += uint32(math.Pow(2, float64(course.ID)))
}
s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", newInt, s.charID)
updateRights(s)
} else {
sendServerChatMessage(s, fmt.Sprintf(`%s Course is locked.`, course.Aliases[0]))
sendServerChatMessage(s, fmt.Sprintf(`%s Course is locked`, course.Aliases[0]))
}
}
}

View File

@@ -2005,6 +2005,7 @@ func handleMsgMhfCheckMonthlyItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCheckMonthlyItem)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
// TODO: Implement month-by-month tracker, 0 = Not claimed, 1 = Claimed
// Also handles HLC and EXC items, IDs = 064D, 076B
}
func handleMsgMhfAcquireMonthlyItem(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -62,10 +62,9 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
mail := &Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: "Invitation!",
Subject: s.server.dict["guildInviteName"],
Body: fmt.Sprintf(
"%s has invited you to join 「%s」\nDo you want to accept?",
getCharacterName(s, s.charID),
s.server.dict["guildInvite"],
guildInfo.Name,
),
IsGuildInvite: true,
@@ -149,30 +148,30 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
err = guild.AcceptApplication(s, s.charID)
mail = append(mail, Mail{
RecipientID: s.charID,
Subject: "Success!",
Body: fmt.Sprintf("You successfully joined 「%s」.", guild.Name),
Subject: s.server.dict["guildInviteSuccessName"],
Body: fmt.Sprintf(s.server.dict["guildInviteSuccess"], guild.Name),
IsSystemMessage: true,
})
mail = append(mail, Mail{
SenderID: s.charID,
RecipientID: pkt.LeaderID,
Subject: "Accepted",
Body: fmt.Sprintf("%s accepted your invitation to join 「%s」.", s.Name, guild.Name),
Subject: s.server.dict["guildInviteAcceptedName"],
Body: fmt.Sprintf(s.server.dict["guildInviteAccepted"], guild.Name),
IsSystemMessage: true,
})
} else {
err = guild.RejectApplication(s, s.charID)
mail = append(mail, Mail{
RecipientID: s.charID,
Subject: "Declined",
Body: fmt.Sprintf("You declined the invitation to join 「%s」.", guild.Name),
Subject: s.server.dict["guildInviteRejectName"],
Body: fmt.Sprintf(s.server.dict["guildInviteReject"], guild.Name),
IsSystemMessage: true,
})
mail = append(mail, Mail{
SenderID: s.charID,
RecipientID: pkt.LeaderID,
Subject: "Declined",
Body: fmt.Sprintf("%s declined your invitation to join 「%s」.", s.Name, guild.Name),
Subject: s.server.dict["guildInviteDeclined"],
Body: fmt.Sprintf(s.server.dict["guildInviteDeclined"], guild.Name),
IsSystemMessage: true,
})
}

View File

@@ -2,13 +2,13 @@ package channelserver
import (
"fmt"
"go.uber.org/zap"
"io"
"os"
"path/filepath"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -54,5 +54,3 @@ func handleMsgSysGetUserBinary(s *Session, p mhfpacket.MHFPacket) {
}
func handleMsgSysNotifyUserBinary(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetBbsUserStatus(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -52,6 +52,9 @@ type Server struct {
stagesLock sync.RWMutex
stages map[string]*Stage
// Used to map different languages
dict map[string]string
// UserBinary
userBinaryPartsLock sync.RWMutex
userBinaryParts map[userBinaryPartID][]byte
@@ -164,6 +167,8 @@ func NewServer(config *Config) *Server {
// MezFes
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
s.dict = getLangStrings(s)
return s
}
@@ -279,6 +284,7 @@ func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session,
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
pkt.Build(bf, session.clientContext)
bf.WriteUint16(0x0010)
session.QueueSendNonBlocking(bf.Data())
}
}
@@ -313,15 +319,17 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
var text string
switch _type {
case 2:
text = "<Great Slaying: Berserk> is being held!"
text = s.dict["ravienteBerserk"]
case 3:
text = s.dict["ravienteExtreme"]
case 4:
text = "<Great Slaying: Extreme> is being held!"
text = s.dict["ravienteExtremeLimited"]
case 5:
text = "<Great Slaying: Berserk Practice> is being held!"
text = s.dict["ravienteBerserkSmall"]
default:
s.logger.Error("Unk raviente type", zap.Uint8("_type", _type))
}
ps.Uint16(bf, text, false)
ps.Uint16(bf, text, true)
bf.WriteBytes([]byte{0x5F, 0x53, 0x00})
bf.WriteUint32(ip) // IP address
bf.WriteUint16(port) // Port

View File

@@ -0,0 +1,52 @@
package channelserver
func getLangStrings(s *Server) map[string]string {
strings := make(map[string]string)
switch s.erupeConfig.Language {
case "jp":
strings["language"] = "日本語"
strings["cafeReset"] = "%d/%dにリセット"
strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!"
strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!"
strings["ravienteExtremeLimited"] = "<大討伐:猛狂期【極】(制限付)>が開催されました!"
strings["ravienteBerserkSmall"] = "<大討伐:猛狂期(小数)>が開催されました!"
strings["guildInviteName"] = "猟団勧誘のご案内"
strings["guildInvite"] = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。"
strings["guildInviteSuccessName"] = "成功"
strings["guildInviteSuccess"] = "あなたは「%s」に参加できました。"
strings["guildInviteAcceptedName"] = "承諾されました"
strings["guildInviteAccepted"] = "招待した狩人が「%s」への招待を承諾しました。"
strings["guildInviteRejectName"] = "却下しました"
strings["guildInviteReject"] = "あなたは「%s」への参加を却下しました。"
strings["guildInviteDeclinedName"] = "辞退しました"
strings["guildInviteDeclined"] = "招待した狩人が「%s」への招待を辞退しました。"
default:
strings["language"] = "English"
strings["cafeReset"] = "Resets on %d/%d"
strings["ravienteBerserk"] = "<Great Slaying: Berserk> is being held!"
strings["ravienteExtreme"] = "<Great Slaying: Extreme> is being held!"
strings["ravienteExtremeLimited"] = "<Great Slaying: Extreme (Limited)> is being held!"
strings["ravienteBerserkSmall"] = "<Great Slaying: Berserk (Small)> is being held!"
strings["guildInviteName"] = "Invitation!"
strings["guildInvite"] = "You have been invited to join\n「%s」\nDo you want to accept?"
strings["guildInviteSuccessName"] = "Success!"
strings["guildInviteSuccess"] = "You have successfully joined\n「%s」."
strings["guildInviteAcceptedName"] = "Accepted"
strings["guildInviteAccepted"] = "The recipient accepted your invitation to join\n「%s」."
strings["guildInviteRejectName"] = "Rejected"
strings["guildInviteReject"] = "You rejected the invitation to join\n「%s」."
strings["guildInviteDeclinedName"] = "Declined"
strings["guildInviteDeclined"] = "The recipient declined your invitation to join\n「%s」."
}
return strings
}

View File

@@ -116,10 +116,9 @@ func (s *Session) QueueSend(data []byte) {
func (s *Session) QueueSendNonBlocking(data []byte) {
select {
case s.sendPackets <- packet{data, true}:
// Enqueued data
s.logMessage(binary.BigEndian.Uint16(data[0:2]), data, "Server", s.Name)
default:
s.logger.Warn("Packet queue too full, dropping!")
// Queue too full
}
}

View File

@@ -4,6 +4,7 @@ import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport"
"erupe-ce/common/token"
"erupe-ce/server/channelserver"
"fmt"
"math/rand"
@@ -18,15 +19,6 @@ func makeSignInFailureResp(respID RespID) []byte {
return bf.Data()
}
func randSeq(n int) string {
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
func (s *Session) makeSignInResp(uid int) []byte {
returnExpiry := s.server.getReturnExpiry(uid)
@@ -37,13 +29,13 @@ func (s *Session) makeSignInResp(uid int) []byte {
}
rand.Seed(time.Now().UnixNano())
token := randSeq(16)
s.server.registerToken(uid, token)
sessToken := token.Generate(16)
s.server.registerToken(uid, sessToken)
bf := byteframe.NewByteFrame()
bf.WriteUint8(1) // resp_code
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.PatchServerManifest != "" && s.server.erupeConfig.DevModeOptions.PatchServerFile != "" {
if s.server.erupeConfig.DevMode && s.server.erupeConfig.PatchServerManifest != "" && s.server.erupeConfig.PatchServerFile != "" {
bf.WriteUint8(2)
} else {
bf.WriteUint8(0)
@@ -51,12 +43,12 @@ func (s *Session) makeSignInResp(uid int) []byte {
bf.WriteUint8(1) // entrance server count
bf.WriteUint8(uint8(len(chars))) // character count
bf.WriteUint32(0xFFFFFFFF) // login_token_number
bf.WriteBytes([]byte(token)) // login_token
bf.WriteBytes([]byte(sessToken)) // login_token
bf.WriteUint32(uint32(time.Now().Unix())) // current time
if s.server.erupeConfig.DevMode {
if s.server.erupeConfig.DevModeOptions.PatchServerManifest != "" && s.server.erupeConfig.DevModeOptions.PatchServerFile != "" {
ps.Uint8(bf, s.server.erupeConfig.DevModeOptions.PatchServerManifest, false)
ps.Uint8(bf, s.server.erupeConfig.DevModeOptions.PatchServerFile, false)
if s.server.erupeConfig.PatchServerManifest != "" && s.server.erupeConfig.PatchServerFile != "" {
ps.Uint8(bf, s.server.erupeConfig.PatchServerManifest, false)
ps.Uint8(bf, s.server.erupeConfig.PatchServerFile, false)
}
}
ps.Uint8(bf, fmt.Sprintf("%s:%d", s.server.erupeConfig.Host, s.server.erupeConfig.Entrance.Port), false)
@@ -111,11 +103,11 @@ func (s *Session) makeSignInResp(uid int) []byte {
}
}
if s.server.erupeConfig.DevModeOptions.HideLoginNotice {
if s.server.erupeConfig.HideLoginNotice {
bf.WriteUint8(0)
} else {
bf.WriteUint8(1) // Notice count
noticeText := s.server.erupeConfig.DevModeOptions.LoginNotice
noticeText := s.server.erupeConfig.LoginNotice
ps.Uint32(bf, noticeText, true)
}

View File

@@ -0,0 +1,122 @@
package signv2server
import (
"context"
"database/sql"
"erupe-ce/common/token"
"fmt"
"time"
"golang.org/x/crypto/bcrypt"
)
func (s *Server) createNewUser(ctx context.Context, username string, password string) (int, error) {
// Create salted hash of user password
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return 0, err
}
var id int
err = s.db.QueryRowContext(
ctx, `
INSERT INTO users (username, password, return_expires)
VALUES ($1, $2, $3)
RETURNING id
`,
username, string(passwordHash), time.Now().Add(time.Hour*24*30),
).Scan(&id)
return id, err
}
func (s *Server) createLoginToken(ctx context.Context, uid int) (string, error) {
loginToken := token.Generate(16)
_, err := s.db.ExecContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2)", uid, loginToken)
if err != nil {
return "", err
}
return loginToken, nil
}
func (s *Server) userIDFromToken(ctx context.Context, token string) (int, error) {
var userID int
err := s.db.QueryRowContext(ctx, "SELECT user_id FROM sign_sessions WHERE token = $1", token).Scan(&userID)
if err == sql.ErrNoRows {
return 0, fmt.Errorf("invalid login token")
} else if err != nil {
return 0, err
}
return userID, nil
}
func (s *Server) createCharacter(ctx context.Context, userID int) (int, error) {
var charID int
err := s.db.QueryRowContext(ctx,
"SELECT id FROM characters WHERE is_new_character = true AND user_id = $1",
userID,
).Scan(&charID)
if err == sql.ErrNoRows {
err = s.db.QueryRowContext(ctx, `
INSERT INTO characters (
user_id, is_female, is_new_character, name, unk_desc_string,
hrp, gr, weapon_type, last_login
)
VALUES ($1, false, true, '', '', 0, 0, 0, $2)
RETURNING id`,
userID, uint32(time.Now().Unix()),
).Scan(&charID)
}
return charID, err
}
func (s *Server) deleteCharacter(ctx context.Context, userID int, charID int) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
_, err = tx.ExecContext(
ctx, `
DELETE FROM login_boost_state
WHERE char_id = $1`,
charID,
)
if err != nil {
return err
}
_, err = tx.ExecContext(
ctx, `
DELETE FROM guild_characters
WHERE character_id = $1`,
charID,
)
if err != nil {
return err
}
_, err = tx.ExecContext(
ctx, `
DELETE FROM characters
WHERE user_id = $1 AND id = $2`,
userID, charID,
)
if err != nil {
return err
}
return tx.Commit()
}
func (s *Server) getCharactersForUser(ctx context.Context, uid int) ([]Character, error) {
characters := make([]Character, 0)
err := s.db.SelectContext(
ctx, &characters, `
SELECT id, name, is_female, weapon_type, hrp, gr, last_login
FROM characters
WHERE user_id = $1 AND deleted = false AND is_new_character = false ORDER BY id ASC`,
uid,
)
if err != nil {
return nil, err
}
return characters, nil
}

View File

@@ -0,0 +1,208 @@
package signv2server
import (
"database/sql"
"encoding/json"
"errors"
"net/http"
"time"
"github.com/lib/pq"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
)
type LauncherMessage struct {
Message string `json:"message"`
Date int64 `json:"date"`
Link string `json:"link"`
}
type Character struct {
ID int `json:"id"`
Name string `json:"name"`
IsFemale bool `json:"isFemale" db:"is_female"`
Weapon int `json:"weapon" db:"weapon_type"`
HR int `json:"hr" db:"hrp"`
GR int `json:"gr"`
LastLogin int64 `json:"lastLogin" db:"last_login"`
}
func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) {
var respData struct {
Important []LauncherMessage `json:"important"`
Normal []LauncherMessage `json:"normal"`
}
respData.Important = []LauncherMessage{
{
Message: "Server Update 9 Released!",
Date: time.Date(2022, 8, 2, 0, 0, 0, 0, time.UTC).Unix(),
Link: "https://discord.com/channels/368424389416583169/929509970624532511/1003985850255818762",
},
{
Message: "Eng 2.0 & Ravi Patch Released!",
Date: time.Date(2022, 5, 3, 0, 0, 0, 0, time.UTC).Unix(),
Link: "https://discord.com/channels/368424389416583169/929509970624532511/969305400795078656",
},
{
Message: "Launcher Patch V1.0 Released!",
Date: time.Date(2022, 4, 24, 0, 0, 0, 0, time.UTC).Unix(),
Link: "https://discord.com/channels/368424389416583169/929509970624532511/969286397301248050",
},
}
respData.Normal = []LauncherMessage{
{
Message: "Join the community Discord for updates!",
Date: time.Date(2022, 4, 24, 0, 0, 0, 0, time.UTC).Unix(),
Link: "https://discord.gg/CFnzbhQ",
},
}
w.WriteHeader(200)
w.Header().Add("Content-Type", "application/json")
json.NewEncoder(w).Encode(respData)
}
func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
s.logger.Error("JSON decode error", zap.Error(err))
w.WriteHeader(400)
w.Write([]byte("Invalid data received"))
return
}
var (
userID int
password string
)
err := s.db.QueryRow("SELECT id, password FROM users WHERE username = $1", reqData.Username).Scan(&userID, &password)
if err == sql.ErrNoRows {
w.WriteHeader(400)
w.Write([]byte("Username does not exist"))
return
} else if err != nil {
s.logger.Warn("SQL query error", zap.Error(err))
w.WriteHeader(500)
return
}
if bcrypt.CompareHashAndPassword([]byte(password), []byte(reqData.Password)) != nil {
w.WriteHeader(400)
w.Write([]byte("Your password is incorrect"))
return
}
var respData struct {
Token string `json:"token"`
Characters []Character `json:"characters"`
}
respData.Token, err = s.createLoginToken(ctx, userID)
if err != nil {
s.logger.Warn("Error registering login token", zap.Error(err))
w.WriteHeader(500)
return
}
respData.Characters, err = s.getCharactersForUser(ctx, userID)
if err != nil {
s.logger.Warn("Error getting characters from DB", zap.Error(err))
w.WriteHeader(500)
return
}
w.WriteHeader(200)
w.Header().Add("Content-Type", "application/json")
json.NewEncoder(w).Encode(respData)
}
func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
s.logger.Error("JSON decode error", zap.Error(err))
w.WriteHeader(400)
w.Write([]byte("Invalid data received"))
return
}
s.logger.Info("Creating account", zap.String("username", reqData.Username))
userID, err := s.createNewUser(ctx, reqData.Username, reqData.Password)
if err != nil {
var pqErr *pq.Error
if errors.As(err, &pqErr) && pqErr.Constraint == "users_username_key" {
w.WriteHeader(400)
w.Write([]byte("User already exists"))
return
}
s.logger.Error("Error checking user", zap.Error(err), zap.String("username", reqData.Username))
w.WriteHeader(500)
return
}
var respData struct {
Token string `json:"token"`
}
respData.Token, err = s.createLoginToken(ctx, userID)
if err != nil {
s.logger.Error("Error registering login token", zap.Error(err))
w.WriteHeader(500)
return
}
json.NewEncoder(w).Encode(respData)
}
func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
Token string `json:"token"`
}
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
s.logger.Error("JSON decode error", zap.Error(err))
w.WriteHeader(400)
w.Write([]byte("Invalid data received"))
return
}
var respData struct {
CharID int `json:"id"`
}
userID, err := s.userIDFromToken(ctx, reqData.Token)
if err != nil {
w.WriteHeader(401)
return
}
respData.CharID, err = s.createCharacter(ctx, userID)
if err != nil {
s.logger.Error("Failed to create character", zap.Error(err), zap.String("token", reqData.Token))
w.WriteHeader(500)
return
}
json.NewEncoder(w).Encode(respData)
}
func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
Token string `json:"token"`
CharID int `json:"id"`
}
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
s.logger.Error("JSON decode error", zap.Error(err))
w.WriteHeader(400)
w.Write([]byte("Invalid data received"))
return
}
userID, err := s.userIDFromToken(ctx, reqData.Token)
if err != nil {
w.WriteHeader(401)
return
}
if err := s.deleteCharacter(ctx, userID, reqData.CharID); err != nil {
s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Int("charID", reqData.CharID))
w.WriteHeader(500)
return
}
json.NewEncoder(w).Encode(struct{}{})
}

View File

@@ -0,0 +1,89 @@
package signv2server
import (
"context"
"erupe-ce/config"
"fmt"
"net/http"
"os"
"sync"
"time"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
type Config struct {
Logger *zap.Logger
DB *sqlx.DB
ErupeConfig *config.Config
}
// Server is the MHF custom launcher sign server.
type Server struct {
sync.Mutex
logger *zap.Logger
erupeConfig *config.Config
db *sqlx.DB
httpServer *http.Server
isShuttingDown bool
}
// NewServer creates a new Server type.
func NewServer(config *Config) *Server {
s := &Server{
logger: config.Logger,
erupeConfig: config.ErupeConfig,
db: config.DB,
httpServer: &http.Server{},
}
return s
}
// Start starts the server in a new goroutine.
func (s *Server) Start() error {
// Set up the routes responsible for serving the launcher HTML, serverlist, unique name check, and JP auth.
r := mux.NewRouter()
r.HandleFunc("/launcher", s.Launcher)
r.HandleFunc("/login", s.Login)
r.HandleFunc("/register", s.Register)
r.HandleFunc("/character/create", s.CreateCharacter)
r.HandleFunc("/character/delete", s.DeleteCharacter)
handler := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}))(r)
s.httpServer.Handler = handlers.LoggingHandler(os.Stdout, handler)
s.httpServer.Addr = fmt.Sprintf(":%d", s.erupeConfig.SignV2.Port)
serveError := make(chan error, 1)
go func() {
if err := s.httpServer.ListenAndServe(); err != nil {
// Send error if any.
serveError <- err
}
}()
// Get the error from calling ListenAndServe, otherwise assume it's good after 250 milliseconds.
select {
case err := <-serveError:
return err
case <-time.After(250 * time.Millisecond):
return nil
}
}
// Shutdown exits the server gracefully.
func (s *Server) Shutdown() {
s.logger.Debug("Shutting down")
s.Lock()
s.isShuttingDown = true
s.Unlock()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := s.httpServer.Shutdown(ctx); err != nil {
// Just warn because we are shutting down the server anyway.
s.logger.Warn("Got error on httpServer shutdown", zap.Error(err))
}
}