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)
}