mirror of
https://github.com/Mezeporta/Erupe.git
synced 2025-12-15 00:15:08 +01:00
Merge branch 'main' into feature/diva
# Conflicts: # server/channelserver/handlers_quest.go
This commit is contained in:
13
common/token/token.go
Normal file
13
common/token/token.go
Normal file
@@ -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)
|
||||
}
|
||||
24
config.json
24
config.json
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"Host": "127.0.0.1",
|
||||
"BinPath": "bin",
|
||||
"Language": "en",
|
||||
"DisableSoftCrash": false,
|
||||
"FeaturedWeapons": 1,
|
||||
"HideLoginNotice": true,
|
||||
"LoginNotice": "<BODY><CENTER><SIZE_3><C_4>Welcome to Erupe SU9.1!<BR><BODY><LEFT><SIZE_2><C_5>Erupe is experimental software<C_7>, we are not liable for any<BR><BODY>issues caused by installing the software!<BR><BODY><BR><BODY><C_4>■Report bugs on Discord!<C_7><BR><BODY><BR><BODY><C_4>■Test everything!<C_7><BR><BODY><BR><BODY><C_4>■Don't talk to softlocking NPCs!<C_7><BR><BODY><BR><BODY><C_4>■Fork the code on GitHub!<C_7><BR><BODY><BR><BODY>Thank you to all of the contributors,<BR><BODY><BR><BODY>this wouldn't exist without you.",
|
||||
"PatchServerManifest": "",
|
||||
"PatchServerFile": "",
|
||||
"ScreenshotAPIURL": "",
|
||||
"DevMode": true,
|
||||
"DevModeOptions": {
|
||||
"PatchServerManifest": "",
|
||||
"PatchServerFile": "",
|
||||
"AutoCreateAccount": true,
|
||||
"EnableLauncherServer": false,
|
||||
"HideLoginNotice": false,
|
||||
"LoginNotice": "<BODY><CENTER><SIZE_3><C_4>Welcome to Erupe SU9.1 Beta!<BR><BODY><LEFT><SIZE_2><C_5>Erupe is experimental software<C_7>, we are not liable for any<BR><BODY>issues caused by installing the software!<BR><BODY><BR><BODY><C_4>■Report bugs on Discord!<C_7><BR><BODY><BR><BODY><C_4>■Test everything!<C_7><BR><BODY><BR><BODY><C_4>■Don't talk to softlocking NPCs!<C_7><BR><BODY><BR><BODY><C_4>■Fork the code on GitHub!<C_7><BR><BODY><BR><BODY>Thank you to all of the contributors,<BR><BODY><BR><BODY>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
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
23
main.go
23
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()
|
||||
}
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
package mhfpacket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package mhfpacket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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.
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package mhfpacket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,7 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.rasta_id_seq;
|
||||
|
||||
UPDATE characters SET savemercenary=NULL;
|
||||
|
||||
END;
|
||||
@@ -1,6 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE IF EXISTS public.mail
|
||||
ADD COLUMN IF NOT EXISTS locked boolean NOT NULL DEFAULT false;
|
||||
|
||||
END;
|
||||
@@ -1,6 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters
|
||||
ADD COLUMN cafe_reset timestamp without time zone;
|
||||
|
||||
END;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
|
||||
|
||||
41
server/channelserver/handlers_bbs.go
Normal file
41
server/channelserver/handlers_bbs.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/common/token"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgMhfGetBbsUserStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBbsUserStatus)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(200)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBbsSnsStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBbsSnsStatus)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(200)
|
||||
bf.WriteUint32(401)
|
||||
bf.WriteUint32(401)
|
||||
bf.WriteUint32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfApplyBbsArticle(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfApplyBbsArticle)
|
||||
bf := byteframe.NewByteFrame()
|
||||
articleToken := token.Generate(40)
|
||||
bf.WriteUint32(200)
|
||||
bf.WriteUint32(80)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteBytes(stringsupport.PaddedString(articleToken, 64, false))
|
||||
bf.WriteBytes(stringsupport.PaddedString(s.server.erupeConfig.ScreenshotAPIURL, 64, false))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -52,6 +52,9 @@ type Server struct {
|
||||
stagesLock sync.RWMutex
|
||||
stages map[string]*Stage
|
||||
|
||||
// Used to map different languages
|
||||
dict map[string]string
|
||||
|
||||
// UserBinary
|
||||
userBinaryPartsLock sync.RWMutex
|
||||
userBinaryParts map[userBinaryPartID][]byte
|
||||
@@ -164,6 +167,8 @@ func NewServer(config *Config) *Server {
|
||||
// MezFes
|
||||
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
|
||||
|
||||
s.dict = getLangStrings(s)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -279,6 +284,7 @@ func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session,
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(pkt.Opcode()))
|
||||
pkt.Build(bf, session.clientContext)
|
||||
bf.WriteUint16(0x0010)
|
||||
session.QueueSendNonBlocking(bf.Data())
|
||||
}
|
||||
}
|
||||
@@ -313,15 +319,17 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
|
||||
var text string
|
||||
switch _type {
|
||||
case 2:
|
||||
text = "<Great Slaying: Berserk> is being held!"
|
||||
text = s.dict["ravienteBerserk"]
|
||||
case 3:
|
||||
text = s.dict["ravienteExtreme"]
|
||||
case 4:
|
||||
text = "<Great Slaying: Extreme> is being held!"
|
||||
text = s.dict["ravienteExtremeLimited"]
|
||||
case 5:
|
||||
text = "<Great Slaying: Berserk Practice> is being held!"
|
||||
text = s.dict["ravienteBerserkSmall"]
|
||||
default:
|
||||
s.logger.Error("Unk raviente type", zap.Uint8("_type", _type))
|
||||
}
|
||||
ps.Uint16(bf, text, false)
|
||||
ps.Uint16(bf, text, true)
|
||||
bf.WriteBytes([]byte{0x5F, 0x53, 0x00})
|
||||
bf.WriteUint32(ip) // IP address
|
||||
bf.WriteUint16(port) // Port
|
||||
|
||||
52
server/channelserver/sys_language.go
Normal file
52
server/channelserver/sys_language.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package channelserver
|
||||
|
||||
func getLangStrings(s *Server) map[string]string {
|
||||
strings := make(map[string]string)
|
||||
switch s.erupeConfig.Language {
|
||||
case "jp":
|
||||
strings["language"] = "日本語"
|
||||
strings["cafeReset"] = "%d/%dにリセット"
|
||||
strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!"
|
||||
strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!"
|
||||
strings["ravienteExtremeLimited"] = "<大討伐:猛狂期【極】(制限付)>が開催されました!"
|
||||
strings["ravienteBerserkSmall"] = "<大討伐:猛狂期(小数)>が開催されました!"
|
||||
|
||||
strings["guildInviteName"] = "猟団勧誘のご案内"
|
||||
strings["guildInvite"] = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。"
|
||||
|
||||
strings["guildInviteSuccessName"] = "成功"
|
||||
strings["guildInviteSuccess"] = "あなたは「%s」に参加できました。"
|
||||
|
||||
strings["guildInviteAcceptedName"] = "承諾されました"
|
||||
strings["guildInviteAccepted"] = "招待した狩人が「%s」への招待を承諾しました。"
|
||||
|
||||
strings["guildInviteRejectName"] = "却下しました"
|
||||
strings["guildInviteReject"] = "あなたは「%s」への参加を却下しました。"
|
||||
|
||||
strings["guildInviteDeclinedName"] = "辞退しました"
|
||||
strings["guildInviteDeclined"] = "招待した狩人が「%s」への招待を辞退しました。"
|
||||
default:
|
||||
strings["language"] = "English"
|
||||
strings["cafeReset"] = "Resets on %d/%d"
|
||||
strings["ravienteBerserk"] = "<Great Slaying: Berserk> is being held!"
|
||||
strings["ravienteExtreme"] = "<Great Slaying: Extreme> is being held!"
|
||||
strings["ravienteExtremeLimited"] = "<Great Slaying: Extreme (Limited)> is being held!"
|
||||
strings["ravienteBerserkSmall"] = "<Great Slaying: Berserk (Small)> is being held!"
|
||||
|
||||
strings["guildInviteName"] = "Invitation!"
|
||||
strings["guildInvite"] = "You have been invited to join\n「%s」\nDo you want to accept?"
|
||||
|
||||
strings["guildInviteSuccessName"] = "Success!"
|
||||
strings["guildInviteSuccess"] = "You have successfully joined\n「%s」."
|
||||
|
||||
strings["guildInviteAcceptedName"] = "Accepted"
|
||||
strings["guildInviteAccepted"] = "The recipient accepted your invitation to join\n「%s」."
|
||||
|
||||
strings["guildInviteRejectName"] = "Rejected"
|
||||
strings["guildInviteReject"] = "You rejected the invitation to join\n「%s」."
|
||||
|
||||
strings["guildInviteDeclinedName"] = "Declined"
|
||||
strings["guildInviteDeclined"] = "The recipient declined your invitation to join\n「%s」."
|
||||
}
|
||||
return strings
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
122
server/signv2server/dbutils.go
Normal file
122
server/signv2server/dbutils.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package signv2server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"erupe-ce/common/token"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func (s *Server) createNewUser(ctx context.Context, username string, password string) (int, error) {
|
||||
// Create salted hash of user password
|
||||
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var id int
|
||||
err = s.db.QueryRowContext(
|
||||
ctx, `
|
||||
INSERT INTO users (username, password, return_expires)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id
|
||||
`,
|
||||
username, string(passwordHash), time.Now().Add(time.Hour*24*30),
|
||||
).Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *Server) createLoginToken(ctx context.Context, uid int) (string, error) {
|
||||
loginToken := token.Generate(16)
|
||||
_, err := s.db.ExecContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2)", uid, loginToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return loginToken, nil
|
||||
}
|
||||
|
||||
func (s *Server) userIDFromToken(ctx context.Context, token string) (int, error) {
|
||||
var userID int
|
||||
err := s.db.QueryRowContext(ctx, "SELECT user_id FROM sign_sessions WHERE token = $1", token).Scan(&userID)
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, fmt.Errorf("invalid login token")
|
||||
} else if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return userID, nil
|
||||
}
|
||||
|
||||
func (s *Server) createCharacter(ctx context.Context, userID int) (int, error) {
|
||||
var charID int
|
||||
err := s.db.QueryRowContext(ctx,
|
||||
"SELECT id FROM characters WHERE is_new_character = true AND user_id = $1",
|
||||
userID,
|
||||
).Scan(&charID)
|
||||
if err == sql.ErrNoRows {
|
||||
err = s.db.QueryRowContext(ctx, `
|
||||
INSERT INTO characters (
|
||||
user_id, is_female, is_new_character, name, unk_desc_string,
|
||||
hrp, gr, weapon_type, last_login
|
||||
)
|
||||
VALUES ($1, false, true, '', '', 0, 0, 0, $2)
|
||||
RETURNING id`,
|
||||
userID, uint32(time.Now().Unix()),
|
||||
).Scan(&charID)
|
||||
}
|
||||
return charID, err
|
||||
}
|
||||
|
||||
func (s *Server) deleteCharacter(ctx context.Context, userID int, charID int) error {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
_, err = tx.ExecContext(
|
||||
ctx, `
|
||||
DELETE FROM login_boost_state
|
||||
WHERE char_id = $1`,
|
||||
charID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.ExecContext(
|
||||
ctx, `
|
||||
DELETE FROM guild_characters
|
||||
WHERE character_id = $1`,
|
||||
charID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.ExecContext(
|
||||
ctx, `
|
||||
DELETE FROM characters
|
||||
WHERE user_id = $1 AND id = $2`,
|
||||
userID, charID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (s *Server) getCharactersForUser(ctx context.Context, uid int) ([]Character, error) {
|
||||
characters := make([]Character, 0)
|
||||
err := s.db.SelectContext(
|
||||
ctx, &characters, `
|
||||
SELECT id, name, is_female, weapon_type, hrp, gr, last_login
|
||||
FROM characters
|
||||
WHERE user_id = $1 AND deleted = false AND is_new_character = false ORDER BY id ASC`,
|
||||
uid,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return characters, nil
|
||||
}
|
||||
208
server/signv2server/endpoints.go
Normal file
208
server/signv2server/endpoints.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package signv2server
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type LauncherMessage struct {
|
||||
Message string `json:"message"`
|
||||
Date int64 `json:"date"`
|
||||
Link string `json:"link"`
|
||||
}
|
||||
|
||||
type Character struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
IsFemale bool `json:"isFemale" db:"is_female"`
|
||||
Weapon int `json:"weapon" db:"weapon_type"`
|
||||
HR int `json:"hr" db:"hrp"`
|
||||
GR int `json:"gr"`
|
||||
LastLogin int64 `json:"lastLogin" db:"last_login"`
|
||||
}
|
||||
|
||||
func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) {
|
||||
var respData struct {
|
||||
Important []LauncherMessage `json:"important"`
|
||||
Normal []LauncherMessage `json:"normal"`
|
||||
}
|
||||
respData.Important = []LauncherMessage{
|
||||
{
|
||||
Message: "Server Update 9 Released!",
|
||||
Date: time.Date(2022, 8, 2, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
Link: "https://discord.com/channels/368424389416583169/929509970624532511/1003985850255818762",
|
||||
},
|
||||
{
|
||||
Message: "Eng 2.0 & Ravi Patch Released!",
|
||||
Date: time.Date(2022, 5, 3, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
Link: "https://discord.com/channels/368424389416583169/929509970624532511/969305400795078656",
|
||||
},
|
||||
{
|
||||
Message: "Launcher Patch V1.0 Released!",
|
||||
Date: time.Date(2022, 4, 24, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
Link: "https://discord.com/channels/368424389416583169/929509970624532511/969286397301248050",
|
||||
},
|
||||
}
|
||||
respData.Normal = []LauncherMessage{
|
||||
{
|
||||
Message: "Join the community Discord for updates!",
|
||||
Date: time.Date(2022, 4, 24, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
Link: "https://discord.gg/CFnzbhQ",
|
||||
},
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(respData)
|
||||
}
|
||||
|
||||
func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var reqData struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
|
||||
s.logger.Error("JSON decode error", zap.Error(err))
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Invalid data received"))
|
||||
return
|
||||
}
|
||||
var (
|
||||
userID int
|
||||
password string
|
||||
)
|
||||
err := s.db.QueryRow("SELECT id, password FROM users WHERE username = $1", reqData.Username).Scan(&userID, &password)
|
||||
if err == sql.ErrNoRows {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Username does not exist"))
|
||||
return
|
||||
} else if err != nil {
|
||||
s.logger.Warn("SQL query error", zap.Error(err))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(password), []byte(reqData.Password)) != nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Your password is incorrect"))
|
||||
return
|
||||
}
|
||||
|
||||
var respData struct {
|
||||
Token string `json:"token"`
|
||||
Characters []Character `json:"characters"`
|
||||
}
|
||||
respData.Token, err = s.createLoginToken(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Warn("Error registering login token", zap.Error(err))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
respData.Characters, err = s.getCharactersForUser(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Warn("Error getting characters from DB", zap.Error(err))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(respData)
|
||||
}
|
||||
|
||||
func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var reqData struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
|
||||
s.logger.Error("JSON decode error", zap.Error(err))
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Invalid data received"))
|
||||
return
|
||||
}
|
||||
s.logger.Info("Creating account", zap.String("username", reqData.Username))
|
||||
userID, err := s.createNewUser(ctx, reqData.Username, reqData.Password)
|
||||
if err != nil {
|
||||
var pqErr *pq.Error
|
||||
if errors.As(err, &pqErr) && pqErr.Constraint == "users_username_key" {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("User already exists"))
|
||||
return
|
||||
}
|
||||
s.logger.Error("Error checking user", zap.Error(err), zap.String("username", reqData.Username))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
var respData struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
respData.Token, err = s.createLoginToken(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("Error registering login token", zap.Error(err))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(respData)
|
||||
}
|
||||
|
||||
func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var reqData struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
|
||||
s.logger.Error("JSON decode error", zap.Error(err))
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Invalid data received"))
|
||||
return
|
||||
}
|
||||
|
||||
var respData struct {
|
||||
CharID int `json:"id"`
|
||||
}
|
||||
userID, err := s.userIDFromToken(ctx, reqData.Token)
|
||||
if err != nil {
|
||||
w.WriteHeader(401)
|
||||
return
|
||||
}
|
||||
respData.CharID, err = s.createCharacter(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to create character", zap.Error(err), zap.String("token", reqData.Token))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(respData)
|
||||
}
|
||||
|
||||
func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var reqData struct {
|
||||
Token string `json:"token"`
|
||||
CharID int `json:"id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
|
||||
s.logger.Error("JSON decode error", zap.Error(err))
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Invalid data received"))
|
||||
return
|
||||
}
|
||||
userID, err := s.userIDFromToken(ctx, reqData.Token)
|
||||
if err != nil {
|
||||
w.WriteHeader(401)
|
||||
return
|
||||
}
|
||||
if err := s.deleteCharacter(ctx, userID, reqData.CharID); err != nil {
|
||||
s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Int("charID", reqData.CharID))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(struct{}{})
|
||||
}
|
||||
89
server/signv2server/signv2_server.go
Normal file
89
server/signv2server/signv2_server.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package signv2server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"erupe-ce/config"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Logger *zap.Logger
|
||||
DB *sqlx.DB
|
||||
ErupeConfig *config.Config
|
||||
}
|
||||
|
||||
// Server is the MHF custom launcher sign server.
|
||||
type Server struct {
|
||||
sync.Mutex
|
||||
logger *zap.Logger
|
||||
erupeConfig *config.Config
|
||||
db *sqlx.DB
|
||||
httpServer *http.Server
|
||||
isShuttingDown bool
|
||||
}
|
||||
|
||||
// NewServer creates a new Server type.
|
||||
func NewServer(config *Config) *Server {
|
||||
s := &Server{
|
||||
logger: config.Logger,
|
||||
erupeConfig: config.ErupeConfig,
|
||||
db: config.DB,
|
||||
httpServer: &http.Server{},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Start starts the server in a new goroutine.
|
||||
func (s *Server) Start() error {
|
||||
// Set up the routes responsible for serving the launcher HTML, serverlist, unique name check, and JP auth.
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/launcher", s.Launcher)
|
||||
r.HandleFunc("/login", s.Login)
|
||||
r.HandleFunc("/register", s.Register)
|
||||
r.HandleFunc("/character/create", s.CreateCharacter)
|
||||
r.HandleFunc("/character/delete", s.DeleteCharacter)
|
||||
handler := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}))(r)
|
||||
s.httpServer.Handler = handlers.LoggingHandler(os.Stdout, handler)
|
||||
s.httpServer.Addr = fmt.Sprintf(":%d", s.erupeConfig.SignV2.Port)
|
||||
|
||||
serveError := make(chan error, 1)
|
||||
go func() {
|
||||
if err := s.httpServer.ListenAndServe(); err != nil {
|
||||
// Send error if any.
|
||||
serveError <- err
|
||||
}
|
||||
}()
|
||||
|
||||
// Get the error from calling ListenAndServe, otherwise assume it's good after 250 milliseconds.
|
||||
select {
|
||||
case err := <-serveError:
|
||||
return err
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown exits the server gracefully.
|
||||
func (s *Server) Shutdown() {
|
||||
s.logger.Debug("Shutting down")
|
||||
|
||||
s.Lock()
|
||||
s.isShuttingDown = true
|
||||
s.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := s.httpServer.Shutdown(ctx); err != nil {
|
||||
// Just warn because we are shutting down the server anyway.
|
||||
s.logger.Warn("Got error on httpServer shutdown", zap.Error(err))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user