diff --git a/bundled-schema/GachaDemo.sql b/bundled-schema/GachaDemo.sql new file mode 100644 index 000000000..b32c1c3ac --- /dev/null +++ b/bundled-schema/GachaDemo.sql @@ -0,0 +1,102 @@ +BEGIN; + +-- Start Normal Demo +INSERT INTO gacha_shop (min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden) + VALUES (0, 0, 'Normal Demo', + 'http://img4.imagetitan.com/img4/QeRWNAviFD8UoTx/26/26_template_innerbanner.png', + 'http://img4.imagetitan.com/img4/QeRWNAviFD8UoTx/26/26_template_feature.png', + 'http://img4.imagetitan.com/img4/small/26/26_template_outerbanner.png', + false, false, 0, false); + +-- Create two different 'rolls', the first rolls once for 1z, the second rolls eleven times for 10z +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) +VALUES + ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 0, 10, 1, 0, 0, 0, 1, 0, 0), + ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 1, 10, 10, 0, 0, 0, 11, 0, 0); + +-- Creates a prize of 1z with a weighted chance of 100 +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) + VALUES ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 100, 0, 0, 0, 100, 0, 0, 0, 0); +INSERT INTO gacha_items (entry_id, item_type, item_id, quantity) + VALUES ((SELECT id FROM gacha_entries ORDER BY id DESC LIMIT 1), 10, 1, 0); + +-- Creates a prize of 2z with a weighted chance of 70 +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) + VALUES ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 100, 0, 0, 0, 70, 1, 0, 0, 0); +INSERT INTO gacha_items (entry_id, item_type, item_id, quantity) + VALUES ((SELECT id FROM gacha_entries ORDER BY id DESC LIMIT 1), 10, 2, 0); + +-- Creates a prize of 3z with a weighted chance of 10 +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) + VALUES ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 100, 0, 0, 0, 10, 2, 0, 0, 0); +INSERT INTO gacha_items (entry_id, item_type, item_id, quantity) + VALUES ((SELECT id FROM gacha_entries ORDER BY id DESC LIMIT 1), 10, 3, 0); +-- End Normal Demo + +-- Start Step-Up Demo +INSERT INTO gacha_shop (min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden) +VALUES (0, 0, 'Step-Up Demo', '', '', '', false, false, 1, false); + +-- Create two 'steps', the first costs 1z, the second costs 2z +-- The first step has zero rolls so it will only give the prizes directly linked to the entry ID, being 1z +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) + VALUES ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 0, 10, 1, 0, 0, 0, 0, 0, 0); +INSERT INTO gacha_items (entry_id, item_type, item_id, quantity) + VALUES ((SELECT id FROM gacha_entries ORDER BY id DESC LIMIT 1), 10, 1, 0); + +-- The second step has one roll on the random prize list as will as the direct prize, being 3z +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) + VALUES ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 1, 10, 2, 0, 0, 0, 1, 0, 0); +INSERT INTO gacha_items (entry_id, item_type, item_id, quantity) + VALUES ((SELECT id FROM gacha_entries ORDER BY id DESC LIMIT 1), 10, 3, 0); + +-- Set up two random prizes, the first gives 1z, the second gives 2z +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) + VALUES ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 100, 0, 0, 0, 100, 0, 0, 0, 0); +INSERT INTO gacha_items (entry_id, item_type, item_id, quantity) + VALUES ((SELECT id FROM gacha_entries ORDER BY id DESC LIMIT 1), 10, 1, 0); + +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) + VALUES ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 100, 0, 0, 0, 90, 1, 0, 0, 0); +INSERT INTO gacha_items (entry_id, item_type, item_id, quantity) + VALUES ((SELECT id FROM gacha_entries ORDER BY id DESC LIMIT 1), 10, 2, 0); +-- End Step-Up Demo + +-- Start Box Demo +INSERT INTO gacha_shop (min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden) +VALUES (0, 0, 'Box Demo', '', '', '', false, false, 4, false); + +-- Create two different 'rolls', the first rolls once for 1z, the second rolls twice for 2z +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) +VALUES + ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 0, 10, 1, 0, 0, 0, 1, 0, 0), + ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 1, 10, 2, 0, 0, 0, 2, 0, 0); + +-- Create five different 'Box' items, weight is always 0 for these +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) + VALUES ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 100, 0, 0, 0, 0, 0, 0, 0, 0); +INSERT INTO gacha_items (entry_id, item_type, item_id, quantity) + VALUES ((SELECT id FROM gacha_entries ORDER BY id DESC LIMIT 1), 10, 1, 0); + +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) + VALUES ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 100, 0, 0, 0, 0, 0, 0, 0, 0); +INSERT INTO gacha_items (entry_id, item_type, item_id, quantity) + VALUES ((SELECT id FROM gacha_entries ORDER BY id DESC LIMIT 1), 10, 1, 0); + +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) + VALUES ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 100, 0, 0, 0, 0, 0, 0, 0, 0); +INSERT INTO gacha_items (entry_id, item_type, item_id, quantity) + VALUES ((SELECT id FROM gacha_entries ORDER BY id DESC LIMIT 1), 10, 1, 0); + +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) + VALUES ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 100, 0, 0, 0, 0, 0, 0, 0, 0); +INSERT INTO gacha_items (entry_id, item_type, item_id, quantity) + VALUES ((SELECT id FROM gacha_entries ORDER BY id DESC LIMIT 1), 10, 2, 0); + +INSERT INTO gacha_entries (gacha_id, entry_type, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points) + VALUES ((SELECT id FROM gacha_shop ORDER BY id DESC LIMIT 1), 100, 0, 0, 0, 0, 0, 0, 0, 0); +INSERT INTO gacha_items (entry_id, item_type, item_id, quantity) + VALUES ((SELECT id FROM gacha_entries ORDER BY id DESC LIMIT 1), 10, 3, 0); +-- End Box Demo + +END; \ No newline at end of file diff --git a/common/mhfcourse/mhfcourse.go b/common/mhfcourse/mhfcourse.go new file mode 100644 index 000000000..13496119b --- /dev/null +++ b/common/mhfcourse/mhfcourse.go @@ -0,0 +1,102 @@ +package mhfcourse + +import ( + "golang.org/x/exp/slices" + "math" + "time" +) + +type Course struct { + ID uint16 + Expiry time.Time +} + +var aliases = map[uint16][]string{ + 1: {"Trial", "TL"}, + 2: {"HunterLife", "HL"}, + 3: {"Extra", "ExtraA", "EX"}, + 4: {"ExtraB"}, + 5: {"Mobile"}, + 6: {"Premium"}, + 7: {"Pallone", "ExtraC"}, + 8: {"Assist", "***ist", "Legend", "Rasta"}, + 9: {"N"}, + 10: {"Hiden", "Secret"}, + 11: {"HunterSupport", "HunterAid", "Support", "Aid", "Royal"}, + 12: {"NBoost", "NetCafeBoost", "Boost"}, + // 13-19 show up as (unknown) + 20: {"DEBUG"}, + 21: {"COG_LINK_EXPIRED"}, + 22: {"360_GOLD"}, + 23: {"PS3_TROP"}, + 24: {"COG"}, + 25: {"CAFE_SP"}, + 26: {"NetCafe", "Cafe", "OfficialCafe", "Official"}, + 27: {"HLRenewing", "HLR", "HLRenewal", "HLRenew", "CardHL"}, + 28: {"EXRenewing", "EXR", "EXRenewal", "EXRenew", "CardEX"}, + 29: {"Free"}, + // 30 = Real NetCafe course +} + +func (c Course) Aliases() []string { + return aliases[c.ID] +} + +func Courses() []Course { + courses := make([]Course, 32) + for i := range courses { + courses[i].ID = uint16(i) + } + return courses +} + +func (c Course) Value() uint32 { + return uint32(math.Pow(2, float64(c.ID))) +} + +// CourseExists returns true if the named course exists in the given slice +func CourseExists(ID uint16, c []Course) bool { + for _, course := range c { + if course.ID == ID { + return true + } + } + return false +} + +// GetCourseStruct returns a slice of Course(s) from a rights integer +func GetCourseStruct(rights uint32) ([]Course, uint32) { + resp := []Course{{ID: 1}, {ID: 24}} + s := Courses() + slices.SortStableFunc(s, func(i, j Course) bool { + return i.ID > j.ID + }) + var normalCafeCourseSet, netcafeCourseSet bool + for _, course := range s { + if rights-course.Value() < 0x80000000 { + switch course.ID { + case 26: + if normalCafeCourseSet { + break + } + normalCafeCourseSet = true + resp = append(resp, Course{ID: 25}) + fallthrough + case 9: + if netcafeCourseSet { + break + } + netcafeCourseSet = true + resp = append(resp, Course{ID: 30}) + } + course.Expiry = time.Date(2030, 1, 1, 0, 0, 0, 0, time.FixedZone("UTC+9", 9*60*60)) + resp = append(resp, course) + rights -= course.Value() + } + } + rights = 0 + for _, course := range resp { + rights += course.Value() + } + return resp, rights +} diff --git a/common/stringsupport/string_convert.go b/common/stringsupport/string_convert.go index ab91311ac..53ed2ec69 100644 --- a/common/stringsupport/string_convert.go +++ b/common/stringsupport/string_convert.go @@ -2,87 +2,14 @@ package stringsupport import ( "bytes" - "io/ioutil" + "io" "strconv" "strings" - "golang.org/x/text/encoding" "golang.org/x/text/encoding/japanese" "golang.org/x/text/transform" ) -// StringConverter is a small helper for encoding/decoding strings. -type StringConverter struct { - Encoding encoding.Encoding -} - -// Decode decodes the given bytes as the set encoding. -func (sc *StringConverter) Decode(data []byte) (string, error) { - decoded, err := ioutil.ReadAll(transform.NewReader(bytes.NewBuffer(data), sc.Encoding.NewDecoder())) - - if err != nil { - return "", err - } - - return string(decoded), nil -} - -// MustDecode decodes the given bytes as the set encoding. Panics on decode failure. -func (sc *StringConverter) MustDecode(data []byte) string { - decoded, err := sc.Decode(data) - if err != nil { - panic(err) - } - - return decoded -} - -// Encode encodes the given string as the set encoding. -func (sc *StringConverter) Encode(data string) ([]byte, error) { - encoded, err := ioutil.ReadAll(transform.NewReader(bytes.NewBuffer([]byte(data)), sc.Encoding.NewEncoder())) - - if err != nil { - return nil, err - } - - return encoded, nil -} - -// MustEncode encodes the given string as the set encoding. Panics on encode failure. -func (sc *StringConverter) MustEncode(data string) []byte { - encoded, err := sc.Encode(data) - if err != nil { - panic(err) - } - - return encoded -} - -/* -func MustConvertShiftJISToUTF8(text string) string { - result, err := ConvertShiftJISToUTF8(text) - if err != nil { - panic(err) - } - return result -} -func MustConvertUTF8ToShiftJIS(text string) string { - result, err := ConvertUTF8ToShiftJIS(text) - if err != nil { - panic(err) - } - return result -} -func ConvertShiftJISToUTF8(text string) (string, error) { - r := bytes.NewBuffer([]byte(text)) - decoded, err := ioutil.ReadAll(transform.NewReader(r, japanese.ShiftJIS.NewDecoder())) - if err != nil { - return "", err - } - return string(decoded), nil -} -*/ - func UTF8ToSJIS(x string) []byte { e := japanese.ShiftJIS.NewEncoder() xt, _, err := transform.String(e, x) @@ -94,7 +21,7 @@ func UTF8ToSJIS(x string) []byte { func SJISToUTF8(b []byte) string { d := japanese.ShiftJIS.NewDecoder() - result, err := ioutil.ReadAll(transform.NewReader(bytes.NewReader(b), d)) + result, err := io.ReadAll(transform.NewReader(bytes.NewReader(b), d)) if err != nil { panic(err) } @@ -169,22 +96,3 @@ func CSVElems(csv string) []int { } return r } - -// ConvertUTF8ToShiftJIS converts a UTF8 string to a Shift-JIS []byte. -func ConvertUTF8ToShiftJIS(text string) ([]byte, error) { - r := bytes.NewBuffer([]byte(text)) - encoded, err := ioutil.ReadAll(transform.NewReader(r, japanese.ShiftJIS.NewEncoder())) - if err != nil { - return nil, err - } - - return encoded, nil -} - -func ConvertUTF8ToSJIS(text string) (string, error) { - r, _, err := transform.String(japanese.ShiftJIS.NewEncoder(), text) - if err != nil { - return "", err - } - return r, nil -} diff --git a/common/token/token.go b/common/token/token.go index 73568bcbc..c474fdaf5 100644 --- a/common/token/token.go +++ b/common/token/token.go @@ -1,13 +1,22 @@ package token -import "math/rand" +import ( + "math/rand" + "time" +) // Generate returns an alphanumeric token of specified length func Generate(length int) string { + rng := RNG() var chars = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") b := make([]rune, length) for i := range b { - b[i] = chars[rand.Intn(len(chars))] + b[i] = chars[rng.Intn(len(chars))] } return string(b) } + +// RNG returns a new RNG generator +func RNG() *rand.Rand { + return rand.New(rand.NewSource(time.Now().UnixNano())) +} diff --git a/config.json b/config.json index 98311f132..2a62a3809 100644 --- a/config.json +++ b/config.json @@ -5,7 +5,7 @@ "DisableSoftCrash": false, "HideLoginNotice": true, "LoginNotices": [ - "
Welcome to Erupe SU9.2!
Erupe is experimental software, we are not liable for any
issues caused by installing the software!

■Report bugs on Discord!

■Test everything!

■Don't talk to softlocking NPCs!

■Fork the code on GitHub!

Thank you to all of the contributors,

this wouldn't exist without you." + "
Welcome to Erupe SU9.3!
Erupe is experimental software, we are not liable for any
issues caused by installing the software!

■Report bugs on Discord!

■Test everything!

■Don't talk to softlocking NPCs!

■Fork the code on GitHub!

Thank you to all of the contributors,

this wouldn't exist without you." ], "PatchServerManifest": "", "PatchServerFile": "", @@ -24,7 +24,6 @@ "TournamentEvent": 0, "MezFesEvent": true, "MezFesAlt": false, - "DisableMailItems": true, "DisableTokenCheck": false, "QuestDebugTools": false, "SaveDumps": { @@ -73,6 +72,10 @@ "Name": "Course", "Enabled": true, "Prefix": "!course" + }, { + "Name": "PSN", + "Enabled": true, + "Prefix": "!psn" } ], "Courses": [ @@ -85,7 +88,6 @@ {"Name": "HunterSupport", "Enabled": false}, {"Name": "NBoost", "Enabled": false}, {"Name": "NetCafe", "Enabled": true}, - {"Name": "OfficialCafe", "Enabled": true}, {"Name": "HLRenewing", "Enabled": true}, {"Name": "EXRenewing", "Enabled": true} ], diff --git a/config/config.go b/config/config.go index 61ae99d42..1e91b24b7 100644 --- a/config/config.go +++ b/config/config.go @@ -50,7 +50,6 @@ type DevModeOptions struct { MezFesEvent bool // MezFes status MezFesAlt bool // Swaps out Volpakkun for Tokotoko DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) - DisableMailItems bool // Hack to prevent english versions of MHF from crashing QuestDebugTools bool // Enable various quest debug logs SaveDumps SaveDumpOptions } diff --git a/main.go b/main.go index 33cbbfdd7..0f104eccf 100644 --- a/main.go +++ b/main.go @@ -54,7 +54,7 @@ func main() { defer zapLogger.Sync() logger := zapLogger.Named("main") - logger.Info(fmt.Sprintf("Starting Erupe (9.2b-%s)", Commit())) + logger.Info(fmt.Sprintf("Starting Erupe (9.3b-%s)", Commit())) if config.ErupeConfig.Database.Password == "" { preventClose("Database password is blank") diff --git a/network/mhfpacket/msg_mhf_get_ryoudama.go b/network/mhfpacket/msg_mhf_get_ryoudama.go index a173ded18..2cffc3bdc 100644 --- a/network/mhfpacket/msg_mhf_get_ryoudama.go +++ b/network/mhfpacket/msg_mhf_get_ryoudama.go @@ -1,19 +1,20 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfGetRyoudama represents the MSG_MHF_GET_RYOUDAMA -type MsgMhfGetRyoudama struct{ - AckHandle uint32 - Unk0 uint16 - Unk1 uint32 - Unk2 uint8 +type MsgMhfGetRyoudama struct { + AckHandle uint32 + Unk0 uint8 + Unk1 uint8 + GuildID uint32 + Unk3 uint8 } // Opcode returns the ID associated with this packet type. @@ -24,9 +25,10 @@ func (m *MsgMhfGetRyoudama) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfGetRyoudama) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint32() - m.Unk2 = bf.ReadUint8() + m.Unk0 = bf.ReadUint8() + m.Unk1 = bf.ReadUint8() + m.GuildID = bf.ReadUint32() + m.Unk3 = bf.ReadUint8() return nil } diff --git a/network/mhfpacket/msg_mhf_post_tiny_bin.go b/network/mhfpacket/msg_mhf_post_tiny_bin.go index 4c06a51e0..7c709d1e4 100644 --- a/network/mhfpacket/msg_mhf_post_tiny_bin.go +++ b/network/mhfpacket/msg_mhf_post_tiny_bin.go @@ -11,7 +11,10 @@ import ( // MsgMhfPostTinyBin represents the MSG_MHF_POST_TINY_BIN type MsgMhfPostTinyBin struct { AckHandle uint32 - Unk []byte + Unk0 uint16 + Unk1 uint8 + Unk2 uint8 + Data []byte } // Opcode returns the ID associated with this packet type. @@ -22,7 +25,10 @@ func (m *MsgMhfPostTinyBin) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfPostTinyBin) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk = bf.ReadBytes(14) + m.Unk0 = bf.ReadUint16() + m.Unk1 = bf.ReadUint8() + m.Unk2 = bf.ReadUint8() + m.Data = bf.ReadBytes(uint(bf.ReadUint16())) return nil } diff --git a/network/mhfpacket/msg_mhf_update_guild_message_board.go b/network/mhfpacket/msg_mhf_update_guild_message_board.go index b8c4ee5e0..94316cc52 100644 --- a/network/mhfpacket/msg_mhf_update_guild_message_board.go +++ b/network/mhfpacket/msg_mhf_update_guild_message_board.go @@ -2,6 +2,7 @@ package mhfpacket import ( "errors" + "erupe-ce/common/stringsupport" "erupe-ce/common/byteframe" "erupe-ce/network" @@ -10,9 +11,16 @@ import ( // MsgMhfUpdateGuildMessageBoard represents the MSG_MHF_UPDATE_GUILD_MESSAGE_BOARD type MsgMhfUpdateGuildMessageBoard struct { - AckHandle uint32 - MessageOp uint32 - Request []byte + AckHandle uint32 + MessageOp uint32 + PostType uint32 + StampID uint32 + TitleLength uint32 + BodyLength uint32 + Title string + Body string + PostID uint32 + LikeState bool } // Opcode returns the ID associated with this packet type. @@ -24,9 +32,31 @@ func (m *MsgMhfUpdateGuildMessageBoard) Opcode() network.PacketID { func (m *MsgMhfUpdateGuildMessageBoard) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.MessageOp = bf.ReadUint32() - if m.MessageOp != 5 { - m.Request = bf.DataFromCurrent() - bf.Seek(int64(len(bf.Data())-2), 0) + switch m.MessageOp { + case 0: + m.PostType = bf.ReadUint32() + m.StampID = bf.ReadUint32() + m.TitleLength = bf.ReadUint32() + m.BodyLength = bf.ReadUint32() + m.Title = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.TitleLength))) + m.Body = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.BodyLength))) + case 1: + m.PostID = bf.ReadUint32() + case 2: + m.PostID = bf.ReadUint32() + bf.ReadBytes(8) + m.TitleLength = bf.ReadUint32() + m.BodyLength = bf.ReadUint32() + m.Title = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.TitleLength))) + m.Body = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.BodyLength))) + case 3: + m.PostID = bf.ReadUint32() + bf.ReadBytes(8) + m.StampID = bf.ReadUint32() + case 4: + m.PostID = bf.ReadUint32() + bf.ReadBytes(8) + m.LikeState = bf.ReadBool() } return nil } diff --git a/network/mhfpacket/msg_sys_update_right.go b/network/mhfpacket/msg_sys_update_right.go index d6260c934..c384deece 100644 --- a/network/mhfpacket/msg_sys_update_right.go +++ b/network/mhfpacket/msg_sys_update_right.go @@ -3,50 +3,17 @@ package mhfpacket import ( "errors" "erupe-ce/common/byteframe" + "erupe-ce/common/mhfcourse" ps "erupe-ce/common/pascalstring" "erupe-ce/network" "erupe-ce/network/clientctx" - "golang.org/x/exp/slices" - "math" ) -/* -00 58 // Opcode - -00 00 00 00 -00 00 00 4e - -00 04 // Count -00 00 // Skipped(padding?) - -00 01 00 00 00 00 00 00 -00 02 00 00 5d fa 14 c0 -00 03 00 00 5d fa 14 c0 -00 06 00 00 5d e7 05 10 - -00 00 // Count of some buf up to 0x800 bytes following it. - -00 10 // Trailer -*/ - -// ClientRight represents a right that the client has. -type ClientRight struct { - ID uint16 - Unk0 uint16 - Timestamp uint32 -} - -type Course struct { - Aliases []string - ID uint16 - Value uint32 -} - // MsgSysUpdateRight represents the MSG_SYS_UPDATE_RIGHT type MsgSysUpdateRight struct { ClientRespAckHandle uint32 // If non-0, requests the client to send back a MSG_SYS_ACK packet with this value. Bitfield uint32 - Rights []ClientRight + Rights []mhfcourse.Course UnkSize uint16 // Count of some buf up to 0x800 bytes following it. } @@ -68,54 +35,13 @@ func (m *MsgSysUpdateRight) Build(bf *byteframe.ByteFrame, ctx *clientctx.Client bf.WriteUint16(0) for _, v := range m.Rights { bf.WriteUint16(v.ID) - bf.WriteUint16(v.Unk0) - bf.WriteUint32(v.Timestamp) + bf.WriteUint16(0) + if v.Expiry.IsZero() { + bf.WriteUint32(0) + } else { + bf.WriteUint32(uint32(v.Expiry.Unix())) + } } ps.Uint16(bf, "", false) // update client login token / password in the game's launcherstate struct return nil } - -func Courses() []Course { - var courses = []Course{ - {Aliases: []string{"Trial", "TL"}, ID: 1}, - {Aliases: []string{"HunterLife", "HL"}, ID: 2}, - {Aliases: []string{"Extra", "ExtraA", "EX"}, ID: 3}, - {Aliases: []string{"ExtraB"}, ID: 4}, - {Aliases: []string{"Mobile"}, ID: 5}, - {Aliases: []string{"Premium"}, ID: 6}, - {Aliases: []string{"Pallone", "ExtraC"}, ID: 7}, - {Aliases: []string{"Assist", "Legend", "Rasta"}, ID: 8}, // Legend - {Aliases: []string{"N"}, ID: 9}, - {Aliases: []string{"Hiden", "Secret"}, ID: 10}, // Secret - {Aliases: []string{"HunterSupport", "HunterAid", "Support", "Aid", "Royal"}, ID: 11}, // Royal - {Aliases: []string{"NBoost", "NetCafeBoost", "Boost"}, ID: 12}, - // 13-19 = (unknown), 20 = DEBUG, 21 = COG_LINK_EXPIRED, 22 = 360_GOLD, 23 = PS3_TROP - {Aliases: []string{"COG"}, ID: 24}, - {Aliases: []string{"NetCafe", "Cafe", "InternetCafe"}, ID: 25}, - {Aliases: []string{"OfficialCafe", "Official"}, ID: 26}, - {Aliases: []string{"HLRenewing", "HLR", "HLRenewal", "HLRenew"}, ID: 27}, - {Aliases: []string{"EXRenewing", "EXR", "EXRenewal", "EXRenew"}, ID: 28}, - {Aliases: []string{"Free"}, ID: 29}, - // 30 = real netcafe bit - } - for i := range courses { - courses[i].Value = uint32(math.Pow(2, float64(courses[i].ID))) - } - return courses -} - -// GetCourseStruct returns a slice of Course(s) from a rights integer -func GetCourseStruct(rights uint32) []Course { - var resp []Course - s := Courses() - slices.SortStableFunc(s, func(i, j Course) bool { - return i.ID > j.ID - }) - for _, course := range s { - if rights-course.Value < 0x80000000 { - resp = append(resp, course) - rights -= course.Value - } - } - return resp -} diff --git a/patch-schema/etc-points.sql b/patch-schema/etc-points.sql deleted file mode 100644 index 88062922f..000000000 --- a/patch-schema/etc-points.sql +++ /dev/null @@ -1,9 +0,0 @@ -BEGIN; - -ALTER TABLE characters ADD bonus_quests INT NOT NULL DEFAULT 0; - -ALTER TABLE characters ADD daily_quests INT NOT NULL DEFAULT 0; - -ALTER TABLE characters ADD promo_points INT NOT NULL DEFAULT 0; - -END; \ No newline at end of file diff --git a/patch-schema/gacha-db-2.sql b/patch-schema/gacha-db-2.sql deleted file mode 100644 index d3faa1ed9..000000000 --- a/patch-schema/gacha-db-2.sql +++ /dev/null @@ -1,77 +0,0 @@ -BEGIN; - -ALTER TABLE characters - DROP COLUMN IF EXISTS gacha_prem; - -ALTER TABLE characters - DROP COLUMN IF EXISTS gacha_trial; - -ALTER TABLE characters - DROP COLUMN IF EXISTS frontier_points; - -ALTER TABLE users - ADD IF NOT EXISTS gacha_premium INT; - -ALTER TABLE users - ADD IF NOT EXISTS gacha_trial INT; - -ALTER TABLE users - ADD IF NOT EXISTS frontier_points INT; - -DROP TABLE IF EXISTS public.gacha_shop; - -CREATE TABLE IF NOT EXISTS public.gacha_shop ( - id SERIAL PRIMARY KEY, - min_gr INTEGER, - min_hr INTEGER, - name TEXT, - url_banner TEXT, - url_feature TEXT, - url_thumbnail TEXT, - wide BOOLEAN, - recommended BOOLEAN, - gacha_type INTEGER, - hidden BOOLEAN -); - -DROP TABLE IF EXISTS public.gacha_shop_items; - -CREATE TABLE IF NOT EXISTS public.gacha_entries ( - id SERIAL PRIMARY KEY, - gacha_id INTEGER, - entry_type INTEGER, - item_type INTEGER, - item_number INTEGER, - item_quantity INTEGER, - weight INTEGER, - rarity INTEGER, - rolls INTEGER, - frontier_points INTEGER, - daily_limit INTEGER -); - -CREATE TABLE IF NOT EXISTS public.gacha_items ( - id SERIAL PRIMARY KEY, - entry_id INTEGER, - item_type INTEGER, - item_id INTEGER, - quantity INTEGER -); - -DROP TABLE IF EXISTS public.stepup_state; - -CREATE TABLE IF NOT EXISTS public.gacha_stepup ( - gacha_id INTEGER, - step INTEGER, - character_id INTEGER -); - -DROP TABLE IF EXISTS public.lucky_box_state; - -CREATE TABLE IF NOT EXISTS public.gacha_box ( - gacha_id INTEGER, - entry_id INTEGER, - character_id INTEGER -); - -END; \ No newline at end of file diff --git a/patch-schema/gacha-db.sql b/patch-schema/gacha-db.sql deleted file mode 100644 index 2fab717fe..000000000 --- a/patch-schema/gacha-db.sql +++ /dev/null @@ -1,29 +0,0 @@ -BEGIN; - -DROP TABLE IF EXISTS public.gacha_shop; - -CREATE TABLE IF NOT EXISTS public.gacha_shop ( - id serial PRIMARY KEY, - min_gr integer, - min_hr integer, - name text, - link1 text, - link2 text, - link3 text, - icon integer, - type integer, - hide boolean -); - -DROP TABLE IF EXISTS public.fpoint_items; - -CREATE TABLE IF NOT EXISTS public.fpoint_items ( - id serial PRIMARY KEY, - item_type integer, - item_id integer, - quantity integer, - fpoints integer, - trade_type integer -); - -END; \ No newline at end of file diff --git a/patch-schema/guild-event-rp.sql b/patch-schema/guild-event-rp.sql deleted file mode 100644 index 1dca6aae1..000000000 --- a/patch-schema/guild-event-rp.sql +++ /dev/null @@ -1,7 +0,0 @@ -BEGIN; - -ALTER TABLE IF EXISTS public.guild_characters ADD rp_today INT DEFAULT 0; - -ALTER TABLE IF EXISTS public.guild_characters ADD rp_yesterday INT DEFAULT 0; - -END; \ No newline at end of file diff --git a/patch-schema/login-boost.sql b/patch-schema/login-boost.sql deleted file mode 100644 index 3b451a65f..000000000 --- a/patch-schema/login-boost.sql +++ /dev/null @@ -1,12 +0,0 @@ -BEGIN; - -DROP TABLE IF EXISTS public.login_boost_state; - -CREATE TABLE IF NOT EXISTS public.login_boost ( - char_id INTEGER, - week_req INTEGER, - expiration TIMESTAMP WITH TIME ZONE, - reset TIMESTAMP WITH TIME ZONE -); - -END; \ No newline at end of file diff --git a/patch-schema/mezfes-save.sql b/patch-schema/mezfes-save.sql deleted file mode 100644 index a4445dc15..000000000 --- a/patch-schema/mezfes-save.sql +++ /dev/null @@ -1,6 +0,0 @@ -BEGIN; - -ALTER TABLE public.characters - ADD COLUMN mezfes BYTEA; - -END; \ No newline at end of file diff --git a/patch-schema/psn-id.sql b/patch-schema/psn-id.sql new file mode 100644 index 000000000..49ae1bdd8 --- /dev/null +++ b/patch-schema/psn-id.sql @@ -0,0 +1,5 @@ +BEGIN; + +ALTER TABLE users ADD COLUMN IF NOT EXISTS psn_id TEXT; + +END; \ No newline at end of file diff --git a/patch-schema/rasta-id.sql b/patch-schema/rasta-id.sql deleted file mode 100644 index 14541e378..000000000 --- a/patch-schema/rasta-id.sql +++ /dev/null @@ -1,9 +0,0 @@ -BEGIN; - -UPDATE characters SET savemercenary = NULL; - -ALTER TABLE characters ADD rasta_id INT; - -ALTER TABLE characters ADD pact_id INT; - -END; \ No newline at end of file diff --git a/patch-schema/shop-db.sql b/patch-schema/shop-db.sql deleted file mode 100644 index 3911b13bc..000000000 --- a/patch-schema/shop-db.sql +++ /dev/null @@ -1,29 +0,0 @@ -BEGIN; - -DROP TABLE IF EXISTS public.normal_shop_items; - -CREATE TABLE IF NOT EXISTS public.shop_items ( - id SERIAL PRIMARY KEY, - shop_type INTEGER, - shop_id INTEGER, - item_id INTEGER, - cost INTEGER, - quantity INTEGER, - min_hr INTEGER, - min_sr INTEGER, - min_gr INTEGER, - store_level INTEGER, - max_quantity INTEGER, - road_floors INTEGER, - road_fatalis INTEGER -); - -DROP TABLE IF EXISTS public.shop_item_state; - -CREATE TABLE IF NOT EXISTS public.shop_items_bought ( - character_id INTEGER, - shop_item_id INTEGER, - bought INTEGER -); - -END; \ No newline at end of file diff --git a/patch-schema/stampcard.sql b/patch-schema/stampcard.sql deleted file mode 100644 index f2c6b7d10..000000000 --- a/patch-schema/stampcard.sql +++ /dev/null @@ -1,5 +0,0 @@ -BEGIN; - -ALTER TABLE characters ADD stampcard INT NOT NULL DEFAULT 0; - -END; \ No newline at end of file diff --git a/patch-schema/time-fix.sql b/patch-schema/time-fix.sql deleted file mode 100644 index 8c5d890c0..000000000 --- a/patch-schema/time-fix.sql +++ /dev/null @@ -1,69 +0,0 @@ -BEGIN; - -ALTER TABLE IF EXISTS public.characters - ALTER COLUMN daily_time TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.characters - ALTER COLUMN guild_post_checked TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.characters - ALTER COLUMN boost_time TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.characters - ALTER COLUMN cafe_reset TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.distribution - ALTER COLUMN deadline TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.events - ALTER COLUMN start_time TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.feature_weapon - ALTER COLUMN start_time TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.guild_alliances - ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.guild_applications - ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.guild_characters - ALTER COLUMN joined_at TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.guild_posts - ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.characters - ALTER COLUMN daily_time TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.guilds - ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.mail - ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.stamps - ALTER COLUMN hl_next TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.stamps - ALTER COLUMN ex_next TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.titles - ALTER COLUMN unlocked_at TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.titles - ALTER COLUMN updated_at TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.users - ALTER COLUMN last_login TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.users - ALTER COLUMN return_expires TYPE TIMESTAMP WITH TIME ZONE; - -ALTER TABLE IF EXISTS public.guild_meals - DROP COLUMN expires; - -ALTER TABLE IF EXISTS public.guild_meals - ADD COLUMN created_at TIMESTAMP WITH TIME ZONE; - -END; \ No newline at end of file diff --git a/patch-schema/unused-tables.sql b/patch-schema/unused-tables.sql deleted file mode 100644 index a3411d517..000000000 --- a/patch-schema/unused-tables.sql +++ /dev/null @@ -1,17 +0,0 @@ -BEGIN; - -DROP TABLE IF EXISTS public.account_ban; - -DROP TABLE IF EXISTS public.account_history; - -DROP TABLE IF EXISTS public.account_moderation; - -DROP TABLE IF EXISTS public.account_sub; - -DROP TABLE IF EXISTS public.history; - -DROP TABLE IF EXISTS public.questlists; - -DROP TABLE IF EXISTS public.schema_migrations; - -END; \ No newline at end of file diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 1b5bf2f75..eda406911 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -3,6 +3,7 @@ package channelserver import ( "encoding/binary" "encoding/hex" + "erupe-ce/common/mhfcourse" ps "erupe-ce/common/pascalstring" "erupe-ce/common/stringsupport" "fmt" @@ -11,11 +12,11 @@ import ( "strings" "time" + "crypto/rand" "erupe-ce/common/byteframe" "erupe-ce/network/mhfpacket" "go.uber.org/zap" "math/bits" - "math/rand" ) // Temporary function to just return no results for a MSG_MHF_ENUMERATE* packet @@ -74,23 +75,13 @@ func doAckSimpleFail(s *Session, ackHandle uint32, data []byte) { } func updateRights(s *Session) { - rightsInt := uint32(0x0E) + rightsInt := uint32(2) 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 == 25 || 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}) - } + s.courses, rightsInt = mhfcourse.GetCourseStruct(rightsInt) update := &mhfpacket.MsgSysUpdateRight{ ClientRespAckHandle: 0, Bitfield: rightsInt, - Rights: rights, + Rights: s.courses, UnkSize: 0, } s.QueueSendMHF(update) @@ -221,7 +212,7 @@ func logoutPlayer(s *Session) { timePlayed += sessionTime var rpGained int - if s.FindCourse("NetCafe").ID != 0 || s.FindCourse("N").ID != 0 { + if mhfcourse.CourseExists(30, s.courses) { rpGained = timePlayed / 900 timePlayed = timePlayed % 900 s.server.db.Exec("UPDATE characters SET cafe_time=cafe_time+$1 WHERE id=$2", sessionTime, s.charID) diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go index 2973472de..46a6425a0 100644 --- a/server/channelserver/handlers_cafe.go +++ b/server/channelserver/handlers_cafe.go @@ -2,6 +2,7 @@ package channelserver import ( "erupe-ce/common/byteframe" + "erupe-ce/common/mhfcourse" ps "erupe-ce/common/pascalstring" "erupe-ce/network/mhfpacket" "fmt" @@ -88,7 +89,7 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) { if err != nil { panic(err) } - if s.FindCourse("NetCafe").ID != 0 || s.FindCourse("N").ID != 0 { + if mhfcourse.CourseExists(30, s.courses) { cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime } bf.WriteUint32(cafeTime) // Total cafe time diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 9ef5eec4e..7c961b5e9 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -3,13 +3,14 @@ package channelserver import ( "encoding/hex" "erupe-ce/common/byteframe" + "erupe-ce/common/mhfcourse" + "erupe-ce/common/token" "erupe-ce/config" "erupe-ce/network/binpacket" "erupe-ce/network/mhfpacket" "fmt" "golang.org/x/exp/slices" "math" - "math/rand" "strings" "time" @@ -81,6 +82,21 @@ func sendServerChatMessage(s *Session, message string) { } func parseChatCommand(s *Session, command string) { + if strings.HasPrefix(command, commands["PSN"].Prefix) { + if commands["PSN"].Enabled { + var id string + n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%s", commands["PSN"].Prefix), &id) + if err != nil || n != 1 { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNError"], commands["PSN"].Prefix)) + } else { + _, err = s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, id, s.charID) + if err == nil { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNSuccess"], id)) + } + } + } + } + if strings.HasPrefix(command, commands["Reload"].Prefix) { // Flush all objects and users and reload if commands["Reload"].Enabled { @@ -192,13 +208,14 @@ func parseChatCommand(s *Session, command string) { sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix)) } else { name = strings.ToLower(name) - for _, course := range mhfpacket.Courses() { - for _, alias := range course.Aliases { + for _, course := range mhfcourse.Courses() { + 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).ID != 0 { - ei := slices.IndexFunc(s.courses, func(c mhfpacket.Course) bool { - for _, alias := range c.Aliases { + if slices.Contains(s.server.erupeConfig.Courses, config.Course{Name: course.Aliases()[0], Enabled: true}) { + var delta, rightsInt uint32 + if mhfcourse.CourseExists(course.ID, s.courses) { + ei := slices.IndexFunc(s.courses, func(c mhfcourse.Course) bool { + for _, alias := range c.Aliases() { if strings.ToLower(name) == strings.ToLower(alias) { return true } @@ -206,25 +223,26 @@ func parseChatCommand(s *Session, command string) { return false }) if ei != -1 { - s.courses = append(s.courses[:ei], s.courses[ei+1:]...) - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseDisabled"], course.Aliases[0])) + delta = uint32(-1 * math.Pow(2, float64(course.ID))) + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseDisabled"], course.Aliases()[0])) } } else { - s.courses = append(s.courses, course) - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseEnabled"], course.Aliases[0])) + delta = uint32(math.Pow(2, float64(course.ID))) + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseEnabled"], course.Aliases()[0])) } - var newInt uint32 - for _, course := range s.courses { - newInt += uint32(math.Pow(2, float64(course.ID))) + err = 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) + if err == nil { + s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", rightsInt+delta, s.charID) } - 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.server.dict["commandCourseLocked"], course.Aliases[0])) + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseLocked"], course.Aliases()[0])) } + return } } } + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix)) } } else { sendDisabledCommandMessage(s, commands["Course"]) @@ -367,8 +385,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { roll.SetLE() roll.WriteUint16(4) // Unk roll.WriteUint16(authorLen) - rand.Seed(time.Now().UnixNano()) - dice := fmt.Sprintf("%d", rand.Intn(100)+1) + dice := fmt.Sprintf("%d", token.RNG().Intn(100)+1) roll.WriteUint16(uint16(len(dice) + 1)) roll.WriteNullTerminatedBytes([]byte(dice)) roll.WriteNullTerminatedBytes(tmp.ReadNullTerminatedBytes()) diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index 7de800e04..165580fe7 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -12,7 +12,7 @@ import ( ) const ( - pointerGender = 0x81 // +1 + pointerGender = 0x51 // +1 pointerRP = 0x22D16 // +2 pointerHouseTier = 0x1FB6C // +5 pointerHouseData = 0x1FE01 // +195 diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index 36dd2e596..64b14072c 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -5,7 +5,6 @@ import ( "erupe-ce/common/stringsupport" "fmt" "io" - "io/ioutil" "os" "path/filepath" @@ -260,7 +259,7 @@ func dumpSaveData(s *Session, data []byte, suffix string) { func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoaddata) if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "save_override.bin")); err == nil { - data, _ := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "save_override.bin")) + data, _ := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "save_override.bin")) doAckBufSucceed(s, pkt.AckHandle, data) return } diff --git a/server/channelserver/handlers_event.go b/server/channelserver/handlers_event.go index 1c8d3f319..bba4c1065 100644 --- a/server/channelserver/handlers_event.go +++ b/server/channelserver/handlers_event.go @@ -1,8 +1,8 @@ package channelserver import ( + "erupe-ce/common/token" "math" - "math/rand" "time" "erupe-ce/common/byteframe" @@ -95,9 +95,9 @@ func generateFeatureWeapons(count int) activeFeature { } nums := make([]int, 0) var result int - r := rand.New(rand.NewSource(time.Now().UnixNano())) for len(nums) < count { - num := r.Intn(14) + rng := token.RNG() + num := rng.Intn(14) exist := false for _, v := range nums { if v == num { @@ -131,6 +131,7 @@ func handleMsgMhfGetKeepLoginBoostStatus(s *Session, p mhfpacket.MHFPacket) { var loginBoosts []loginBoost rows, err := s.server.db.Queryx("SELECT week_req, expiration, reset FROM login_boost WHERE char_id=$1 ORDER BY week_req", s.charID) if err != nil || s.server.erupeConfig.GameplayOptions.DisableLoginBoost { + rows.Close() doAckBufSucceed(s, pkt.AckHandle, make([]byte, 35)) return } diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index 2659aae1d..155cee3d8 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -4,8 +4,8 @@ import ( "encoding/hex" "erupe-ce/common/byteframe" ps "erupe-ce/common/pascalstring" + "erupe-ce/common/token" "erupe-ce/network/mhfpacket" - "math/rand" "sort" "time" ) @@ -336,8 +336,7 @@ func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) return } - rand.Seed(time.Now().UnixNano()) - team := uint32(rand.Intn(2)) + team := uint32(token.RNG().Intn(2)) switch team { case 0: s.server.db.Exec("INSERT INTO festa_registrations VALUES ($1, 'blue')", guild.ID) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 7d7559cfb..697f005e6 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -1060,7 +1060,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { uint32 guild id uint32 guild leader id (for mail) uint32 unk (always null in pcap) - uint16 unk (always 0001 in pcap) + uint16 member count uint16 len guild name string nullterm guild name uint16 len guild leader name @@ -1862,7 +1862,6 @@ func handleMsgMhfEnumerateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUpdateGuildMessageBoard) - bf := byteframe.NewByteFrameFromBytes(pkt.Request) guild, err := GetGuildInfoByCharacterId(s, s.charID) applicant := false if guild != nil { @@ -1874,45 +1873,26 @@ func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) { } switch pkt.MessageOp { case 0: // Create message - postType := bf.ReadUint32() // 0 = message, 1 = news - stampID := bf.ReadUint32() - titleLength := bf.ReadUint32() - bodyLength := bf.ReadUint32() - title := stringsupport.SJISToUTF8(bf.ReadBytes(uint(titleLength))) - body := stringsupport.SJISToUTF8(bf.ReadBytes(uint(bodyLength))) - s.server.db.Exec("INSERT INTO guild_posts (guild_id, author_id, stamp_id, post_type, title, body) VALUES ($1, $2, $3, $4, $5, $6)", guild.ID, s.charID, stampID, postType, title, body) + s.server.db.Exec("INSERT INTO guild_posts (guild_id, author_id, stamp_id, post_type, title, body) VALUES ($1, $2, $3, $4, $5, $6)", guild.ID, s.charID, pkt.StampID, pkt.PostType, pkt.Title, pkt.Body) // TODO: if there are too many messages, purge excess case 1: // Delete message - postID := bf.ReadUint32() - s.server.db.Exec("DELETE FROM guild_posts WHERE id = $1", postID) + s.server.db.Exec("DELETE FROM guild_posts WHERE id = $1", pkt.PostID) case 2: // Update message - postID := bf.ReadUint32() - bf.ReadBytes(8) - titleLength := bf.ReadUint32() - bodyLength := bf.ReadUint32() - title := stringsupport.SJISToUTF8(bf.ReadBytes(uint(titleLength))) - body := stringsupport.SJISToUTF8(bf.ReadBytes(uint(bodyLength))) - s.server.db.Exec("UPDATE guild_posts SET title = $1, body = $2 WHERE id = $3", title, body, postID) + s.server.db.Exec("UPDATE guild_posts SET title = $1, body = $2 WHERE id = $3", pkt.Title, pkt.Body, pkt.PostID) case 3: // Update stamp - postID := bf.ReadUint32() - bf.ReadBytes(8) - stampID := bf.ReadUint32() - s.server.db.Exec("UPDATE guild_posts SET stamp_id = $1 WHERE id = $2", stampID, postID) + s.server.db.Exec("UPDATE guild_posts SET stamp_id = $1 WHERE id = $2", pkt.StampID, pkt.PostID) case 4: // Like message - postID := bf.ReadUint32() - bf.ReadBytes(8) - likeState := bf.ReadBool() var likedBy string - err := s.server.db.QueryRow("SELECT liked_by FROM guild_posts WHERE id = $1", postID).Scan(&likedBy) + err := s.server.db.QueryRow("SELECT liked_by FROM guild_posts WHERE id = $1", pkt.PostID).Scan(&likedBy) if err != nil { s.logger.Error("Failed to get guild message like data from db", zap.Error(err)) } else { - if likeState { + if pkt.LikeState { likedBy = stringsupport.CSVAdd(likedBy, int(s.charID)) - s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, postID) + s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, pkt.PostID) } else { likedBy = stringsupport.CSVRemove(likedBy, int(s.charID)) - s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, postID) + s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, pkt.PostID) } } case 5: // Check for new messages diff --git a/server/channelserver/handlers_guild_scout.go b/server/channelserver/handlers_guild_scout.go index 4b45cc93b..fceeb037d 100644 --- a/server/channelserver/handlers_guild_scout.go +++ b/server/channelserver/handlers_guild_scout.go @@ -1,14 +1,12 @@ package channelserver import ( - "fmt" - "io" - "time" - "erupe-ce/common/byteframe" "erupe-ce/common/stringsupport" "erupe-ce/network/mhfpacket" + "fmt" "go.uber.org/zap" + "io" ) func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) { @@ -206,7 +204,7 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) { } rows, err := s.server.db.Queryx(` - SELECT c.id, c.name, ga.actor_id + SELECT c.id, c.name, c.hrp, c.gr, ga.actor_id FROM guild_applications ga JOIN characters c ON c.id = ga.character_id WHERE ga.guild_id = $1 AND ga.application_type = 'invited' @@ -231,14 +229,14 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) { for rows.Next() { var charName string - var charID uint32 - var actorID uint32 + var charID, actorID uint32 + var hrp, gr uint16 - err = rows.Scan(&charID, &charName, &actorID) + err = rows.Scan(&charID, &charName, &hrp, &gr, &actorID) if err != nil { doAckSimpleFail(s, pkt.AckHandle, nil) - panic(err) + continue } // This seems to be used as a unique ID for the invitation sent @@ -247,14 +245,10 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint32(charID) bf.WriteUint32(actorID) bf.WriteUint32(charID) - bf.WriteUint32(uint32(time.Now().Unix())) - bf.WriteUint16(0x00) // HR? - bf.WriteUint16(0x00) // GR? - - charNameBytes, _ := stringsupport.ConvertUTF8ToShiftJIS(charName) - - bf.WriteBytes(charNameBytes) - bf.WriteBytes(make([]byte, 32-len(charNameBytes))) // Fixed length string + bf.WriteUint32(uint32(TimeAdjusted().Unix())) + bf.WriteUint16(hrp) // HR? + bf.WriteUint16(gr) // GR? + bf.WriteBytes(stringsupport.PaddedString(charName, 32, true)) count++ } diff --git a/server/channelserver/handlers_mail.go b/server/channelserver/handlers_mail.go index 1df4711c6..7e9784ee3 100644 --- a/server/channelserver/handlers_mail.go +++ b/server/channelserver/handlers_mail.go @@ -326,15 +326,8 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) { flags |= 0x04 } - // Workaround until EN mail items are patched - if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DisableMailItems { - if itemAttached { - flags |= 0x08 - } - } else { - if m.AttachedItemReceived { - flags |= 0x08 - } + if m.AttachedItemReceived { + flags |= 0x08 } if m.IsGuildInvite { diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index e44d9f1f2..251bc107b 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -8,7 +8,6 @@ import ( "erupe-ce/server/channelserver/compression/nullcomp" "go.uber.org/zap" "io" - "io/ioutil" "os" "path/filepath" "time" @@ -292,7 +291,7 @@ func handleMsgMhfEnumerateAiroulist(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateAiroulist) resp := byteframe.NewByteFrame() if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin")); err == nil { - data, _ := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin")) + data, _ := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin")) resp.WriteBytes(data) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) return diff --git a/server/channelserver/handlers_rengoku.go b/server/channelserver/handlers_rengoku.go index 732cb77ef..63a591fd2 100644 --- a/server/channelserver/handlers_rengoku.go +++ b/server/channelserver/handlers_rengoku.go @@ -4,7 +4,7 @@ import ( ps "erupe-ce/common/pascalstring" "fmt" "github.com/jmoiron/sqlx" - "io/ioutil" + "os" "path/filepath" "erupe-ce/common/byteframe" @@ -92,7 +92,7 @@ func handleMsgMhfLoadRengokuData(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetRengokuBinary(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetRengokuBinary) // a (massively out of date) version resides in the game's /dat/ folder or up to date can be pulled from packets - data, err := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "rengoku_data.bin")) + data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "rengoku_data.bin")) if err != nil { panic(err) } diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 2ebf209ea..1341e341c 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -358,6 +358,9 @@ func getRandomEntries(entries []GachaEntry, rolls int, isBox bool) ([]GachaEntry totalWeight += entries[i].Weight } for { + if rolls == len(chosen) { + break + } if !isBox { result := rand.Float64() * totalWeight for _, entry := range entries { @@ -373,9 +376,6 @@ func getRandomEntries(entries []GachaEntry, rolls int, isBox bool) ([]GachaEntry entries[result] = entries[len(entries)-1] entries = entries[:len(entries)-1] } - if rolls == len(chosen) { - break - } } return chosen, nil } diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index c577f9a9f..993f2dc8d 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -57,6 +57,8 @@ func getLangStrings(s *Server) map[string]string { strings["commandCourseLocked"] = "%sコースはロックされています" strings["commandTeleportError"] = "テレポートコマンドエラー 構文:%s x y" strings["commandTeleportSuccess"] = "%d %dにテレポート" + strings["commandPSNError"] = "PSN連携コマンドエラー 例:%s " + strings["commandPSNSuccess"] = "PSN「%s」が連携されています" strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません" strings["commandRaviStartSuccess"] = "大討伐を開始します" @@ -142,6 +144,8 @@ func getLangStrings(s *Server) map[string]string { strings["commandCourseLocked"] = "%s Course is locked" strings["commandTeleportError"] = "Error in command. Format: %s x y" strings["commandTeleportSuccess"] = "Teleporting to %d %d" + strings["commandPSNError"] = "Error in command. Format: %s " + strings["commandPSNSuccess"] = "Connected PSN ID: %s" strings["commandRaviNoCommand"] = "No Raviente command specified!" strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment" diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index 7691cb3a0..40d54fdb3 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -3,10 +3,10 @@ package channelserver import ( "encoding/binary" "encoding/hex" + "erupe-ce/common/mhfcourse" "fmt" "io" "net" - "strings" "sync" "time" @@ -32,6 +32,7 @@ type Session struct { cryptConn *network.CryptConn sendPackets chan packet clientContext *clientctx.ClientContext + lastPacket time.Time userEnteredStage bool // If the user has entered a stage before stageID string @@ -42,7 +43,7 @@ type Session struct { charID uint32 logKey []byte sessionStart int64 - courses []mhfpacket.Course + courses []mhfcourse.Course token string kqf []byte kqfOverride bool @@ -73,6 +74,7 @@ func NewSession(server *Server, conn net.Conn) *Session { cryptConn: network.NewCryptConn(conn), sendPackets: make(chan packet, 20), clientContext: &clientctx.ClientContext{}, // Unused + lastPacket: time.Now(), sessionStart: TimeAdjusted().Unix(), stageMoveStack: stringstack.New(), } @@ -160,6 +162,10 @@ func (s *Session) sendLoop() { func (s *Session) recvLoop() { for { + if time.Now().Add(-30 * time.Second).After(s.lastPacket) { + logoutPlayer(s) + return + } if s.closed { logoutPlayer(s) return @@ -181,6 +187,7 @@ func (s *Session) recvLoop() { } func (s *Session) handlePacketGroup(pktGroup []byte) { + s.lastPacket = time.Now() bf := byteframe.NewByteFrameFromBytes(pktGroup) opcodeUint16 := bf.ReadUint16() opcode := network.PacketID(opcodeUint16) @@ -228,7 +235,6 @@ func ignored(opcode network.PacketID) bool { network.MSG_SYS_TIME, network.MSG_SYS_EXTEND_THRESHOLD, network.MSG_SYS_POSITION_OBJECT, - network.MSG_MHF_ENUMERATE_QUEST, network.MSG_MHF_SAVEDATA, } set := make(map[network.PacketID]struct{}, len(ignoreList)) @@ -262,14 +268,3 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien fmt.Printf("Data [%d bytes]:\n(Too long!)\n\n", len(data)) } } - -func (s *Session) FindCourse(name string) mhfpacket.Course { - for _, course := range s.courses { - for _, alias := range course.Aliases { - if strings.ToLower(name) == strings.ToLower(alias) { - return course - } - } - } - return mhfpacket.Course{} -} diff --git a/server/channelserver/sys_stage.go b/server/channelserver/sys_stage.go index 532ae60d4..b69995724 100644 --- a/server/channelserver/sys_stage.go +++ b/server/channelserver/sys_stage.go @@ -3,8 +3,6 @@ package channelserver import ( "sync" - "time" - "erupe-ce/common/byteframe" "erupe-ce/network/mhfpacket" ) @@ -49,7 +47,6 @@ type Stage struct { host *Session maxPlayers uint16 password string - createdAt string } // NewStage creates a new stage with intialized values. @@ -62,7 +59,6 @@ func NewStage(ID string) *Stage { objectIndex: 0, rawBinaryData: make(map[stageBinaryKey][]byte), maxPlayers: 4, - createdAt: time.Now().Format("01-02-2006 15:04:05"), } return s } diff --git a/server/signserver/dbutils.go b/server/signserver/dbutils.go index 213a74a14..ee4dcc493 100644 --- a/server/signserver/dbutils.go +++ b/server/signserver/dbutils.go @@ -1,6 +1,7 @@ package signserver import ( + "erupe-ce/common/mhfcourse" "strings" "time" @@ -119,8 +120,9 @@ func (s *Server) getLastCID(uid int) uint32 { } func (s *Server) getUserRights(uid int) uint32 { - var rights uint32 + rights := uint32(2) _ = s.db.QueryRow("SELECT rights FROM users WHERE id=$1", uid).Scan(&rights) + _, rights = mhfcourse.GetCourseStruct(rights) return rights } diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 1fc78f3d9..926711d57 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -7,31 +7,19 @@ import ( "erupe-ce/common/token" "erupe-ce/server/channelserver" "fmt" - "math/rand" - "strings" - "time" - "go.uber.org/zap" + "strings" ) -func makeSignInFailureResp(respID RespID) []byte { - bf := byteframe.NewByteFrame() - bf.WriteUint8(uint8(respID)) - return bf.Data() -} - -func (s *Session) makeSignInResp(uid int) []byte { - returnExpiry := s.server.getReturnExpiry(uid) - +func (s *Session) makeSignResponse(uid int) []byte { // Get the characters from the DB. chars, err := s.server.getCharactersForUser(uid) if err != nil { s.logger.Warn("Error getting characters from DB", zap.Error(err)) } - rand.Seed(time.Now().UnixNano()) sessToken := token.Generate(16) - s.server.registerToken(uid, sessToken) + _ = s.server.registerToken(uid, sessToken) bf := byteframe.NewByteFrame() @@ -41,11 +29,11 @@ func (s *Session) makeSignInResp(uid int) []byte { } else { bf.WriteUint8(0) } - bf.WriteUint8(1) // entrance server count - bf.WriteUint8(uint8(len(chars))) // character count - bf.WriteUint32(0xFFFFFFFF) // login_token_number - bf.WriteBytes([]byte(sessToken)) // login_token - bf.WriteUint32(uint32(time.Now().Unix())) // current time + bf.WriteUint8(1) // entrance server count + bf.WriteUint8(uint8(len(chars))) + bf.WriteUint32(0xFFFFFFFF) // login_token_number + bf.WriteBytes([]byte(sessToken)) + bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix())) if s.server.erupeConfig.DevMode { if s.server.erupeConfig.PatchServerManifest != "" && s.server.erupeConfig.PatchServerFile != "" { ps.Uint8(bf, s.server.erupeConfig.PatchServerManifest, false) @@ -120,6 +108,11 @@ func (s *Session) makeSignInResp(uid int) []byte { bf.WriteUint32(s.server.getLastCID(uid)) bf.WriteUint32(s.server.getUserRights(uid)) ps.Uint16(bf, "", false) // filters + if s.client == VITA || s.client == PS3 { + var psnUser string + s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser) + bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true)) + } bf.WriteUint16(0xCA10) bf.WriteUint16(0x4E20) ps.Uint16(bf, "", false) // unk key @@ -128,7 +121,7 @@ func (s *Session) makeSignInResp(uid int) []byte { bf.WriteUint16(0x0001) bf.WriteUint16(0x4E20) ps.Uint16(bf, "", false) // unk ipv4 - bf.WriteUint32(uint32(returnExpiry.Unix())) + bf.WriteUint32(uint32(s.server.getReturnExpiry(uid).Unix())) bf.WriteUint32(0x00000000) bf.WriteUint32(0x0A5197DF) // unk id diff --git a/server/signserver/respid.go b/server/signserver/respid.go index 47e7683d4..014daa862 100644 --- a/server/signserver/respid.go +++ b/server/signserver/respid.go @@ -1,10 +1,7 @@ package signserver -//revive:disable +type RespID uint8 -type RespID uint16 - -//go:generate stringer -type=RespID const ( SIGN_UNKNOWN RespID = iota SIGN_SUCCESS diff --git a/server/signserver/session.go b/server/signserver/session.go index 19270fafc..dfe034226 100644 --- a/server/signserver/session.go +++ b/server/signserver/session.go @@ -13,6 +13,14 @@ import ( "golang.org/x/crypto/bcrypt" ) +type Client int + +const ( + PC100 Client = iota + VITA + PS3 +) + // Session holds state for the sign server connection. type Session struct { sync.Mutex @@ -20,6 +28,7 @@ type Session struct { server *Server rawConn net.Conn cryptConn *network.CryptConn + client Client } func (s *Session) work() { @@ -42,91 +51,68 @@ func (s *Session) handlePacket(pkt []byte) error { 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 "DLTSKEYSIGN:100", "DSGN:100": + s.handleDSGN(bf) + case "PS3SGN:100": + s.client = PS3 + s.handlePSSGN(bf) + case "VITASGN:100": + s.client = VITA + s.handlePSSGN(bf) case "DELETE:100": loginTokenString := string(bf.ReadNullTerminatedBytes()) characterID := int(bf.ReadUint32()) _ = int(bf.ReadUint32()) // login_token_number - s.server.deleteCharacter(characterID, loginTokenString) - s.logger.Info("Deleted character", zap.Int("CharacterID", characterID)) - err := s.cryptConn.SendPacket([]byte{0x01}) // DEL_SUCCESS - if err != nil { - return nil + err := s.server.deleteCharacter(characterID, loginTokenString) + if err == nil { + s.logger.Info("Deleted character", zap.Int("CharacterID", characterID)) + s.cryptConn.SendPacket([]byte{0x01}) // DEL_SUCCESS } default: - s.logger.Warn("Unknown sign request", zap.String("reqType", reqType)) + s.logger.Warn("Unknown request", zap.String("reqType", reqType)) if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages { fmt.Printf("\n[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt)) } } - return nil } -func (s *Session) handleDSGNRequest(bf *byteframe.ByteFrame) error { - - reqUsername := string(bf.ReadNullTerminatedBytes()) - reqPassword := string(bf.ReadNullTerminatedBytes()) - _ = string(bf.ReadNullTerminatedBytes()) // Unk - +func (s *Session) authenticate(username string, password string) { newCharaReq := false - if reqUsername[len(reqUsername)-1] == 43 { // '+' - reqUsername = reqUsername[:len(reqUsername)-1] + if username[len(username)-1] == 43 { // '+' + username = username[:len(username)-1] newCharaReq = true } - 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 + var id int + var hash string + bf := byteframe.NewByteFrame() + + err := s.server.db.QueryRow("SELECT id, password FROM users WHERE username = $1", username).Scan(&id, &hash) switch { case err == sql.ErrNoRows: - s.logger.Info("User not found", zap.String("Username", reqUsername)) - serverRespBytes = makeSignInFailureResp(SIGN_EAUTH) - + s.logger.Info("User not found", zap.String("Username", username)) if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.AutoCreateAccount { - s.logger.Info("Creating user", zap.String("Username", reqUsername)) - err = s.server.registerDBAccount(reqUsername, reqPassword) - if err != nil { - s.logger.Error("Error registering new user", zap.Error(err)) - serverRespBytes = makeSignInFailureResp(SIGN_EABORT) - break + s.logger.Info("Creating user", zap.String("Username", username)) + err = s.server.registerDBAccount(username, password) + if err == nil { + bf.WriteBytes(s.makeSignResponse(id)) } } else { - break + bf.WriteUint8(uint8(SIGN_EAUTH)) } - - var id int - err = s.server.db.QueryRow("SELECT id FROM users WHERE username = $1", reqUsername).Scan(&id) - if err != nil { - s.logger.Error("Error getting new user ID", zap.Error(err)) - serverRespBytes = makeSignInFailureResp(SIGN_EABORT) - break - } - - serverRespBytes = s.makeSignInResp(id) - break case err != nil: - serverRespBytes = makeSignInFailureResp(SIGN_EABORT) + bf.WriteUint8(uint8(SIGN_EABORT)) s.logger.Error("Error getting user details", zap.Error(err)) - break default: - if bcrypt.CompareHashAndPassword([]byte(password), []byte(reqPassword)) == nil { + if bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil || s.client == VITA || s.client == PS3 { s.logger.Debug("Passwords match!") if newCharaReq { - err = s.server.newUserChara(reqUsername) + err = s.server.newUserChara(username) if err != nil { s.logger.Error("Error adding new character to user", zap.Error(err)) - serverRespBytes = makeSignInFailureResp(SIGN_EABORT) + bf.WriteUint8(uint8(SIGN_EABORT)) break } } @@ -137,22 +123,39 @@ func (s *Session) handleDSGNRequest(bf *byteframe.ByteFrame) error { // serverRespBytes = makeSignInFailureResp(SIGN_EABORT) // break // } - serverRespBytes = s.makeSignInResp(id) + bf.WriteBytes(s.makeSignResponse(id)) } else { s.logger.Warn("Incorrect password") - serverRespBytes = makeSignInFailureResp(SIGN_EPASS) + bf.WriteUint8(uint8(SIGN_EPASS)) } - } if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogOutboundMessages { - fmt.Printf("\n[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(serverRespBytes), hex.Dump(serverRespBytes)) + fmt.Printf("\n[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(bf.Data()), hex.Dump(bf.Data())) } - err = s.cryptConn.SendPacket(serverRespBytes) - if err != nil { - return err - } - - return nil + err = s.cryptConn.SendPacket(bf.Data()) +} + +func (s *Session) handlePSSGN(bf *byteframe.ByteFrame) { + _ = bf.ReadNullTerminatedBytes() // 0000000256 + _ = bf.ReadNullTerminatedBytes() // 1 + _ = bf.ReadBytes(82) + psnUser := string(bf.ReadNullTerminatedBytes()) + var reqUsername string + err := s.server.db.QueryRow(`SELECT username FROM users WHERE psn_id = $1`, psnUser).Scan(&reqUsername) + if err == sql.ErrNoRows { + resp := byteframe.NewByteFrame() + resp.WriteUint8(uint8(SIGN_ECOGLINK)) + s.cryptConn.SendPacket(resp.Data()) + return + } + s.authenticate(reqUsername, "") +} + +func (s *Session) handleDSGN(bf *byteframe.ByteFrame) { + reqUsername := string(bf.ReadNullTerminatedBytes()) + reqPassword := string(bf.ReadNullTerminatedBytes()) + _ = string(bf.ReadNullTerminatedBytes()) // Unk + s.authenticate(reqUsername, reqPassword) }