diff --git a/bin/questlists/.gitkeep b/bin/questlists/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/common/token/token.go b/common/token/token.go
new file mode 100644
index 000000000..73568bcbc
--- /dev/null
+++ b/common/token/token.go
@@ -0,0 +1,13 @@
+package token
+
+import "math/rand"
+
+// Generate returns an alphanumeric token of specified length
+func Generate(length int) string {
+ var chars = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
+ b := make([]rune, length)
+ for i := range b {
+ b[i] = chars[rand.Intn(len(chars))]
+ }
+ return string(b)
+}
diff --git a/config.json b/config.json
index 31ecd8416..b6967be68 100644
--- a/config.json
+++ b/config.json
@@ -1,16 +1,17 @@
{
"Host": "127.0.0.1",
"BinPath": "bin",
+ "Language": "en",
"DisableSoftCrash": false,
"FeaturedWeapons": 1,
+ "HideLoginNotice": true,
+ "LoginNotice": "
Welcome to Erupe SU9.1!
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": "",
+ "ScreenshotAPIURL": "",
"DevMode": true,
"DevModeOptions": {
- "PatchServerManifest": "",
- "PatchServerFile": "",
"AutoCreateAccount": true,
- "EnableLauncherServer": false,
- "HideLoginNotice": false,
- "LoginNotice": "Welcome to Erupe SU9.1 Beta!
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.",
"CleanDB": false,
"MaxLauncherHR": false,
"LogInboundMessages": false,
@@ -63,13 +64,16 @@
],
"Courses": [
{"Name": "HunterLife", "Enabled": true},
- {"Name": "ExtraA", "Enabled": true},
+ {"Name": "Extra", "Enabled": true},
{"Name": "Premium", "Enabled": true},
{"Name": "Assist", "Enabled": false},
- {"Name": "Netcafe", "Enabled": false},
+ {"Name": "N", "Enabled": false},
{"Name": "Hiden", "Enabled": false},
{"Name": "HunterSupport", "Enabled": false},
- {"Name": "NetcafeBoost", "Enabled": false}
+ {"Name": "NBoost", "Enabled": false},
+ {"Name": "NetCafe", "Enabled": true},
+ {"Name": "HLRenewing", "Enabled": true},
+ {"Name": "EXRenewing", "Enabled": true}
],
"Database": {
"Host": "localhost",
@@ -87,6 +91,10 @@
"Enabled": true,
"Port": 53312
},
+ "SignV2": {
+ "Enabled": false,
+ "Port": 8080
+ },
"Channel": {
"Enabled": true
},
diff --git a/config/config.go b/config/config.go
index d2804b9fc..c2c66f925 100644
--- a/config/config.go
+++ b/config/config.go
@@ -12,11 +12,17 @@ import (
// Config holds the global server-wide config.
type Config struct {
- Host string `mapstructure:"Host"`
- BinPath string `mapstructure:"BinPath"`
- DisableSoftCrash bool // Disables the 'Press Return to exit' dialog allowing scripts to reboot the server automatically
- FeaturedWeapons int // Number of Active Feature weapons to generate daily
- DevMode bool
+ Host string `mapstructure:"Host"`
+ BinPath string `mapstructure:"BinPath"`
+ Language string
+ DisableSoftCrash bool // Disables the 'Press Return to exit' dialog allowing scripts to reboot the server automatically
+ FeaturedWeapons int // Number of Active Feature weapons to generate daily
+ HideLoginNotice bool // Hide the Erupe notice on login
+ LoginNotice string // MHFML string of the login notice displayed
+ PatchServerManifest string // Manifest patch server override
+ PatchServerFile string // File patch server override
+ ScreenshotAPIURL string // Destination for screenshots uploaded to BBS
+ DevMode bool
DevModeOptions DevModeOptions
Discord Discord
@@ -25,30 +31,27 @@ type Config struct {
Database Database
Launcher Launcher
Sign Sign
+ SignV2 SignV2
Channel Channel
Entrance Entrance
}
// DevModeOptions holds various debug/temporary options for use while developing Erupe.
type DevModeOptions struct {
- PatchServerManifest string // Manifest patch server override
- PatchServerFile string // File patch server override
- AutoCreateAccount bool // Automatically create accounts if they don't exist
- HideLoginNotice bool // Hide the Erupe notice on login
- LoginNotice string // MHFML string of the login notice displayed
- CleanDB bool // Automatically wipes the DB on server reset.
- MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds.
- LogInboundMessages bool // Log all messages sent to the server
- LogOutboundMessages bool // Log all messages sent to the clients
- MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled
- DivaEvent int // Diva Defense event status
- FestaEvent int // Hunter's Festa event status
- TournamentEvent int // VS Tournament event status
- 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
+ AutoCreateAccount bool // Automatically create accounts if they don't exist
+ CleanDB bool // Automatically wipes the DB on server reset.
+ MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds.
+ LogInboundMessages bool // Log all messages sent to the server
+ LogOutboundMessages bool // Log all messages sent to the clients
+ MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled
+ DivaEvent int // Diva Defense event status
+ FestaEvent int // Hunter's Festa event status
+ TournamentEvent int // VS Tournament event status
+ 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
}
@@ -99,6 +102,12 @@ type Sign struct {
Port int
}
+// SignV2 holds the new sign server config
+type SignV2 struct {
+ Enabled bool
+ Port int
+}
+
type Channel struct {
Enabled bool
}
diff --git a/main.go b/main.go
index dcb3a14b4..d347f9859 100644
--- a/main.go
+++ b/main.go
@@ -14,6 +14,7 @@ import (
"erupe-ce/server/entranceserver"
"erupe-ce/server/launcherserver"
"erupe-ce/server/signserver"
+ "erupe-ce/server/signv2server"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
@@ -35,7 +36,7 @@ func main() {
defer zapLogger.Sync()
logger := zapLogger.Named("main")
- logger.Info("Starting Erupe (9.1b)")
+ logger.Info("Starting Erupe (9.2b)")
if config.ErupeConfig.Database.Password == "" {
preventClose("Database password is blank")
@@ -166,6 +167,22 @@ func main() {
logger.Info("Started sign server")
}
+ // New Sign server
+ var newSignServer *signv2server.Server
+ if config.ErupeConfig.SignV2.Enabled {
+ newSignServer = signv2server.NewServer(
+ &signv2server.Config{
+ Logger: logger.Named("sign"),
+ ErupeConfig: config.ErupeConfig,
+ DB: db,
+ })
+ err = newSignServer.Start()
+ if err != nil {
+ preventClose(fmt.Sprintf("Failed to start sign-v2 server: %s", err.Error()))
+ }
+ logger.Info("Started new sign server")
+ }
+
var channels []*channelserver.Server
if config.ErupeConfig.Channel.Enabled {
@@ -229,6 +246,10 @@ func main() {
signServer.Shutdown()
}
+ if config.ErupeConfig.SignV2.Enabled {
+ newSignServer.Shutdown()
+ }
+
if config.ErupeConfig.Entrance.Enabled {
entranceServer.Shutdown()
}
diff --git a/network/mhfpacket/msg_mhf_apply_bbs_article.go b/network/mhfpacket/msg_mhf_apply_bbs_article.go
index df0218ce0..d2b9c803b 100644
--- a/network/mhfpacket/msg_mhf_apply_bbs_article.go
+++ b/network/mhfpacket/msg_mhf_apply_bbs_article.go
@@ -1,15 +1,24 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
+ "erupe-ce/common/bfutil"
+ "erupe-ce/common/stringsupport"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfApplyBbsArticle represents the MSG_MHF_APPLY_BBS_ARTICLE
-type MsgMhfApplyBbsArticle struct{}
+type MsgMhfApplyBbsArticle struct {
+ AckHandle uint32
+ Unk0 uint32
+ Unk1 []byte
+ Name string
+ Title string
+ Description string
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfApplyBbsArticle) Opcode() network.PacketID {
@@ -18,7 +27,13 @@ func (m *MsgMhfApplyBbsArticle) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfApplyBbsArticle) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ m.Unk0 = bf.ReadUint32()
+ m.Unk1 = bf.ReadBytes(16)
+ m.Name = stringsupport.SJISToUTF8(bfutil.UpToNull(bf.ReadBytes(32)))
+ m.Title = stringsupport.SJISToUTF8(bfutil.UpToNull(bf.ReadBytes(128)))
+ m.Description = stringsupport.SJISToUTF8(bfutil.UpToNull(bf.ReadBytes(256)))
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_mhf_check_monthly_item.go b/network/mhfpacket/msg_mhf_check_monthly_item.go
index d79c65240..860725aa4 100644
--- a/network/mhfpacket/msg_mhf_check_monthly_item.go
+++ b/network/mhfpacket/msg_mhf_check_monthly_item.go
@@ -11,7 +11,8 @@ import (
// MsgMhfCheckMonthlyItem represents the MSG_MHF_CHECK_MONTHLY_ITEM
type MsgMhfCheckMonthlyItem struct {
AckHandle uint32
- Unk uint32
+ Type uint8
+ Unk []byte
}
// Opcode returns the ID associated with this packet type.
@@ -22,7 +23,8 @@ func (m *MsgMhfCheckMonthlyItem) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfCheckMonthlyItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
- m.Unk = bf.ReadUint32()
+ m.Type = bf.ReadUint8()
+ m.Unk = bf.ReadBytes(3)
return nil
}
diff --git a/network/mhfpacket/msg_mhf_get_bbs_sns_status.go b/network/mhfpacket/msg_mhf_get_bbs_sns_status.go
index 07ab9e485..0c50f67e8 100644
--- a/network/mhfpacket/msg_mhf_get_bbs_sns_status.go
+++ b/network/mhfpacket/msg_mhf_get_bbs_sns_status.go
@@ -1,15 +1,18 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfGetBbsSnsStatus represents the MSG_MHF_GET_BBS_SNS_STATUS
-type MsgMhfGetBbsSnsStatus struct{}
+type MsgMhfGetBbsSnsStatus struct {
+ AckHandle uint32
+ Unk []byte
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfGetBbsSnsStatus) Opcode() network.PacketID {
@@ -18,7 +21,9 @@ func (m *MsgMhfGetBbsSnsStatus) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfGetBbsSnsStatus) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ m.Unk = bf.ReadBytes(12)
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_mhf_get_bbs_user_status.go b/network/mhfpacket/msg_mhf_get_bbs_user_status.go
index 69bcef671..9c44faa76 100644
--- a/network/mhfpacket/msg_mhf_get_bbs_user_status.go
+++ b/network/mhfpacket/msg_mhf_get_bbs_user_status.go
@@ -1,15 +1,18 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfGetBbsUserStatus represents the MSG_MHF_GET_BBS_USER_STATUS
-type MsgMhfGetBbsUserStatus struct{}
+type MsgMhfGetBbsUserStatus struct {
+ AckHandle uint32
+ Unk []byte
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfGetBbsUserStatus) Opcode() network.PacketID {
@@ -18,7 +21,9 @@ func (m *MsgMhfGetBbsUserStatus) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfGetBbsUserStatus) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ m.Unk = bf.ReadBytes(12)
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_sys_update_right.go b/network/mhfpacket/msg_sys_update_right.go
index 0702a5b3e..99f0da4ae 100644
--- a/network/mhfpacket/msg_sys_update_right.go
+++ b/network/mhfpacket/msg_sys_update_right.go
@@ -2,12 +2,12 @@ package mhfpacket
import (
"errors"
- ps "erupe-ce/common/pascalstring"
- "golang.org/x/exp/slices"
-
"erupe-ce/common/byteframe"
+ ps "erupe-ce/common/pascalstring"
"erupe-ce/network"
"erupe-ce/network/clientctx"
+ "golang.org/x/exp/slices"
+ "math"
)
/*
@@ -77,18 +77,27 @@ func (m *MsgSysUpdateRight) Build(bf *byteframe.ByteFrame, ctx *clientctx.Client
func Courses() []Course {
var courses = []Course{
- {[]string{"Trial", "TL"}, 1, 0x00000002},
- {[]string{"HunterLife", "HL"}, 2, 0x00000004},
- {[]string{"ExtraA", "Extra", "EX"}, 3, 0x00000008},
- {[]string{"ExtraB"}, 4, 0x00000010},
- {[]string{"Mobile"}, 5, 0x00000020},
- {[]string{"Premium"}, 6, 0x00000040},
- {[]string{"Pallone"}, 7, 0x00000080},
- {[]string{"Assist", "Legend", "Rasta"}, 8, 0x00000100}, // Legend
- {[]string{"Netcafe", "N", "Cafe"}, 9, 0x00000200},
- {[]string{"Hiden", "Secret"}, 10, 0x00000400}, // Secret
- {[]string{"HunterSupport", "HunterAid", "Support", "Royal", "Aid"}, 11, 0x00000800}, // Royal
- {[]string{"NetcafeBoost", "NBoost", "Boost"}, 12, 0x00001000},
+ {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-25 do nothing
+ {Aliases: []string{"NetCafe", "Cafe", "InternetCafe"}, 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
}
diff --git a/patch-schema/achievements.sql b/patch-schema/achievements.sql
deleted file mode 100644
index 333ae5ed6..000000000
--- a/patch-schema/achievements.sql
+++ /dev/null
@@ -1,41 +0,0 @@
-BEGIN;
-
-CREATE TABLE IF NOT EXISTS public.achievements
-(
- id int NOT NULL PRIMARY KEY ,
- ach0 int DEFAULT 0,
- ach1 int DEFAULT 0,
- ach2 int DEFAULT 0,
- ach3 int DEFAULT 0,
- ach4 int DEFAULT 0,
- ach5 int DEFAULT 0,
- ach6 int DEFAULT 0,
- ach7 int DEFAULT 0,
- ach8 int DEFAULT 0,
- ach9 int DEFAULT 0,
- ach10 int DEFAULT 0,
- ach11 int DEFAULT 0,
- ach12 int DEFAULT 0,
- ach13 int DEFAULT 0,
- ach14 int DEFAULT 0,
- ach15 int DEFAULT 0,
- ach16 int DEFAULT 0,
- ach17 int DEFAULT 0,
- ach18 int DEFAULT 0,
- ach19 int DEFAULT 0,
- ach20 int DEFAULT 0,
- ach21 int DEFAULT 0,
- ach22 int DEFAULT 0,
- ach23 int DEFAULT 0,
- ach24 int DEFAULT 0,
- ach25 int DEFAULT 0,
- ach26 int DEFAULT 0,
- ach27 int DEFAULT 0,
- ach28 int DEFAULT 0,
- ach29 int DEFAULT 0,
- ach30 int DEFAULT 0,
- ach31 int DEFAULT 0,
- ach32 int DEFAULT 0
-);
-
-END;
\ No newline at end of file
diff --git a/patch-schema/active-feature.sql b/patch-schema/active-feature.sql
deleted file mode 100644
index f8b835100..000000000
--- a/patch-schema/active-feature.sql
+++ /dev/null
@@ -1,9 +0,0 @@
-BEGIN;
-
-CREATE TABLE IF NOT EXISTS public.feature_weapon
-(
- start_time timestamp without time zone NOT NULL,
- featured integer NOT NULL
-);
-
-END;
\ No newline at end of file
diff --git a/patch-schema/festa.sql b/patch-schema/festa.sql
deleted file mode 100644
index ce7d03594..000000000
--- a/patch-schema/festa.sql
+++ /dev/null
@@ -1,311 +0,0 @@
-BEGIN;
-
-CREATE TYPE event_type AS ENUM ('festa', 'diva', 'vs', 'mezfes');
-
-DROP TABLE IF EXISTS public.event_week;
-
-ALTER TABLE IF EXISTS public.guild_characters
- ADD COLUMN IF NOT EXISTS souls int DEFAULT 0;
-
-ALTER TABLE IF EXISTS public.guilds
- DROP COLUMN IF EXISTS festival_colour;
-
-CREATE TABLE IF NOT EXISTS public.events
-(
- id serial NOT NULL PRIMARY KEY,
- event_type event_type NOT NULL,
- start_time timestamp without time zone NOT NULL DEFAULT now()
-);
-
-CREATE TABLE IF NOT EXISTS public.festa_registrations
-(
- guild_id int NOT NULL,
- team festival_colour NOT NULL
-);
-
-CREATE TABLE IF NOT EXISTS public.festa_trials
-(
- id serial NOT NULL PRIMARY KEY,
- objective int NOT NULL,
- goal_id int NOT NULL,
- times_req int NOT NULL,
- locale_req int NOT NULL DEFAULT 0,
- reward int NOT NULL
-);
-
-CREATE TYPE prize_type AS ENUM ('personal', 'guild');
-
-CREATE TABLE IF NOT EXISTS public.festa_prizes
-(
- id serial NOT NULL PRIMARY KEY,
- type prize_type NOT NULL,
- tier int NOT NULL,
- souls_req int NOT NULL,
- item_id int NOT NULL,
- num_item int NOT NULL
-);
-
-CREATE TABLE IF NOT EXISTS public.festa_prizes_accepted
-(
- prize_id int NOT NULL,
- character_id int NOT NULL
-);
-
--- Ripped prizes
-INSERT INTO public.festa_prizes
- (type, tier, souls_req, item_id, num_item)
-VALUES
- ('personal', 1, 1, 9647, 7),
- ('personal', 2, 1, 9647, 7),
- ('personal', 3, 1, 9647, 7),
- ('personal', 1, 200, 11284, 4),
- ('personal', 2, 200, 11284, 4),
- ('personal', 3, 200, 11284, 4),
- ('personal', 1, 400, 11381, 3),
- ('personal', 2, 400, 11381, 3),
- ('personal', 3, 400, 11381, 3),
- ('personal', 1, 600, 11284, 8),
- ('personal', 2, 600, 11284, 8),
- ('personal', 3, 600, 11284, 8),
- ('personal', 1, 800, 11384, 3),
- ('personal', 2, 800, 11384, 3),
- ('personal', 3, 800, 11384, 3),
- ('personal', 1, 1000, 11284, 12),
- ('personal', 2, 1000, 11284, 12),
- ('personal', 3, 1000, 11284, 12),
- ('personal', 1, 1200, 11381, 5),
- ('personal', 2, 1200, 11381, 5),
- ('personal', 3, 1200, 11381, 5),
- ('personal', 1, 1400, 11284, 16),
- ('personal', 2, 1400, 11284, 16),
- ('personal', 3, 1400, 11284, 16),
- ('personal', 1, 1700, 11384, 5),
- ('personal', 2, 1700, 11384, 5),
- ('personal', 3, 1700, 11384, 5),
- ('personal', 1, 2000, 11284, 16),
- ('personal', 2, 2000, 11284, 16),
- ('personal', 3, 2000, 11284, 16),
- ('personal', 1, 2500, 11382, 4),
- ('personal', 2, 2500, 11382, 4),
- ('personal', 3, 2500, 11382, 4),
- ('personal', 1, 3000, 11284, 24),
- ('personal', 2, 3000, 11284, 24),
- ('personal', 3, 3000, 11284, 24),
- ('personal', 1, 4000, 11385, 4),
- ('personal', 2, 4000, 11385, 4),
- ('personal', 3, 4000, 11385, 4),
- ('personal', 1, 5000, 11381, 11),
- ('personal', 2, 5000, 11381, 11),
- ('personal', 3, 5000, 11381, 11),
- ('personal', 1, 6000, 5177, 5),
- ('personal', 2, 6000, 5177, 5),
- ('personal', 3, 6000, 5177, 5),
- ('personal', 1, 7000, 11384, 11),
- ('personal', 2, 7000, 11384, 11),
- ('personal', 3, 7000, 11384, 11),
- ('personal', 1, 10000, 11382, 8),
- ('personal', 2, 10000, 11382, 8),
- ('personal', 3, 10000, 11382, 8),
- ('personal', 1, 15000, 11385, 4),
- ('personal', 2, 15000, 11385, 4),
- ('personal', 3, 15000, 11385, 4),
- ('personal', 1, 20000, 11381, 13),
- ('personal', 2, 20000, 11381, 13),
- ('personal', 3, 20000, 11381, 13),
- ('personal', 1, 25000, 11385, 4),
- ('personal', 2, 25000, 11385, 4),
- ('personal', 3, 25000, 11385, 4),
- ('personal', 1, 30000, 11383, 1),
- ('personal', 2, 30000, 11383, 1),
- ('personal', 3, 30000, 11383, 1);
-
-INSERT INTO public.festa_prizes
-(type, tier, souls_req, item_id, num_item)
-VALUES
- ('guild', 1, 100, 7468, 5),
- ('guild', 2, 100, 7468, 5),
- ('guild', 3, 100, 7465, 5),
- ('guild', 1, 300, 7469, 5),
- ('guild', 2, 300, 7469, 5),
- ('guild', 3, 300, 7466, 5),
- ('guild', 1, 700, 7470, 5),
- ('guild', 2, 700, 7470, 5),
- ('guild', 3, 700, 7467, 5),
- ('guild', 1, 1500, 13405, 14),
- ('guild', 1, 1500, 1520, 3),
- ('guild', 2, 1500, 13405, 14),
- ('guild', 2, 1500, 1520, 3),
- ('guild', 3, 1500, 7011, 3),
- ('guild', 3, 1500, 13405, 14),
- ('guild', 1, 3000, 10201, 10),
- ('guild', 2, 3000, 10201, 10),
- ('guild', 3, 3000, 10201, 10),
- ('guild', 1, 6000, 13895, 14),
- ('guild', 1, 6000, 1520, 6),
- ('guild', 2, 6000, 13895, 14),
- ('guild', 2, 6000, 1520, 6),
- ('guild', 3, 6000, 13895, 14),
- ('guild', 3, 6000, 7011, 4),
- ('guild', 1, 12000, 13406, 14),
- ('guild', 1, 12000, 1520, 9),
- ('guild', 2, 12000, 13406, 14),
- ('guild', 2, 12000, 1520, 9),
- ('guild', 3, 12000, 13406, 14),
- ('guild', 3, 12000, 7011, 5),
- ('guild', 1, 25000, 10207, 10),
- ('guild', 2, 25000, 10207, 10),
- ('guild', 3, 25000, 10207, 10),
- ('guild', 1, 50000, 1520, 12),
- ('guild', 1, 50000, 13896, 14),
- ('guild', 2, 50000, 1520, 12),
- ('guild', 2, 50000, 13896, 14),
- ('guild', 3, 50000, 7011, 6),
- ('guild', 3, 50000, 13896, 14),
- ('guild', 1, 100000, 10201, 10),
- ('guild', 2, 100000, 10201, 10),
- ('guild', 3, 100000, 10201, 10),
- ('guild', 1, 200000, 13406, 16),
- ('guild', 2, 200000, 13406, 16),
- ('guild', 3, 200000, 13406, 16),
- ('guild', 1, 300000, 13896, 16),
- ('guild', 2, 300000, 13896, 16),
- ('guild', 3, 300000, 13896, 16),
- ('guild', 1, 400000, 10207, 10),
- ('guild', 2, 400000, 10207, 10),
- ('guild', 3, 400000, 10207, 10),
- ('guild', 1, 500000, 13407, 6),
- ('guild', 1, 500000, 13897, 6),
- ('guild', 2, 500000, 13407, 6),
- ('guild', 2, 500000, 13897, 6),
- ('guild', 3, 500000, 13407, 6),
- ('guild', 3, 500000, 13897, 6);
-
--- Ripped trials
-INSERT INTO public.festa_trials
- (objective, goal_id, times_req, locale_req, reward)
-VALUES
- (1,27,1,0,1),
- (5,53034,0,0,400),
- (5,22042,0,0,89),
- (5,23397,0,0,89),
- (1,28,1,0,1),
- (1,68,1,0,1),
- (1,6,1,0,2),
- (1,38,1,0,2),
- (1,20,1,0,3),
- (1,39,1,0,4),
- (1,48,1,0,4),
- (1,67,1,0,4),
- (1,93,1,0,4),
- (1,22,1,0,5),
- (1,52,1,0,5),
- (1,101,1,0,5),
- (1,1,1,0,5),
- (1,37,1,0,5),
- (1,15,1,0,5),
- (1,45,1,0,5),
- (1,74,1,0,5),
- (1,78,1,0,5),
- (1,103,1,0,5),
- (1,51,1,0,6),
- (1,17,1,0,6),
- (1,21,1,0,6),
- (1,92,1,0,6),
- (1,47,1,0,7),
- (1,46,1,0,7),
- (1,26,1,0,7),
- (1,14,1,0,7),
- (1,11,1,0,7),
- (1,44,1,0,8),
- (1,43,1,0,8),
- (1,49,1,0,8),
- (1,40,1,0,8),
- (1,76,1,0,8),
- (1,89,1,0,8),
- (1,94,1,0,8),
- (1,96,1,0,8),
- (1,75,1,0,8),
- (1,91,1,0,8),
- (1,53,1,0,9),
- (1,80,1,0,9),
- (1,42,1,0,9),
- (1,79,1,0,9),
- (1,81,1,0,10),
- (1,41,1,0,10),
- (1,82,1,0,10),
- (1,90,1,0,10),
- (1,149,1,0,10),
- (1,85,1,0,11),
- (1,95,1,0,11),
- (1,121,1,0,11),
- (1,142,1,0,11),
- (1,141,1,0,11),
- (1,146,1,0,12),
- (1,147,1,0,12),
- (1,148,1,0,12),
- (1,151,1,0,12),
- (1,152,1,0,12),
- (1,159,1,0,12),
- (1,153,1,0,12),
- (1,162,1,0,12),
- (1,111,1,0,13),
- (1,110,1,0,13),
- (1,112,1,0,13),
- (1,109,1,0,14),
- (1,169,1,0,15),
- (2,33,1,0,6),
- (2,104,1,0,8),
- (2,119,1,0,8),
- (2,120,1,0,8),
- (2,54,1,0,8),
- (2,59,1,0,8),
- (2,64,1,0,8),
- (2,65,1,0,8),
- (2,99,1,0,9),
- (2,83,1,0,9),
- (2,84,1,0,10),
- (2,77,1,0,10),
- (2,106,1,0,10),
- (2,55,1,0,10),
- (2,58,1,0,10),
- (2,7,1,0,10),
- (2,50,1,0,11),
- (2,131,1,0,11),
- (2,129,1,0,11),
- (2,140,1,0,11),
- (2,122,1,0,11),
- (2,126,1,0,11),
- (2,127,1,0,11),
- (2,128,1,0,11),
- (2,130,1,0,11),
- (2,139,1,0,11),
- (2,144,1,0,11),
- (2,150,1,0,11),
- (2,158,1,0,11),
- (2,164,1,0,15),
- (2,165,1,0,15),
- (2,2,1,7,15),
- (2,36,1,0,15),
- (2,71,1,0,15),
- (2,108,1,0,15),
- (2,116,1,0,15),
- (2,107,1,0,15),
- (2,154,1,0,17),
- (2,166,1,0,17),
- (2,170,1,0,18),
- (3,31,1,0,1),
- (3,8,1,0,3),
- (3,123,1,0,8),
- (3,105,1,0,9),
- (3,125,1,0,11),
- (3,115,1,0,12),
- (3,114,1,0,12),
- (3,161,1,0,12),
- (4,670,1,0,1),
- (4,671,1,0,1),
- (4,672,1,0,1),
- (4,675,1,0,1),
- (4,673,1,0,1),
- (4,674,1,0,1);
-
-END;
\ No newline at end of file
diff --git a/patch-schema/guild-enumeration.sql b/patch-schema/guild-enumeration.sql
deleted file mode 100644
index 1e7e522a3..000000000
--- a/patch-schema/guild-enumeration.sql
+++ /dev/null
@@ -1,9 +0,0 @@
-BEGIN;
-
-ALTER TABLE IF EXISTS public.guilds
- ADD COLUMN IF NOT EXISTS recruiting bool NOT NULL DEFAULT true;
-
-ALTER TABLE IF EXISTS public.guild_characters
- ADD COLUMN IF NOT EXISTS recruiter bool NOT NULL DEFAULT false;
-
-END;
\ No newline at end of file
diff --git a/patch-schema/guild-poogie-outfits.sql b/patch-schema/guild-poogie-outfits.sql
deleted file mode 100644
index b6ce2e4fa..000000000
--- a/patch-schema/guild-poogie-outfits.sql
+++ /dev/null
@@ -1,15 +0,0 @@
-BEGIN;
-
-ALTER TABLE IF EXISTS public.guilds
- ADD COLUMN IF NOT EXISTS pugi_outfit_1 int NOT NULL DEFAULT 0;
-
-ALTER TABLE IF EXISTS public.guilds
- ADD COLUMN IF NOT EXISTS pugi_outfit_2 int NOT NULL DEFAULT 0;
-
-ALTER TABLE IF EXISTS public.guilds
- ADD COLUMN IF NOT EXISTS pugi_outfit_3 int NOT NULL DEFAULT 0;
-
-ALTER TABLE IF EXISTS public.guilds
- ADD COLUMN IF NOT EXISTS pugi_outfits int NOT NULL DEFAULT 0;
-
-END;
\ No newline at end of file
diff --git a/patch-schema/mail-system-messages.sql b/patch-schema/mail-system-messages.sql
deleted file mode 100644
index 4ce8dfaf6..000000000
--- a/patch-schema/mail-system-messages.sql
+++ /dev/null
@@ -1,13 +0,0 @@
-BEGIN;
-
-ALTER TABLE IF EXISTS public.mail
- ADD COLUMN IF NOT EXISTS is_sys_message bool DEFAULT false;
-
-UPDATE mail SET is_sys_message=false;
-
-ALTER TABLE IF EXISTS public.mail
- DROP CONSTRAINT IF EXISTS mail_sender_id_fkey;
-
-INSERT INTO public.characters (id, name) VALUES (0, '');
-
-END;
\ No newline at end of file
diff --git a/patch-schema/mercenary.sql b/patch-schema/mercenary.sql
deleted file mode 100644
index 9000db9f3..000000000
--- a/patch-schema/mercenary.sql
+++ /dev/null
@@ -1,7 +0,0 @@
-BEGIN;
-
-CREATE SEQUENCE IF NOT EXISTS public.rasta_id_seq;
-
-UPDATE characters SET savemercenary=NULL;
-
-END;
\ No newline at end of file
diff --git a/patch-schema/missing-mail.sql b/patch-schema/missing-mail.sql
deleted file mode 100644
index 601c77d4f..000000000
--- a/patch-schema/missing-mail.sql
+++ /dev/null
@@ -1,6 +0,0 @@
-BEGIN;
-
-ALTER TABLE IF EXISTS public.mail
- ADD COLUMN IF NOT EXISTS locked boolean NOT NULL DEFAULT false;
-
-END;
\ No newline at end of file
diff --git a/patch-schema/netcafe-2.sql b/patch-schema/netcafe-2.sql
deleted file mode 100644
index d2a1f0763..000000000
--- a/patch-schema/netcafe-2.sql
+++ /dev/null
@@ -1,6 +0,0 @@
-BEGIN;
-
-ALTER TABLE IF EXISTS public.characters
- ADD COLUMN cafe_reset timestamp without time zone;
-
-END;
\ No newline at end of file
diff --git a/patch-schema/netcafe.sql b/patch-schema/netcafe.sql
deleted file mode 100644
index 563e10fb0..000000000
--- a/patch-schema/netcafe.sql
+++ /dev/null
@@ -1,30 +0,0 @@
-BEGIN;
-
-ALTER TABLE IF EXISTS public.characters
- ADD COLUMN IF NOT EXISTS cafe_time integer DEFAULT 0;
-
-ALTER TABLE IF EXISTS public.characters
- DROP COLUMN IF EXISTS netcafe_points;
-
-ALTER TABLE IF EXISTS public.characters
- ADD COLUMN IF NOT EXISTS netcafe_points int DEFAULT 0;
-
-ALTER TABLE IF EXISTS public.characters
- ADD COLUMN IF NOT EXISTS boost_time timestamp without time zone;
-
-CREATE TABLE IF NOT EXISTS public.cafebonus
-(
- id serial NOT NULL PRIMARY KEY,
- time_req integer NOT NULL,
- item_type integer NOT NULL,
- item_id integer NOT NULL,
- quantity integer NOT NULL
-);
-
-CREATE TABLE IF NOT EXISTS public.cafe_accepted
-(
- cafe_id integer NOT NULL,
- character_id integer NOT NULL
-);
-
-END;
\ No newline at end of file
diff --git a/patch-schema/persistent-house.sql b/patch-schema/persistent-house.sql
deleted file mode 100644
index 43a02da91..000000000
--- a/patch-schema/persistent-house.sql
+++ /dev/null
@@ -1,37 +0,0 @@
-BEGIN;
-
-CREATE TABLE IF NOT EXISTS public.user_binary
-(
- id serial NOT NULL PRIMARY KEY,
- type2 bytea,
- type3 bytea,
- house_tier bytea,
- house_state int,
- house_password text,
- house_data bytea,
- house_furniture bytea,
- bookshelf bytea,
- gallery bytea,
- tore bytea,
- garden bytea,
- mission bytea
-);
-
--- Create entries for existing users
-INSERT INTO public.user_binary (id) SELECT c.id FROM characters c;
-
--- Copy existing data
-UPDATE public.user_binary
- SET house_furniture = (SELECT house FROM characters WHERE user_binary.id = characters.id);
-
-UPDATE public.user_binary
- SET mission = (SELECT trophy FROM characters WHERE user_binary.id = characters.id);
-
--- Drop old data location
-ALTER TABLE public.characters
- DROP COLUMN house;
-
-ALTER TABLE public.characters
- DROP COLUMN trophy;
-
-END;
\ No newline at end of file
diff --git a/patch-schema/return.sql b/patch-schema/return.sql
deleted file mode 100644
index 4e09d7e47..000000000
--- a/patch-schema/return.sql
+++ /dev/null
@@ -1,9 +0,0 @@
-BEGIN;
-
-ALTER TABLE IF EXISTS public.users
- ADD COLUMN IF NOT EXISTS last_login timestamp without time zone;
-
-ALTER TABLE IF EXISTS public.users
- ADD COLUMN IF NOT EXISTS return_expires timestamp without time zone;
-
-END;
\ No newline at end of file
diff --git a/patch-schema/revert-road-shop.sql b/patch-schema/revert-road-shop.sql
deleted file mode 100644
index 830f3aa0f..000000000
--- a/patch-schema/revert-road-shop.sql
+++ /dev/null
@@ -1,9 +0,0 @@
-BEGIN;
-
-ALTER TABLE IF EXISTS public.normal_shop_items
- DROP COLUMN IF EXISTS enable_weeks;
-
-ALTER TABLE IF EXISTS public.shop_item_state
- DROP COLUMN IF EXISTS week;
-
-END;
\ No newline at end of file
diff --git a/patch-schema/road-leaderboard.sql b/patch-schema/road-leaderboard.sql
deleted file mode 100644
index 8dfea2875..000000000
--- a/patch-schema/road-leaderboard.sql
+++ /dev/null
@@ -1,11 +0,0 @@
-BEGIN;
-
-CREATE TABLE IF NOT EXISTS rengoku_score (
- character_id integer PRIMARY KEY,
- max_stages_mp integer,
- max_points_mp integer,
- max_stages_sp integer,
- max_points_sp integer
-);
-
-END;
\ No newline at end of file
diff --git a/patch-schema/servers_info.sql b/patch-schema/servers_info.sql
deleted file mode 100644
index 06389b6ed..000000000
--- a/patch-schema/servers_info.sql
+++ /dev/null
@@ -1,23 +0,0 @@
---adds world_name and land columns
-BEGIN;
-
-CREATE TABLE IF NOT EXISTS public.servers
-(
- server_id integer NOT NULL,
- season integer NOT NULL,
- current_players integer NOT NULL,
- world_name text COLLATE pg_catalog."default",
- world_description text,
- land integer
-);
-
-ALTER TABLE public.servers
- ADD COLUMN IF NOT EXISTS land integer;
-
-ALTER TABLE public.servers
- ADD COLUMN IF NOT EXISTS world_name text COLLATE pg_catalog."default";
-
-ALTER TABLE public.servers
- ADD COLUMN IF NOT EXISTS world_description text;
-
-END;
\ No newline at end of file
diff --git a/patch-schema/stamps.sql b/patch-schema/stamps.sql
deleted file mode 100644
index 2b940fa8c..000000000
--- a/patch-schema/stamps.sql
+++ /dev/null
@@ -1,13 +0,0 @@
-BEGIN;
-
-CREATE TABLE IF NOT EXISTS public.stamps (
- character_id integer PRIMARY KEY,
- hl_total integer DEFAULT 0,
- hl_redeemed integer DEFAULT 0,
- hl_next timestamp without time zone,
- ex_total integer DEFAULT 0,
- ex_redeemed integer DEFAULT 0,
- ex_next timestamp without time zone
-);
-
-END;
\ No newline at end of file
diff --git a/patch-schema/titles.sql b/patch-schema/titles.sql
deleted file mode 100644
index e4a87dc86..000000000
--- a/patch-schema/titles.sql
+++ /dev/null
@@ -1,11 +0,0 @@
-BEGIN;
-
-CREATE TABLE IF NOT EXISTS public.titles
-(
- id int NOT NULL,
- char_id int NOT NULL,
- unlocked_at timestamp without time zone,
- updated_at timestamp without time zone
-);
-
-END;
\ No newline at end of file
diff --git a/patch-schema/warehouse.sql b/patch-schema/warehouse.sql
deleted file mode 100644
index 2f2a5adde..000000000
--- a/patch-schema/warehouse.sql
+++ /dev/null
@@ -1,49 +0,0 @@
-BEGIN;
-
-CREATE TABLE IF NOT EXISTS public.warehouse (
- character_id integer PRIMARY KEY,
- item0 bytea,
- item1 bytea,
- item2 bytea,
- item3 bytea,
- item4 bytea,
- item5 bytea,
- item6 bytea,
- item7 bytea,
- item8 bytea,
- item9 bytea,
- item10 bytea,
- item0name text,
- item1name text,
- item2name text,
- item3name text,
- item4name text,
- item5name text,
- item6name text,
- item7name text,
- item8name text,
- item9name text,
- equip0 bytea,
- equip1 bytea,
- equip2 bytea,
- equip3 bytea,
- equip4 bytea,
- equip5 bytea,
- equip6 bytea,
- equip7 bytea,
- equip8 bytea,
- equip9 bytea,
- equip10 bytea,
- equip0name text,
- equip1name text,
- equip2name text,
- equip3name text,
- equip4name text,
- equip5name text,
- equip6name text,
- equip7name text,
- equip8name text,
- equip9name text
-);
-
-END;
\ No newline at end of file
diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go
index 5d19d93f4..d2ea7b678 100644
--- a/server/channelserver/handlers.go
+++ b/server/channelserver/handlers.go
@@ -77,7 +77,13 @@ func updateRights(s *Session) {
s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt)
s.courses = mhfpacket.GetCourseStruct(rightsInt)
rights := []mhfpacket.ClientRight{{1, 0, 0}}
+ var netcafeBitSet bool
for _, course := range s.courses {
+ if (course.ID == 9 || course.ID == 26) && !netcafeBitSet {
+ netcafeBitSet = true
+ rightsInt += 0x40000000 // set netcafe bit
+ rights = append(rights, mhfpacket.ClientRight{ID: 30})
+ }
rights = append(rights, mhfpacket.ClientRight{ID: course.ID, Timestamp: 0x70DB59F0})
}
update := &mhfpacket.MsgSysUpdateRight{
@@ -135,6 +141,7 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
s.charID = pkt.CharID0
s.token = pkt.LoginTokenString
s.Unlock()
+
updateRights(s)
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // Unix timestamp
@@ -213,16 +220,16 @@ func logoutPlayer(s *Session) {
timePlayed += sessionTime
var rpGained int
- if s.FindCourse("Netcafe").ID != 0 {
+ if s.FindCourse("NetCafe").ID != 0 || s.FindCourse("N").ID != 0 {
rpGained = timePlayed / 900
timePlayed = timePlayed % 900
+ s.server.db.Exec("UPDATE characters SET cafe_time=cafe_time+$1 WHERE id=$2", sessionTime, s.charID)
} else {
rpGained = timePlayed / 1800
timePlayed = timePlayed % 1800
}
s.server.db.Exec("UPDATE characters SET time_played = $1 WHERE id = $2", timePlayed, s.charID)
- s.server.db.Exec("UPDATE characters SET cafe_time=cafe_time+$1 WHERE id=$2", sessionTime, s.charID)
treasureHuntUnregister(s)
@@ -1489,10 +1496,6 @@ func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
-func handleMsgMhfGetBbsSnsStatus(s *Session, p mhfpacket.MHFPacket) {}
-
-func handleMsgMhfApplyBbsArticle(s *Session, p mhfpacket.MHFPacket) {}
-
func handleMsgMhfGetEtcPoints(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetEtcPoints)
diff --git a/server/channelserver/handlers_bbs.go b/server/channelserver/handlers_bbs.go
new file mode 100644
index 000000000..222a8eadc
--- /dev/null
+++ b/server/channelserver/handlers_bbs.go
@@ -0,0 +1,41 @@
+package channelserver
+
+import (
+ "erupe-ce/common/byteframe"
+ "erupe-ce/common/stringsupport"
+ "erupe-ce/common/token"
+ "erupe-ce/network/mhfpacket"
+)
+
+func handleMsgMhfGetBbsUserStatus(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfGetBbsUserStatus)
+ bf := byteframe.NewByteFrame()
+ bf.WriteUint32(200)
+ bf.WriteUint32(0)
+ bf.WriteUint32(0)
+ bf.WriteUint32(0)
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+}
+
+func handleMsgMhfGetBbsSnsStatus(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfGetBbsSnsStatus)
+ bf := byteframe.NewByteFrame()
+ bf.WriteUint32(200)
+ bf.WriteUint32(401)
+ bf.WriteUint32(401)
+ bf.WriteUint32(0)
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+}
+
+func handleMsgMhfApplyBbsArticle(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfApplyBbsArticle)
+ bf := byteframe.NewByteFrame()
+ articleToken := token.Generate(40)
+ bf.WriteUint32(200)
+ bf.WriteUint32(80)
+ bf.WriteUint32(0)
+ bf.WriteUint32(0)
+ bf.WriteBytes(stringsupport.PaddedString(articleToken, 64, false))
+ bf.WriteBytes(stringsupport.PaddedString(s.server.erupeConfig.ScreenshotAPIURL, 64, false))
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+}
diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go
index 15b01f510..596bb5b33 100644
--- a/server/channelserver/handlers_cafe.go
+++ b/server/channelserver/handlers_cafe.go
@@ -74,9 +74,14 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
var cafeReset time.Time
err := s.server.db.QueryRow(`SELECT cafe_reset FROM characters WHERE id=$1`, s.charID).Scan(&cafeReset)
+ if err != nil {
+ cafeReset = TimeWeekNext()
+ s.server.db.Exec(`UPDATE characters SET cafe_reset=$1 WHERE id=$2`, cafeReset, s.charID)
+ }
if Time_Current_Adjusted().After(cafeReset) {
cafeReset = TimeWeekNext()
- s.server.db.Exec(`UPDATE characters SET cafe_time=0, cafe_reset=$1 WHERE id=$2; DELETE FROM cafe_accepted WHERE character_id=$2`, cafeReset, s.charID)
+ s.server.db.Exec(`UPDATE characters SET cafe_time=0, cafe_reset=$1 WHERE id=$2`, cafeReset, s.charID)
+ s.server.db.Exec(`DELETE FROM cafe_accepted WHERE character_id=$1`, s.charID)
}
var cafeTime uint32
@@ -84,10 +89,13 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
if err != nil {
panic(err)
}
- cafeTime = uint32(Time_Current_Adjusted().Unix()) - uint32(s.sessionStart) + cafeTime
+ if s.FindCourse("NetCafe").ID != 0 || s.FindCourse("N").ID != 0 {
+ cafeTime = uint32(Time_Current_Adjusted().Unix()) - uint32(s.sessionStart) + cafeTime
+ }
bf.WriteUint32(cafeTime) // Total cafe time
bf.WriteUint16(0)
- ps.Uint16(bf, fmt.Sprintf("Resets on %s %d", cafeReset.Month().String(), cafeReset.Day()), true)
+ ps.Uint16(bf, fmt.Sprintf(s.server.dict["cafeReset"], int(cafeReset.Month()), cafeReset.Day()), true)
+
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go
index 64666d9af..17a1d7858 100644
--- a/server/channelserver/handlers_cast_binary.go
+++ b/server/channelserver/handlers_cast_binary.go
@@ -324,7 +324,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
for _, alias := range course.Aliases {
if strings.ToLower(name) == strings.ToLower(alias) {
if slices.Contains(s.server.erupeConfig.Courses, config.Course{Name: course.Aliases[0], Enabled: true}) {
- if s.FindCourse(name).Value != 0 {
+ if s.FindCourse(name).ID != 0 {
ei := slices.IndexFunc(s.courses, func(c mhfpacket.Course) bool {
for _, alias := range c.Aliases {
if strings.ToLower(name) == strings.ToLower(alias) {
@@ -335,20 +335,20 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
})
if ei != -1 {
s.courses = append(s.courses[:ei], s.courses[ei+1:]...)
- sendServerChatMessage(s, fmt.Sprintf(`%s Course disabled.`, course.Aliases[0]))
+ sendServerChatMessage(s, fmt.Sprintf(`%s Course disabled`, course.Aliases[0]))
}
} else {
s.courses = append(s.courses, course)
- sendServerChatMessage(s, fmt.Sprintf(`%s Course enabled.`, course.Aliases[0]))
+ sendServerChatMessage(s, fmt.Sprintf(`%s Course enabled`, course.Aliases[0]))
}
var newInt uint32
for _, course := range s.courses {
- newInt += course.Value
+ newInt += uint32(math.Pow(2, float64(course.ID)))
}
s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", newInt, s.charID)
updateRights(s)
} else {
- sendServerChatMessage(s, fmt.Sprintf(`%s Course is locked.`, course.Aliases[0]))
+ sendServerChatMessage(s, fmt.Sprintf(`%s Course is locked`, course.Aliases[0]))
}
}
}
diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go
index a6129bd1e..0e670bd14 100644
--- a/server/channelserver/handlers_guild.go
+++ b/server/channelserver/handlers_guild.go
@@ -2005,6 +2005,7 @@ func handleMsgMhfCheckMonthlyItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCheckMonthlyItem)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
// TODO: Implement month-by-month tracker, 0 = Not claimed, 1 = Claimed
+ // Also handles HLC and EXC items, IDs = 064D, 076B
}
func handleMsgMhfAcquireMonthlyItem(s *Session, p mhfpacket.MHFPacket) {
diff --git a/server/channelserver/handlers_guild_scout.go b/server/channelserver/handlers_guild_scout.go
index f47c28bc6..4b45cc93b 100644
--- a/server/channelserver/handlers_guild_scout.go
+++ b/server/channelserver/handlers_guild_scout.go
@@ -62,10 +62,9 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
mail := &Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
- Subject: "Invitation!",
+ Subject: s.server.dict["guildInviteName"],
Body: fmt.Sprintf(
- "%s has invited you to join 「%s」\nDo you want to accept?",
- getCharacterName(s, s.charID),
+ s.server.dict["guildInvite"],
guildInfo.Name,
),
IsGuildInvite: true,
@@ -149,30 +148,30 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
err = guild.AcceptApplication(s, s.charID)
mail = append(mail, Mail{
RecipientID: s.charID,
- Subject: "Success!",
- Body: fmt.Sprintf("You successfully joined 「%s」.", guild.Name),
+ Subject: s.server.dict["guildInviteSuccessName"],
+ Body: fmt.Sprintf(s.server.dict["guildInviteSuccess"], guild.Name),
IsSystemMessage: true,
})
mail = append(mail, Mail{
SenderID: s.charID,
RecipientID: pkt.LeaderID,
- Subject: "Accepted",
- Body: fmt.Sprintf("%s accepted your invitation to join 「%s」.", s.Name, guild.Name),
+ Subject: s.server.dict["guildInviteAcceptedName"],
+ Body: fmt.Sprintf(s.server.dict["guildInviteAccepted"], guild.Name),
IsSystemMessage: true,
})
} else {
err = guild.RejectApplication(s, s.charID)
mail = append(mail, Mail{
RecipientID: s.charID,
- Subject: "Declined",
- Body: fmt.Sprintf("You declined the invitation to join 「%s」.", guild.Name),
+ Subject: s.server.dict["guildInviteRejectName"],
+ Body: fmt.Sprintf(s.server.dict["guildInviteReject"], guild.Name),
IsSystemMessage: true,
})
mail = append(mail, Mail{
SenderID: s.charID,
RecipientID: pkt.LeaderID,
- Subject: "Declined",
- Body: fmt.Sprintf("%s declined your invitation to join 「%s」.", s.Name, guild.Name),
+ Subject: s.server.dict["guildInviteDeclined"],
+ Body: fmt.Sprintf(s.server.dict["guildInviteDeclined"], guild.Name),
IsSystemMessage: true,
})
}
diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go
index 347b17ca8..e88262cbd 100644
--- a/server/channelserver/handlers_quest.go
+++ b/server/channelserver/handlers_quest.go
@@ -2,13 +2,13 @@ package channelserver
import (
"fmt"
- "go.uber.org/zap"
"io"
"os"
"path/filepath"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
+ "go.uber.org/zap"
)
func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
diff --git a/server/channelserver/handlers_users.go b/server/channelserver/handlers_users.go
index c360b82a6..fc8b47a8d 100644
--- a/server/channelserver/handlers_users.go
+++ b/server/channelserver/handlers_users.go
@@ -54,5 +54,3 @@ func handleMsgSysGetUserBinary(s *Session, p mhfpacket.MHFPacket) {
}
func handleMsgSysNotifyUserBinary(s *Session, p mhfpacket.MHFPacket) {}
-
-func handleMsgMhfGetBbsUserStatus(s *Session, p mhfpacket.MHFPacket) {}
diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go
index 82f29d03a..7c7447dfa 100644
--- a/server/channelserver/sys_channel_server.go
+++ b/server/channelserver/sys_channel_server.go
@@ -52,6 +52,9 @@ type Server struct {
stagesLock sync.RWMutex
stages map[string]*Stage
+ // Used to map different languages
+ dict map[string]string
+
// UserBinary
userBinaryPartsLock sync.RWMutex
userBinaryParts map[userBinaryPartID][]byte
@@ -164,6 +167,8 @@ func NewServer(config *Config) *Server {
// MezFes
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
+ s.dict = getLangStrings(s)
+
return s
}
@@ -279,6 +284,7 @@ func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session,
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
pkt.Build(bf, session.clientContext)
+ bf.WriteUint16(0x0010)
session.QueueSendNonBlocking(bf.Data())
}
}
@@ -313,15 +319,17 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
var text string
switch _type {
case 2:
- text = " is being held!"
+ text = s.dict["ravienteBerserk"]
+ case 3:
+ text = s.dict["ravienteExtreme"]
case 4:
- text = " is being held!"
+ text = s.dict["ravienteExtremeLimited"]
case 5:
- text = " is being held!"
+ text = s.dict["ravienteBerserkSmall"]
default:
s.logger.Error("Unk raviente type", zap.Uint8("_type", _type))
}
- ps.Uint16(bf, text, false)
+ ps.Uint16(bf, text, true)
bf.WriteBytes([]byte{0x5F, 0x53, 0x00})
bf.WriteUint32(ip) // IP address
bf.WriteUint16(port) // Port
diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go
new file mode 100644
index 000000000..1dc917559
--- /dev/null
+++ b/server/channelserver/sys_language.go
@@ -0,0 +1,52 @@
+package channelserver
+
+func getLangStrings(s *Server) map[string]string {
+ strings := make(map[string]string)
+ switch s.erupeConfig.Language {
+ case "jp":
+ strings["language"] = "日本語"
+ strings["cafeReset"] = "%d/%dにリセット"
+ strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!"
+ strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!"
+ strings["ravienteExtremeLimited"] = "<大討伐:猛狂期【極】(制限付)>が開催されました!"
+ strings["ravienteBerserkSmall"] = "<大討伐:猛狂期(小数)>が開催されました!"
+
+ strings["guildInviteName"] = "猟団勧誘のご案内"
+ strings["guildInvite"] = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。"
+
+ strings["guildInviteSuccessName"] = "成功"
+ strings["guildInviteSuccess"] = "あなたは「%s」に参加できました。"
+
+ strings["guildInviteAcceptedName"] = "承諾されました"
+ strings["guildInviteAccepted"] = "招待した狩人が「%s」への招待を承諾しました。"
+
+ strings["guildInviteRejectName"] = "却下しました"
+ strings["guildInviteReject"] = "あなたは「%s」への参加を却下しました。"
+
+ strings["guildInviteDeclinedName"] = "辞退しました"
+ strings["guildInviteDeclined"] = "招待した狩人が「%s」への招待を辞退しました。"
+ default:
+ strings["language"] = "English"
+ strings["cafeReset"] = "Resets on %d/%d"
+ strings["ravienteBerserk"] = " is being held!"
+ strings["ravienteExtreme"] = " is being held!"
+ strings["ravienteExtremeLimited"] = " is being held!"
+ strings["ravienteBerserkSmall"] = " is being held!"
+
+ strings["guildInviteName"] = "Invitation!"
+ strings["guildInvite"] = "You have been invited to join\n「%s」\nDo you want to accept?"
+
+ strings["guildInviteSuccessName"] = "Success!"
+ strings["guildInviteSuccess"] = "You have successfully joined\n「%s」."
+
+ strings["guildInviteAcceptedName"] = "Accepted"
+ strings["guildInviteAccepted"] = "The recipient accepted your invitation to join\n「%s」."
+
+ strings["guildInviteRejectName"] = "Rejected"
+ strings["guildInviteReject"] = "You rejected the invitation to join\n「%s」."
+
+ strings["guildInviteDeclinedName"] = "Declined"
+ strings["guildInviteDeclined"] = "The recipient declined your invitation to join\n「%s」."
+ }
+ return strings
+}
diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go
index 39a96f40f..2ebc6d080 100644
--- a/server/channelserver/sys_session.go
+++ b/server/channelserver/sys_session.go
@@ -116,10 +116,9 @@ func (s *Session) QueueSend(data []byte) {
func (s *Session) QueueSendNonBlocking(data []byte) {
select {
case s.sendPackets <- packet{data, true}:
- // Enqueued data
+ s.logMessage(binary.BigEndian.Uint16(data[0:2]), data, "Server", s.Name)
default:
s.logger.Warn("Packet queue too full, dropping!")
- // Queue too full
}
}
diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go
index 3966af8d6..9335de0c0 100644
--- a/server/signserver/dsgn_resp.go
+++ b/server/signserver/dsgn_resp.go
@@ -4,6 +4,7 @@ import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport"
+ "erupe-ce/common/token"
"erupe-ce/server/channelserver"
"fmt"
"math/rand"
@@ -18,15 +19,6 @@ func makeSignInFailureResp(respID RespID) []byte {
return bf.Data()
}
-func randSeq(n int) string {
- var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
- b := make([]rune, n)
- for i := range b {
- b[i] = letters[rand.Intn(len(letters))]
- }
- return string(b)
-}
-
func (s *Session) makeSignInResp(uid int) []byte {
returnExpiry := s.server.getReturnExpiry(uid)
@@ -37,13 +29,13 @@ func (s *Session) makeSignInResp(uid int) []byte {
}
rand.Seed(time.Now().UnixNano())
- token := randSeq(16)
- s.server.registerToken(uid, token)
+ sessToken := token.Generate(16)
+ s.server.registerToken(uid, sessToken)
bf := byteframe.NewByteFrame()
bf.WriteUint8(1) // resp_code
- if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.PatchServerManifest != "" && s.server.erupeConfig.DevModeOptions.PatchServerFile != "" {
+ if s.server.erupeConfig.DevMode && s.server.erupeConfig.PatchServerManifest != "" && s.server.erupeConfig.PatchServerFile != "" {
bf.WriteUint8(2)
} else {
bf.WriteUint8(0)
@@ -51,12 +43,12 @@ func (s *Session) makeSignInResp(uid int) []byte {
bf.WriteUint8(1) // entrance server count
bf.WriteUint8(uint8(len(chars))) // character count
bf.WriteUint32(0xFFFFFFFF) // login_token_number
- bf.WriteBytes([]byte(token)) // login_token
+ bf.WriteBytes([]byte(sessToken)) // login_token
bf.WriteUint32(uint32(time.Now().Unix())) // current time
if s.server.erupeConfig.DevMode {
- if s.server.erupeConfig.DevModeOptions.PatchServerManifest != "" && s.server.erupeConfig.DevModeOptions.PatchServerFile != "" {
- ps.Uint8(bf, s.server.erupeConfig.DevModeOptions.PatchServerManifest, false)
- ps.Uint8(bf, s.server.erupeConfig.DevModeOptions.PatchServerFile, false)
+ if s.server.erupeConfig.PatchServerManifest != "" && s.server.erupeConfig.PatchServerFile != "" {
+ ps.Uint8(bf, s.server.erupeConfig.PatchServerManifest, false)
+ ps.Uint8(bf, s.server.erupeConfig.PatchServerFile, false)
}
}
ps.Uint8(bf, fmt.Sprintf("%s:%d", s.server.erupeConfig.Host, s.server.erupeConfig.Entrance.Port), false)
@@ -111,11 +103,11 @@ func (s *Session) makeSignInResp(uid int) []byte {
}
}
- if s.server.erupeConfig.DevModeOptions.HideLoginNotice {
+ if s.server.erupeConfig.HideLoginNotice {
bf.WriteUint8(0)
} else {
bf.WriteUint8(1) // Notice count
- noticeText := s.server.erupeConfig.DevModeOptions.LoginNotice
+ noticeText := s.server.erupeConfig.LoginNotice
ps.Uint32(bf, noticeText, true)
}
diff --git a/server/signv2server/dbutils.go b/server/signv2server/dbutils.go
new file mode 100644
index 000000000..3c8494c95
--- /dev/null
+++ b/server/signv2server/dbutils.go
@@ -0,0 +1,122 @@
+package signv2server
+
+import (
+ "context"
+ "database/sql"
+ "erupe-ce/common/token"
+ "fmt"
+ "time"
+
+ "golang.org/x/crypto/bcrypt"
+)
+
+func (s *Server) createNewUser(ctx context.Context, username string, password string) (int, error) {
+ // Create salted hash of user password
+ passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ if err != nil {
+ return 0, err
+ }
+
+ var id int
+ err = s.db.QueryRowContext(
+ ctx, `
+ INSERT INTO users (username, password, return_expires)
+ VALUES ($1, $2, $3)
+ RETURNING id
+ `,
+ username, string(passwordHash), time.Now().Add(time.Hour*24*30),
+ ).Scan(&id)
+ return id, err
+}
+
+func (s *Server) createLoginToken(ctx context.Context, uid int) (string, error) {
+ loginToken := token.Generate(16)
+ _, err := s.db.ExecContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2)", uid, loginToken)
+ if err != nil {
+ return "", err
+ }
+ return loginToken, nil
+}
+
+func (s *Server) userIDFromToken(ctx context.Context, token string) (int, error) {
+ var userID int
+ err := s.db.QueryRowContext(ctx, "SELECT user_id FROM sign_sessions WHERE token = $1", token).Scan(&userID)
+ if err == sql.ErrNoRows {
+ return 0, fmt.Errorf("invalid login token")
+ } else if err != nil {
+ return 0, err
+ }
+ return userID, nil
+}
+
+func (s *Server) createCharacter(ctx context.Context, userID int) (int, error) {
+ var charID int
+ err := s.db.QueryRowContext(ctx,
+ "SELECT id FROM characters WHERE is_new_character = true AND user_id = $1",
+ userID,
+ ).Scan(&charID)
+ if err == sql.ErrNoRows {
+ err = s.db.QueryRowContext(ctx, `
+ INSERT INTO characters (
+ user_id, is_female, is_new_character, name, unk_desc_string,
+ hrp, gr, weapon_type, last_login
+ )
+ VALUES ($1, false, true, '', '', 0, 0, 0, $2)
+ RETURNING id`,
+ userID, uint32(time.Now().Unix()),
+ ).Scan(&charID)
+ }
+ return charID, err
+}
+
+func (s *Server) deleteCharacter(ctx context.Context, userID int, charID int) error {
+ tx, err := s.db.BeginTx(ctx, nil)
+ if err != nil {
+ return err
+ }
+ defer tx.Rollback()
+
+ _, err = tx.ExecContext(
+ ctx, `
+ DELETE FROM login_boost_state
+ WHERE char_id = $1`,
+ charID,
+ )
+ if err != nil {
+ return err
+ }
+ _, err = tx.ExecContext(
+ ctx, `
+ DELETE FROM guild_characters
+ WHERE character_id = $1`,
+ charID,
+ )
+ if err != nil {
+ return err
+ }
+ _, err = tx.ExecContext(
+ ctx, `
+ DELETE FROM characters
+ WHERE user_id = $1 AND id = $2`,
+ userID, charID,
+ )
+ if err != nil {
+ return err
+ }
+ return tx.Commit()
+}
+
+func (s *Server) getCharactersForUser(ctx context.Context, uid int) ([]Character, error) {
+ characters := make([]Character, 0)
+ err := s.db.SelectContext(
+ ctx, &characters, `
+ SELECT id, name, is_female, weapon_type, hrp, gr, last_login
+ FROM characters
+ WHERE user_id = $1 AND deleted = false AND is_new_character = false ORDER BY id ASC`,
+ uid,
+ )
+ if err != nil {
+ return nil, err
+ }
+ return characters, nil
+}
diff --git a/server/signv2server/endpoints.go b/server/signv2server/endpoints.go
new file mode 100644
index 000000000..eeb7442de
--- /dev/null
+++ b/server/signv2server/endpoints.go
@@ -0,0 +1,208 @@
+package signv2server
+
+import (
+ "database/sql"
+ "encoding/json"
+ "errors"
+ "net/http"
+ "time"
+
+ "github.com/lib/pq"
+ "go.uber.org/zap"
+ "golang.org/x/crypto/bcrypt"
+)
+
+type LauncherMessage struct {
+ Message string `json:"message"`
+ Date int64 `json:"date"`
+ Link string `json:"link"`
+}
+
+type Character struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ IsFemale bool `json:"isFemale" db:"is_female"`
+ Weapon int `json:"weapon" db:"weapon_type"`
+ HR int `json:"hr" db:"hrp"`
+ GR int `json:"gr"`
+ LastLogin int64 `json:"lastLogin" db:"last_login"`
+}
+
+func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) {
+ var respData struct {
+ Important []LauncherMessage `json:"important"`
+ Normal []LauncherMessage `json:"normal"`
+ }
+ respData.Important = []LauncherMessage{
+ {
+ Message: "Server Update 9 Released!",
+ Date: time.Date(2022, 8, 2, 0, 0, 0, 0, time.UTC).Unix(),
+ Link: "https://discord.com/channels/368424389416583169/929509970624532511/1003985850255818762",
+ },
+ {
+ Message: "Eng 2.0 & Ravi Patch Released!",
+ Date: time.Date(2022, 5, 3, 0, 0, 0, 0, time.UTC).Unix(),
+ Link: "https://discord.com/channels/368424389416583169/929509970624532511/969305400795078656",
+ },
+ {
+ Message: "Launcher Patch V1.0 Released!",
+ Date: time.Date(2022, 4, 24, 0, 0, 0, 0, time.UTC).Unix(),
+ Link: "https://discord.com/channels/368424389416583169/929509970624532511/969286397301248050",
+ },
+ }
+ respData.Normal = []LauncherMessage{
+ {
+ Message: "Join the community Discord for updates!",
+ Date: time.Date(2022, 4, 24, 0, 0, 0, 0, time.UTC).Unix(),
+ Link: "https://discord.gg/CFnzbhQ",
+ },
+ }
+ w.WriteHeader(200)
+ w.Header().Add("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(respData)
+}
+
+func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+ var reqData struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
+ s.logger.Error("JSON decode error", zap.Error(err))
+ w.WriteHeader(400)
+ w.Write([]byte("Invalid data received"))
+ return
+ }
+ var (
+ userID int
+ password string
+ )
+ err := s.db.QueryRow("SELECT id, password FROM users WHERE username = $1", reqData.Username).Scan(&userID, &password)
+ if err == sql.ErrNoRows {
+ w.WriteHeader(400)
+ w.Write([]byte("Username does not exist"))
+ return
+ } else if err != nil {
+ s.logger.Warn("SQL query error", zap.Error(err))
+ w.WriteHeader(500)
+ return
+ }
+ if bcrypt.CompareHashAndPassword([]byte(password), []byte(reqData.Password)) != nil {
+ w.WriteHeader(400)
+ w.Write([]byte("Your password is incorrect"))
+ return
+ }
+
+ var respData struct {
+ Token string `json:"token"`
+ Characters []Character `json:"characters"`
+ }
+ respData.Token, err = s.createLoginToken(ctx, userID)
+ if err != nil {
+ s.logger.Warn("Error registering login token", zap.Error(err))
+ w.WriteHeader(500)
+ return
+ }
+ respData.Characters, err = s.getCharactersForUser(ctx, userID)
+ if err != nil {
+ s.logger.Warn("Error getting characters from DB", zap.Error(err))
+ w.WriteHeader(500)
+ return
+ }
+ w.WriteHeader(200)
+ w.Header().Add("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(respData)
+}
+
+func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+ var reqData struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
+ s.logger.Error("JSON decode error", zap.Error(err))
+ w.WriteHeader(400)
+ w.Write([]byte("Invalid data received"))
+ return
+ }
+ s.logger.Info("Creating account", zap.String("username", reqData.Username))
+ userID, err := s.createNewUser(ctx, reqData.Username, reqData.Password)
+ if err != nil {
+ var pqErr *pq.Error
+ if errors.As(err, &pqErr) && pqErr.Constraint == "users_username_key" {
+ w.WriteHeader(400)
+ w.Write([]byte("User already exists"))
+ return
+ }
+ s.logger.Error("Error checking user", zap.Error(err), zap.String("username", reqData.Username))
+ w.WriteHeader(500)
+ return
+ }
+
+ var respData struct {
+ Token string `json:"token"`
+ }
+ respData.Token, err = s.createLoginToken(ctx, userID)
+ if err != nil {
+ s.logger.Error("Error registering login token", zap.Error(err))
+ w.WriteHeader(500)
+ return
+ }
+ json.NewEncoder(w).Encode(respData)
+}
+
+func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+ var reqData struct {
+ Token string `json:"token"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
+ s.logger.Error("JSON decode error", zap.Error(err))
+ w.WriteHeader(400)
+ w.Write([]byte("Invalid data received"))
+ return
+ }
+
+ var respData struct {
+ CharID int `json:"id"`
+ }
+ userID, err := s.userIDFromToken(ctx, reqData.Token)
+ if err != nil {
+ w.WriteHeader(401)
+ return
+ }
+ respData.CharID, err = s.createCharacter(ctx, userID)
+ if err != nil {
+ s.logger.Error("Failed to create character", zap.Error(err), zap.String("token", reqData.Token))
+ w.WriteHeader(500)
+ return
+ }
+ json.NewEncoder(w).Encode(respData)
+}
+
+func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+ var reqData struct {
+ Token string `json:"token"`
+ CharID int `json:"id"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
+ s.logger.Error("JSON decode error", zap.Error(err))
+ w.WriteHeader(400)
+ w.Write([]byte("Invalid data received"))
+ return
+ }
+ userID, err := s.userIDFromToken(ctx, reqData.Token)
+ if err != nil {
+ w.WriteHeader(401)
+ return
+ }
+ if err := s.deleteCharacter(ctx, userID, reqData.CharID); err != nil {
+ s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Int("charID", reqData.CharID))
+ w.WriteHeader(500)
+ return
+ }
+ json.NewEncoder(w).Encode(struct{}{})
+}
diff --git a/server/signv2server/signv2_server.go b/server/signv2server/signv2_server.go
new file mode 100644
index 000000000..c00a4a641
--- /dev/null
+++ b/server/signv2server/signv2_server.go
@@ -0,0 +1,89 @@
+package signv2server
+
+import (
+ "context"
+ "erupe-ce/config"
+ "fmt"
+ "net/http"
+ "os"
+ "sync"
+ "time"
+
+ "github.com/gorilla/handlers"
+ "github.com/gorilla/mux"
+ "github.com/jmoiron/sqlx"
+ "go.uber.org/zap"
+)
+
+type Config struct {
+ Logger *zap.Logger
+ DB *sqlx.DB
+ ErupeConfig *config.Config
+}
+
+// Server is the MHF custom launcher sign server.
+type Server struct {
+ sync.Mutex
+ logger *zap.Logger
+ erupeConfig *config.Config
+ db *sqlx.DB
+ httpServer *http.Server
+ isShuttingDown bool
+}
+
+// NewServer creates a new Server type.
+func NewServer(config *Config) *Server {
+ s := &Server{
+ logger: config.Logger,
+ erupeConfig: config.ErupeConfig,
+ db: config.DB,
+ httpServer: &http.Server{},
+ }
+ return s
+}
+
+// Start starts the server in a new goroutine.
+func (s *Server) Start() error {
+ // Set up the routes responsible for serving the launcher HTML, serverlist, unique name check, and JP auth.
+ r := mux.NewRouter()
+ r.HandleFunc("/launcher", s.Launcher)
+ r.HandleFunc("/login", s.Login)
+ r.HandleFunc("/register", s.Register)
+ r.HandleFunc("/character/create", s.CreateCharacter)
+ r.HandleFunc("/character/delete", s.DeleteCharacter)
+ handler := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}))(r)
+ s.httpServer.Handler = handlers.LoggingHandler(os.Stdout, handler)
+ s.httpServer.Addr = fmt.Sprintf(":%d", s.erupeConfig.SignV2.Port)
+
+ serveError := make(chan error, 1)
+ go func() {
+ if err := s.httpServer.ListenAndServe(); err != nil {
+ // Send error if any.
+ serveError <- err
+ }
+ }()
+
+ // Get the error from calling ListenAndServe, otherwise assume it's good after 250 milliseconds.
+ select {
+ case err := <-serveError:
+ return err
+ case <-time.After(250 * time.Millisecond):
+ return nil
+ }
+}
+
+// Shutdown exits the server gracefully.
+func (s *Server) Shutdown() {
+ s.logger.Debug("Shutting down")
+
+ s.Lock()
+ s.isShuttingDown = true
+ s.Unlock()
+
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ if err := s.httpServer.Shutdown(ctx); err != nil {
+ // Just warn because we are shutting down the server anyway.
+ s.logger.Warn("Got error on httpServer shutdown", zap.Error(err))
+ }
+}