16 Commits

Author SHA1 Message Date
wish
e40ac7539c update login notice 2022-08-04 10:53:38 +10:00
wish
ed11b5ced9 implement token verification 2022-08-04 10:51:31 +10:00
wish
bf851b5c67 add missing mail query 2022-08-04 10:34:38 +10:00
wish
2570dda066 revert road shop changes 2022-08-04 10:34:22 +10:00
wish
05db1922d5 launcher improvements 2022-08-04 10:10:03 +10:00
wish
dcd6b35478 implement guild recruiters 2022-08-04 09:06:21 +10:00
wish
b2ebb8f1d9 recruiting guilds, stub EnumerateInvGuild 2022-08-04 07:56:31 +10:00
wish
38747d389c guild recruitment closing 2022-08-04 07:31:28 +10:00
wish
260d8d0dd8 guild pagination and cleanup 2022-08-04 07:08:10 +10:00
wish
ba927f877d fix guild enumeration and applications 2022-08-04 06:43:41 +10:00
wish
872a0b3785 treasure expiration and prevent overflow 2022-08-04 03:04:42 +10:00
wish
3bdc206ff7 cast binary fixes 2022-08-04 02:46:04 +10:00
wish
6424a5c639 support SJIS mail (#15) 2022-08-04 01:15:04 +10:00
wish
af2cd5cd7c Update LICENSE
Please read the following to understand why the license has been updated.

The entity known as 'The Erupe Developers' (currently only known as Fist, Andoryuuta and possibly some others), previously published a source of Erupe that was available under MIT License. This fork was used by the 'Einherjar Team' to modify and redistribute Erupe which is fully available under MIT.

However, when the 'Einherjar Team' updated the license, they stated 'The Erupe Developers from Einherjar Team'. However none of the persons under the entity 'The Erupe Developers' are part of the 'Einherjar Team'. This ultimately forfeits the licensing of any work that has been published by the 'Einherjar Team'.

The 'Einherjar Team' name will be left in the license as it previously was, this is out of respect for them, their work and the rights of the license itself, however no work exists in this repository that is able to be licensed by the 'Einherjar Team'.

'The Erupe Developers' entity has been separated, as it was in the original license and ZeruLight has also been appended to the list of contributors. Should future contributors like to have their work licensed within this fork of Erupe, they are welcome to append they or the the entity they represent to the license.
2022-08-03 22:27:14 +10:00
Evotushon
95104571a6 Rewrite README.md (#14) 2022-08-03 22:11:02 +10:00
wish
daa788815f post-SU9 cleanup 2022-08-03 17:23:11 +10:00
36 changed files with 360 additions and 565 deletions

View File

@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2019 The Erupe Developers from Einherjar Team Copyright (c) 2019 The Erupe Developers, The Erupe Developers from Einherjar Team, ZeruLight
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,8 @@
# Erupe Community Edition # Erupe Community Edition
This is a community upload of a community project. The amount of people who worked on it is innumerous, and hard to keep track of. Credits to Andoryuuta, Fist's Team, the French Team, Mai's Team and many others. No matter the relations, these files will remain public and open source, free for all to use and modify. This is a community upload of a community project. The amount of people who worked on it is innumerous, and hard to keep track of. Credits to Andoryuuta, Fist's Team, the French Team, Mai's Team and many others. No matter the relations, these files will remain public and open source, free for all to use and modify.
A pastebin with various links, tips, and FAQ: https://pastebin.com/QqAwZSTC [A pastebin with various links, tips, and FAQ](https://pastebin.com/QqAwZSTC)
An upload for the quest and scenario files exists here: https://github.com/xl3lackout/MHFZ-Quest-Files [An upload for the quest and scenario files exists here](https://github.com/xl3lackout/MHFZ-Quest-Files)
(Over 300k+ files) (Over 300k+ files)

View File

@@ -5,7 +5,7 @@
"devmodeoptions": { "devmodeoptions": {
"serverName" : "", "serverName" : "",
"hideLoginNotice": false, "hideLoginNotice": false,
"loginNotice": "<BODY><CENTER><SIZE_3><C_4>Welcome to Erupe SU9!<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.", "loginNotice": "<BODY><CENTER><SIZE_3><C_4>Welcome to Erupe SU9 (Patch 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.",
"cleandb": false, "cleandb": false,
"maxlauncherhr": false, "maxlauncherhr": false,
"LogInboundMessages": false, "LogInboundMessages": false,
@@ -16,6 +16,8 @@
"FestaEvent": 0, "FestaEvent": 0,
"TournamentEvent": 0, "TournamentEvent": 0,
"MezFesEvent": true, "MezFesEvent": true,
"DisableMailItems": true,
"DisableTokenCheck": false,
"SaveDumps": { "SaveDumps": {
"Enabled": true, "Enabled": true,
"OutputDir": "savedata" "OutputDir": "savedata"

View File

@@ -36,6 +36,8 @@ type DevModeOptions struct {
FestaEvent int // Hunter's Festa event status FestaEvent int // Hunter's Festa event status
TournamentEvent int // VS Tournament event status TournamentEvent int // VS Tournament event status
MezFesEvent bool // MezFes status MezFesEvent bool // MezFes status
DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!)
DisableMailItems bool // Hack to prevent english versions of MHF from crashing
SaveDumps SaveDumpOptions SaveDumps SaveDumpOptions
} }

View File

@@ -2,6 +2,7 @@ package mhfpacket
import ( import (
"errors" "errors"
"io"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network" "erupe-ce/network"
@@ -30,6 +31,7 @@ const (
type MsgMhfEnumerateGuild struct { type MsgMhfEnumerateGuild struct {
AckHandle uint32 AckHandle uint32
Type EnumerateGuildType Type EnumerateGuildType
Page uint8
RawDataPayload []byte RawDataPayload []byte
} }
@@ -42,8 +44,9 @@ func (m *MsgMhfEnumerateGuild) Opcode() network.PacketID {
func (m *MsgMhfEnumerateGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfEnumerateGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.Type = EnumerateGuildType(bf.ReadUint8()) m.Type = EnumerateGuildType(bf.ReadUint8())
m.Page = bf.ReadUint8()
m.RawDataPayload = bf.DataFromCurrent() m.RawDataPayload = bf.DataFromCurrent()
bf.Seek(int64(len(bf.Data())-2), 0) bf.Seek(-2, io.SeekEnd)
return nil return nil
} }

View File

@@ -3,13 +3,16 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfEnumerateInvGuild represents the MSG_MHF_ENUMERATE_INV_GUILD // MsgMhfEnumerateInvGuild represents the MSG_MHF_ENUMERATE_INV_GUILD
type MsgMhfEnumerateInvGuild struct{} type MsgMhfEnumerateInvGuild struct {
AckHandle uint32
Unk []byte
}
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfEnumerateInvGuild) Opcode() network.PacketID { func (m *MsgMhfEnumerateInvGuild) Opcode() network.PacketID {
@@ -18,7 +21,9 @@ func (m *MsgMhfEnumerateInvGuild) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfEnumerateInvGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfEnumerateInvGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED") m.AckHandle = bf.ReadUint32()
m.Unk = bf.ReadBytes(9)
return nil
} }
// Build builds a binary packet from the current data. // Build builds a binary packet from the current data.

View File

@@ -3,9 +3,9 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
type OperateGuildMemberAction uint8 type OperateGuildMemberAction uint8
@@ -23,6 +23,7 @@ type MsgMhfOperateGuildMember struct {
GuildID uint32 GuildID uint32
CharID uint32 CharID uint32
Action uint8 Action uint8
Unk []byte
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -36,7 +37,7 @@ func (m *MsgMhfOperateGuildMember) Parse(bf *byteframe.ByteFrame, ctx *clientctx
m.GuildID = bf.ReadUint32() m.GuildID = bf.ReadUint32()
m.CharID = bf.ReadUint32() m.CharID = bf.ReadUint32()
m.Action = bf.ReadUint8() m.Action = bf.ReadUint8()
m.Unk = bf.ReadBytes(3)
return nil return nil
} }

View File

@@ -3,9 +3,9 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
type OperateMailOperation uint8 type OperateMailOperation uint8
@@ -41,8 +41,7 @@ func (m *MsgMhfOprtMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCon
m.Index = bf.ReadUint8() m.Index = bf.ReadUint8()
m.Operation = OperateMailOperation(bf.ReadUint8()) m.Operation = OperateMailOperation(bf.ReadUint8())
m.Unk0 = bf.ReadUint8() m.Unk0 = bf.ReadUint8()
switch m.Operation { if m.Operation == OPERATE_MAIL_ACQUIRE_ITEM {
case OPERATE_MAIL_ACQUIRE_ITEM:
m.Amount = bf.ReadUint16() m.Amount = bf.ReadUint16()
m.ItemID = bf.ReadUint16() m.ItemID = bf.ReadUint16()
} }

View File

@@ -2,10 +2,11 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/common/stringsupport"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfSendMail represents the MSG_MHF_SEND_MAIL // MsgMhfSendMail represents the MSG_MHF_SEND_MAIL
@@ -16,8 +17,8 @@ type MsgMhfSendMail struct {
BodyLength uint16 BodyLength uint16
Quantity uint32 Quantity uint32
ItemID uint16 ItemID uint16
Subject []byte Subject string
Body []byte Body string
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -33,8 +34,8 @@ func (m *MsgMhfSendMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCon
m.BodyLength = bf.ReadUint16() m.BodyLength = bf.ReadUint16()
m.Quantity = bf.ReadUint32() m.Quantity = bf.ReadUint32()
m.ItemID = bf.ReadUint16() m.ItemID = bf.ReadUint16()
m.Subject = bf.ReadNullTerminatedBytes() m.Subject = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
m.Body = bf.ReadNullTerminatedBytes() m.Body = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
return nil return nil
} }

View File

@@ -3,13 +3,18 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfSetGuildManageRight represents the MSG_MHF_SET_GUILD_MANAGE_RIGHT // MsgMhfSetGuildManageRight represents the MSG_MHF_SET_GUILD_MANAGE_RIGHT
type MsgMhfSetGuildManageRight struct{} type MsgMhfSetGuildManageRight struct {
AckHandle uint32
CharID uint32
Allowed bool
Unk []byte
}
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfSetGuildManageRight) Opcode() network.PacketID { func (m *MsgMhfSetGuildManageRight) Opcode() network.PacketID {
@@ -18,7 +23,11 @@ func (m *MsgMhfSetGuildManageRight) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfSetGuildManageRight) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfSetGuildManageRight) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED") m.AckHandle = bf.ReadUint32()
m.CharID = bf.ReadUint32()
m.Allowed = bf.ReadBool()
m.Unk = bf.ReadBytes(3)
return nil
} }
// Build builds a binary packet from the current data. // Build builds a binary packet from the current data.

0
patch-schema/.gitkeep Normal file
View File

View File

@@ -1,11 +0,0 @@
BEGIN;
ALTER TABLE IF EXISTS public.users
ALTER rights SET DEFAULT 14;
ALTER TABLE IF EXISTS public.users
ALTER rights SET NOT NULL;
UPDATE public.users SET rights=14 WHERE rights IS NULL;
END;

View File

@@ -1,5 +0,0 @@
BEGIN;
CREATE SEQUENCE IF NOT EXISTS public.airou_id_seq;
END;

View File

@@ -1,10 +0,0 @@
BEGIN;
CREATE TABLE user_binaries
(
id int PRIMARY KEY,
type2 bytea,
type3 bytea
);
END;

View File

@@ -1,26 +0,0 @@
BEGIN;
CREATE TABLE IF NOT EXISTS public.distribution
(
id serial NOT NULL PRIMARY KEY,
character_id int,
type int NOT NULL,
deadline timestamp without time zone,
event_name text NOT NULL DEFAULT 'GM Gift!',
description text NOT NULL DEFAULT '~C05You received a gift!',
times_acceptable int NOT NULL DEFAULT 1,
min_hr int NOT NULL DEFAULT 65535,
max_hr int NOT NULL DEFAULT 65535,
min_sr int NOT NULL DEFAULT 65535,
max_sr int NOT NULL DEFAULT 65535,
min_gr int NOT NULL DEFAULT 65535,
max_gr int NOT NULL DEFAULT 65535,
data bytea NOT NULL
);
CREATE TABLE IF NOT EXISTS public.distributions_accepted
(
distribution_id int,
character_id int
);
END;

View File

@@ -1,26 +0,0 @@
BEGIN;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook0status;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook1status;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook2status;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook3status;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook4status;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook5status;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook5;
UPDATE public.gook SET gook0=NULL, gook1=NULL, gook2=NULL, gook3=NULL, gook4=NULL;
END;

View File

@@ -1,56 +0,0 @@
BEGIN;
ALTER TABLE IF EXISTS public.guilds
ADD COLUMN IF NOT EXISTS pugi_name_1 varchar(12) DEFAULT '';
ALTER TABLE IF EXISTS public.guilds
ADD COLUMN IF NOT EXISTS pugi_name_2 varchar(12) DEFAULT '';
ALTER TABLE IF EXISTS public.guilds
ADD COLUMN IF NOT EXISTS pugi_name_3 varchar(12) DEFAULT '';
CREATE TABLE IF NOT EXISTS public.guild_alliances
(
id serial NOT NULL PRIMARY KEY,
name varchar(24) NOT NULL,
created_at timestamp without time zone NOT NULL DEFAULT now(),
parent_id int NOT NULL,
sub1_id int,
sub2_id int
);
CREATE TABLE IF NOT EXISTS public.guild_adventures
(
id serial NOT NULL PRIMARY KEY,
guild_id int NOT NULL,
destination int NOT NULL,
charge int NOT NULL DEFAULT 0,
depart int NOT NULL,
return int NOT NULL,
collected_by text NOT NULL DEFAULT ''
);
CREATE TABLE IF NOT EXISTS public.guild_meals
(
id serial NOT NULL PRIMARY KEY,
guild_id int NOT NULL,
meal_id int NOT NULL,
level int NOT NULL,
expires int NOT NULL
);
CREATE TABLE IF NOT EXISTS public.guild_hunts
(
id serial NOT NULL PRIMARY KEY,
guild_id int NOT NULL,
host_id int NOT NULL,
destination int NOT NULL,
level int NOT NULL,
return int NOT NULL,
acquired bool NOT NULL DEFAULT false,
claimed bool NOT NULL DEFAULT false,
hunters text NOT NULL DEFAULT '',
treasure text NOT NULL DEFAULT '',
hunt_data bytea NOT NULL,
cats_used text NOT NULL
);
END;

View File

@@ -0,0 +1,9 @@
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;

View File

@@ -1,6 +0,0 @@
BEGIN;
ALTER TABLE IF EXISTS public.characters
ADD COLUMN IF NOT EXISTS house bytea;
END;

View File

@@ -0,0 +1,6 @@
BEGIN;
ALTER TABLE IF EXISTS public.mail
ADD COLUMN IF NOT EXISTS locked boolean NOT NULL DEFAULT false;
END;

View File

@@ -0,0 +1,9 @@
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;

View File

@@ -1,35 +0,0 @@
BEGIN;
CREATE TABLE IF NOT EXISTS public.normal_shop_items
(
shoptype integer,
shopid integer,
itemhash integer not null,
itemid integer,
points integer,
tradequantity integer,
rankreqlow integer,
rankreqhigh integer,
rankreqg integer,
storelevelreq integer,
maximumquantity integer,
boughtquantity integer,
roadfloorsrequired integer,
weeklyfataliskills integer,
enable_weeks character varying(8)
);
ALTER TABLE IF EXISTS public.normal_shop_items
ADD COLUMN IF NOT EXISTS enable_weeks character varying(8);
CREATE TABLE IF NOT EXISTS public.shop_item_state
(
char_id bigint REFERENCES characters (id),
itemhash int UNIQUE NOT NULL,
usedquantity int,
week int
);
ALTER TABLE IF EXISTS public.shop_item_state
ADD COLUMN IF NOT EXISTS week int;
END;

View File

@@ -1,9 +0,0 @@
BEGIN;
ALTER TABLE IF EXISTS public.characters
ADD COLUMN IF NOT EXISTS scenariodata bytea;
ALTER TABLE IF EXISTS public.characters
ADD COLUMN IF NOT EXISTS savefavoritequest bytea;
END;

View File

@@ -1,32 +0,0 @@
BEGIN;
DROP TABLE IF EXISTS public.sign_sessions;
CREATE TABLE IF NOT EXISTS public.sign_sessions
(
user_id int NOT NULL,
char_id int,
token varchar(16) NOT NULL,
server_id integer
);
DROP TABLE IF EXISTS public.servers;
CREATE TABLE IF NOT EXISTS public.servers
(
server_id int NOT NULL,
season int NOT NULL,
current_players int NOT NULL
);
ALTER TABLE IF EXISTS public.characters
ADD COLUMN IF NOT EXISTS deleted boolean NOT NULL DEFAULT false;
ALTER TABLE IF EXISTS public.characters
ADD COLUMN IF NOT EXISTS friends text NOT NULL DEFAULT '';
ALTER TABLE IF EXISTS public.characters
ADD COLUMN IF NOT EXISTS blocked text NOT NULL DEFAULT '';
ALTER TABLE IF EXISTS public.users
ADD COLUMN IF NOT EXISTS last_character int DEFAULT 0;
END;

View File

@@ -135,19 +135,18 @@ func handleMsgSysTerminalLog(s *Session, p mhfpacket.MHFPacket) {
func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysLogin) pkt := p.(*mhfpacket.MsgSysLogin)
rights := uint32(0x0E) if s.server.erupeConfig.DevMode && !s.server.erupeConfig.DevModeOptions.DisableTokenCheck {
// 0e with normal sub 4e when having premium var token string
// 01 = Character can take quests at allows err := s.server.db.QueryRow("SELECT token FROM sign_sessions WHERE token=$1", pkt.LoginTokenString).Scan(&token)
// 02 = Hunter Life, normal quests core sub
// 03 = Extra Course, extra quests, town boxes, QOL course, core sub
// 06 = Premium Course, standard 'premium' which makes ranking etc. faster
// 06 0A 0B = Boost Course, just actually 3 subs combined
// 08 09 1E = N Course, gives you the benefits of being in a netcafe (extra quests, N Points, daily freebies etc.) minimal and pointless
// 0C = N Boost course, ultra luxury course that ruins the game if in use
err := s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", pkt.CharID0).Scan(&rights)
if err != nil { if err != nil {
panic(err) s.rawConn.Close()
s.logger.Warn(fmt.Sprintf("Invalid login token, offending CID: (%d)", pkt.CharID0))
return
} }
}
rights := uint32(0x0E)
s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", pkt.CharID0).Scan(&rights)
s.Lock() s.Lock()
s.charID = pkt.CharID0 s.charID = pkt.CharID0
@@ -157,7 +156,7 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // Unix timestamp bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // Unix timestamp
_, err = s.server.db.Exec("UPDATE servers SET current_players=$1 WHERE server_id=$2", len(s.server.sessions), s.server.ID) _, err := s.server.db.Exec("UPDATE servers SET current_players=$1 WHERE server_id=$2", len(s.server.sessions), s.server.ID)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -118,7 +118,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
// Send to the proper recipients. // Send to the proper recipients.
switch pkt.BroadcastType { switch pkt.BroadcastType {
case BroadcastTypeWorld: case BroadcastTypeWorld:
s.server.WorldcastMHF(resp, s) s.server.WorldcastMHF(resp, s, nil)
case BroadcastTypeStage: case BroadcastTypeStage:
if isDiceCommand { if isDiceCommand {
s.stage.BroadcastMHF(resp, nil) // send dice result back to caller s.stage.BroadcastMHF(resp, nil) // send dice result back to caller
@@ -129,8 +129,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
if pkt.MessageType == 1 { if pkt.MessageType == 1 {
raviSema := getRaviSemaphore(s) raviSema := getRaviSemaphore(s)
if raviSema != "" { if raviSema != "" {
sema := s.server.semaphore[raviSema] s.server.BroadcastMHF(resp, s)
(*sema).BroadcastMHF(resp, s)
} }
} else { } else {
s.server.BroadcastMHF(resp, s) s.server.BroadcastMHF(resp, s)

View File

@@ -12,7 +12,6 @@ import (
"strings" "strings"
"time" "time"
"erupe-ce/common/bfutil"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring" ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport" "erupe-ce/common/stringsupport"
@@ -55,6 +54,7 @@ type Guild struct {
PugiName1 string `db:"pugi_name_1"` PugiName1 string `db:"pugi_name_1"`
PugiName2 string `db:"pugi_name_2"` PugiName2 string `db:"pugi_name_2"`
PugiName3 string `db:"pugi_name_3"` PugiName3 string `db:"pugi_name_3"`
Recruiting bool `db:"recruiting"`
FestivalColour FestivalColour `db:"festival_colour"` FestivalColour FestivalColour `db:"festival_colour"`
Rank uint16 `db:"rank"` Rank uint16 `db:"rank"`
AllianceID uint32 `db:"alliance_id"` AllianceID uint32 `db:"alliance_id"`
@@ -124,6 +124,7 @@ SELECT
pugi_name_1, pugi_name_1,
pugi_name_2, pugi_name_2,
pugi_name_3, pugi_name_3,
recruiting,
festival_colour, festival_colour,
CASE CASE
WHEN rank_rp <= 48 THEN rank_rp/24 WHEN rank_rp <= 48 THEN rank_rp/24
@@ -512,36 +513,6 @@ func rollbackTransaction(s *Session, transaction *sql.Tx) {
} }
} }
func FindGuildsByName(s *Session, name string) ([]*Guild, error) {
searchTerm := fmt.Sprintf("%%%s%%", name)
rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s
WHERE g.name ILIKE $1
`, guildInfoSelectQuery), searchTerm)
if err != nil {
s.logger.Error("failed to find guilds for search term", zap.Error(err), zap.String("searchTerm", name))
return nil, err
}
defer rows.Close()
guilds := make([]*Guild, 0)
for rows.Next() {
guild, err := buildGuildObjectFromDbResult(rows, err, s)
if err != nil {
return nil, err
}
guilds = append(guilds, guild)
}
return guilds, nil
}
func GetGuildInfoByID(s *Session, guildID uint32) (*Guild, error) { func GetGuildInfoByID(s *Session, guildID uint32) (*Guild, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf(` rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s %s
@@ -670,6 +641,8 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
} }
bf.WriteUint32(uint32(response)) bf.WriteUint32(uint32(response))
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
return
case mhfpacket.OPERATE_GUILD_APPLY: case mhfpacket.OPERATE_GUILD_APPLY:
err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil) err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil)
@@ -679,6 +652,8 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
} else { } else {
bf.WriteUint32(guild.LeaderCharID) bf.WriteUint32(guild.LeaderCharID)
} }
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
return
case mhfpacket.OPERATE_GUILD_LEAVE: case mhfpacket.OPERATE_GUILD_LEAVE:
var err error var err error
@@ -695,81 +670,53 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
} }
bf.WriteUint32(uint32(response)) bf.WriteUint32(uint32(response))
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
return
case mhfpacket.OPERATE_GUILD_DONATE_RANK: case mhfpacket.OPERATE_GUILD_DONATE_RANK:
handleDonateRP(s, pkt, bf, guild, false) handleDonateRP(s, pkt, bf, guild, false)
case mhfpacket.OPERATE_GUILD_SET_APPLICATION_DENY: case mhfpacket.OPERATE_GUILD_SET_APPLICATION_DENY:
// TODO: close applications for guild s.server.db.Exec("UPDATE guilds SET recruiting=false WHERE id=$1", guild.ID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_SET_APPLICATION_ALLOW: case mhfpacket.OPERATE_GUILD_SET_APPLICATION_ALLOW:
// TODO: open applications for guild s.server.db.Exec("UPDATE guilds SET recruiting=true WHERE id=$1", guild.ID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_TRUE: case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_TRUE:
handleAvoidLeadershipUpdate(s, pkt, true) handleAvoidLeadershipUpdate(s, pkt, true)
case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_FALSE: case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_FALSE:
handleAvoidLeadershipUpdate(s, pkt, false) handleAvoidLeadershipUpdate(s, pkt, false)
case mhfpacket.OPERATE_GUILD_UPDATE_COMMENT: case mhfpacket.OPERATE_GUILD_UPDATE_COMMENT:
pbf := byteframe.NewByteFrameFromBytes(pkt.UnkData) pbf := byteframe.NewByteFrameFromBytes(pkt.UnkData)
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() { if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return return
} }
_ = pbf.ReadUint8() // len _ = pbf.ReadUint8() // len
_ = pbf.ReadUint32() _ = pbf.ReadUint32()
guild.Comment = stringsupport.SJISToUTF8(pbf.ReadNullTerminatedBytes()) guild.Comment = stringsupport.SJISToUTF8(pbf.ReadNullTerminatedBytes())
err = guild.Save(s) guild.Save(s)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
bf.WriteUint32(0x00)
case mhfpacket.OPERATE_GUILD_UPDATE_MOTTO: case mhfpacket.OPERATE_GUILD_UPDATE_MOTTO:
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() { if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return return
} }
guild.SubMotto = pkt.UnkData[3] guild.SubMotto = pkt.UnkData[3]
guild.MainMotto = pkt.UnkData[4] guild.MainMotto = pkt.UnkData[4]
guild.Save(s)
err := guild.Save(s)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_1: case mhfpacket.OPERATE_GUILD_RENAME_PUGI_1:
handleRenamePugi(s, pkt.UnkData, guild, 1) handleRenamePugi(s, pkt.UnkData, guild, 1)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_2: case mhfpacket.OPERATE_GUILD_RENAME_PUGI_2:
handleRenamePugi(s, pkt.UnkData, guild, 2) handleRenamePugi(s, pkt.UnkData, guild, 2)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_3: case mhfpacket.OPERATE_GUILD_RENAME_PUGI_3:
handleRenamePugi(s, pkt.UnkData, guild, 3) handleRenamePugi(s, pkt.UnkData, guild, 3)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_1: case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_1:
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) // TODO: decode guild poogie outfits
return
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_2: case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_2:
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_3: case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_3:
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_DONATE_EVENT: case mhfpacket.OPERATE_GUILD_DONATE_EVENT:
handleDonateRP(s, pkt, bf, guild, true) handleDonateRP(s, pkt, bf, guild, true)
default: default:
panic(fmt.Sprintf("unhandled operate guild action '%d'", pkt.Action)) panic(fmt.Sprintf("unhandled operate guild action '%d'", pkt.Action))
} }
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }
func handleRenamePugi(s *Session, data []byte, guild *Guild, num int) { func handleRenamePugi(s *Session, data []byte, guild *Guild, num int) {
@@ -854,42 +801,45 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
return return
} }
if pkt.Action == mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT || pkt.Action == mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT { var mail Mail
switch pkt.Action { switch pkt.Action {
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT: case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT:
err = guild.AcceptApplication(s, pkt.CharID) err = guild.AcceptApplication(s, pkt.CharID)
mail = Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: "Accepted!",
Body: fmt.Sprintf("Your application to join 「%s」 was accepted.", guild.Name),
IsGuildInvite: false,
}
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT: case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT:
err = guild.RejectApplication(s, pkt.CharID) err = guild.RejectApplication(s, pkt.CharID)
mail = Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: "Rejected",
Body: fmt.Sprintf("Your application to join 「%s」 was rejected.", guild.Name),
IsGuildInvite: false,
} }
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
}
doAckSimpleSucceed(s, pkt.AckHandle, nil)
return
}
character, err := GetCharacterGuildData(s, pkt.CharID)
if err != nil || character == nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
switch pkt.Action {
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK: case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK:
err = guild.RemoveCharacter(s, pkt.CharID) err = guild.RemoveCharacter(s, pkt.CharID)
mail = Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: "Kicked",
Body: fmt.Sprintf("You were kicked from 「%s」.", guild.Name),
IsGuildInvite: false,
}
default: default:
doAckSimpleFail(s, pkt.AckHandle, nil) doAckSimpleFail(s, pkt.AckHandle, nil)
panic(fmt.Sprintf("unhandled operateGuildMember action '%d'", pkt.Action)) s.logger.Warn(fmt.Sprintf("unhandled operateGuildMember action '%d'", pkt.Action))
} }
if err != nil { if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil) doAckSimpleFail(s, pkt.AckHandle, nil)
return } else {
mail.Send(s, nil)
} }
doAckSimpleSucceed(s, pkt.AckHandle, nil) doAckSimpleSucceed(s, pkt.AckHandle, nil)
} }
@@ -906,6 +856,8 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
} }
if err == nil && guild != nil { if err == nil && guild != nil {
s.prevGuildID = guild.ID
guildName := stringsupport.UTF8ToSJIS(guild.Name) guildName := stringsupport.UTF8ToSJIS(guild.Name)
guildComment := stringsupport.UTF8ToSJIS(guild.Comment) guildComment := stringsupport.UTF8ToSJIS(guild.Comment)
guildLeaderName := stringsupport.UTF8ToSJIS(guild.LeaderName) guildLeaderName := stringsupport.UTF8ToSJIS(guild.LeaderName)
@@ -937,7 +889,9 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint8(guild.SubMotto) bf.WriteUint8(guild.SubMotto)
// Unk appears to be static // Unk appears to be static
bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
bf.WriteBool(!guild.Recruiting)
if characterGuildData == nil || characterGuildData.IsApplicant { if characterGuildData == nil || characterGuildData.IsApplicant {
bf.WriteUint16(0x00) bf.WriteUint16(0x00)
@@ -1057,19 +1011,22 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
if err != nil { if err != nil {
resp := byteframe.NewByteFrame() resp := byteframe.NewByteFrame()
resp.WriteUint32(0) // Count resp.WriteUint32(0) // Count
resp.WriteUint8(5) // Unk, read if count == 0. resp.WriteUint8(0) // Unk, read if count == 0.
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) doAckBufSucceed(s, pkt.AckHandle, resp.Data())
} }
if err != nil {
bf.WriteUint16(0)
} else {
bf.WriteUint16(uint16(len(applicants))) bf.WriteUint16(uint16(len(applicants)))
for _, applicant := range applicants { for _, applicant := range applicants {
bf.WriteUint32(applicant.CharID) bf.WriteUint32(applicant.CharID)
bf.WriteUint32(0x05) bf.WriteUint16(0)
bf.WriteUint16(0x0032) bf.WriteUint16(0)
bf.WriteUint8(0x00) bf.WriteUint16(applicant.HRP)
ps.Uint16(bf, applicant.Name, true) bf.WriteUint16(applicant.GR)
ps.Uint8(bf, applicant.Name, true)
}
} }
bf.WriteUint16(0x0000) bf.WriteUint16(0x0000)
@@ -1128,107 +1085,94 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
switch pkt.Type { switch pkt.Type {
case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME: case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME:
bf.ReadBytes(8) bf.ReadBytes(10)
searchTermLength := bf.ReadUint16() searchTerm := fmt.Sprintf(`%%%s%%`, stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()))
bf.ReadBytes(1) rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE g.name ILIKE $1 OFFSET $2`, guildInfoSelectQuery), searchTerm, pkt.Page*10)
searchTerm := bf.ReadBytes(uint(searchTermLength)) if err == nil {
var searchTermSafe string for rows.Next() {
searchTermSafe, err = s.clientContext.StrConv.Decode(bfutil.UpToNull(searchTerm)) guild, _ := buildGuildObjectFromDbResult(rows, err, s)
if err != nil { guilds = append(guilds, guild)
panic(err) }
} }
guilds, err = FindGuildsByName(s, searchTermSafe)
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME: case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME:
bf.ReadBytes(8) bf.ReadBytes(10)
searchTermLength := bf.ReadUint16() searchTerm := fmt.Sprintf(`%%%s%%`, stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()))
bf.ReadBytes(1) rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE lc.name ILIKE $1 OFFSET $2`, guildInfoSelectQuery), searchTerm, pkt.Page*10)
searchTerm := bf.ReadBytes(uint(searchTermLength)) if err == nil {
var searchTermSafe string
searchTermSafe, err = s.clientContext.StrConv.Decode(bfutil.UpToNull(searchTerm))
if err != nil {
panic(err)
}
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE lc.name ILIKE $1`, guildInfoSelectQuery), searchTermSafe)
if err != nil {
s.logger.Error("Failed to retrieve guild by leader name", zap.Error(err))
} else {
for rows.Next() { for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s) guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild) guilds = append(guilds, guild)
} }
} }
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_ID: case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_ID:
bf.ReadBytes(3) bf.ReadBytes(2)
ID := bf.ReadUint32() ID := bf.ReadUint32()
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE leader_id = $1`, guildInfoSelectQuery), ID) rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE leader_id = $1`, guildInfoSelectQuery), ID)
if err != nil { if err == nil {
s.logger.Error("Failed to retrieve guild by leader ID", zap.Error(err))
} else {
for rows.Next() { for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s) guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild) guilds = append(guilds, guild)
} }
} }
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_MEMBERS: case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_MEMBERS:
sorting := bf.ReadUint16() sorting := bf.ReadUint8()
if sorting == 1 { if sorting == 1 {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count DESC`, guildInfoSelectQuery)) rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count DESC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
} else { } else {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count ASC`, guildInfoSelectQuery)) rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count ASC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
} }
if err != nil { if err == nil {
s.logger.Error("Failed to retrieve guild by member count", zap.Error(err))
} else {
for rows.Next() { for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s) guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild) guilds = append(guilds, guild)
} }
} }
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_REGISTRATION: case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_REGISTRATION:
sorting := bf.ReadUint16() sorting := bf.ReadUint8()
if sorting == 1 { if sorting == 1 {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id ASC`, guildInfoSelectQuery)) rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id ASC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
} else { } else {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id DESC`, guildInfoSelectQuery)) rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id DESC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
} }
if err != nil { if err == nil {
s.logger.Error("Failed to retrieve guild by registration date", zap.Error(err))
} else {
for rows.Next() { for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s) guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild) guilds = append(guilds, guild)
} }
} }
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_RANK: case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_RANK:
sorting := bf.ReadUint16() sorting := bf.ReadUint8()
if sorting == 1 { if sorting == 1 {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp DESC`, guildInfoSelectQuery)) rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp DESC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
} else { } else {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp ASC`, guildInfoSelectQuery)) rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp ASC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
} }
if err != nil { if err == nil {
s.logger.Error("Failed to retrieve guild by rank", zap.Error(err))
} else {
for rows.Next() { for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s) guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild) guilds = append(guilds, guild)
} }
} }
case mhfpacket.ENUMERATE_GUILD_TYPE_MOTTO: case mhfpacket.ENUMERATE_GUILD_TYPE_MOTTO:
bf.ReadBytes(3) bf.ReadBytes(2)
mainMotto := bf.ReadUint16() mainMotto := bf.ReadUint16()
subMotto := bf.ReadUint16() subMotto := bf.ReadUint16()
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE main_motto = $1 AND sub_motto = $2`, guildInfoSelectQuery), mainMotto, subMotto) rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE main_motto = $1 AND sub_motto = $2 OFFSET $3`, guildInfoSelectQuery), mainMotto, subMotto, pkt.Page*10)
if err != nil { if err == nil {
s.logger.Error("Failed to retrieve guild by motto", zap.Error(err))
} else {
for rows.Next() { for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s) guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild) guilds = append(guilds, guild)
} }
} }
case mhfpacket.ENUMERATE_GUILD_TYPE_RECRUITING: case mhfpacket.ENUMERATE_GUILD_TYPE_RECRUITING:
// // Assume the player wants the newest guilds with open recruitment
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE recruiting=true ORDER BY id DESC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
if err == nil {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME: case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME:
// //
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_NAME: case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_NAME:
@@ -1251,23 +1195,21 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
bf = byteframe.NewByteFrame() bf = byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(guilds))) bf.WriteUint16(uint16(len(guilds)))
bf.WriteUint8(0x01) // Unk
for _, guild := range guilds { for _, guild := range guilds {
bf.WriteUint8(0x00) // Unk
bf.WriteUint32(guild.ID) bf.WriteUint32(guild.ID)
bf.WriteUint32(guild.LeaderCharID) bf.WriteUint32(guild.LeaderCharID)
bf.WriteUint16(guild.MemberCount) bf.WriteUint16(guild.MemberCount)
bf.WriteUint8(0x00) // Unk bf.WriteUint16(0x0000) // Unk
bf.WriteUint8(0x00) // Unk bf.WriteUint16(guild.Rank) // OR guilds in alliance
bf.WriteUint16(guild.Rank)
bf.WriteUint32(uint32(guild.CreatedAt.Unix())) bf.WriteUint32(uint32(guild.CreatedAt.Unix()))
ps.Uint8(bf, guild.Name, true) ps.Uint8(bf, guild.Name, true)
ps.Uint8(bf, guild.LeaderName, true) ps.Uint8(bf, guild.LeaderName, true)
bf.WriteUint8(0x01) // Unk bf.WriteUint8(0x01) // Unk
bf.WriteBool(!guild.Recruiting)
} }
bf.WriteUint8(0x01) // Unk
bf.WriteUint8(0x00) // Unk
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
@@ -1318,6 +1260,10 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
guild, err = GetGuildInfoByCharacterId(s, s.charID) guild, err = GetGuildInfoByCharacterId(s, s.charID)
} }
if guild == nil && s.prevGuildID > 0 {
guild, err = GetGuildInfoByID(s, s.prevGuildID)
}
if err != nil { if err != nil {
s.logger.Warn("failed to retrieve guild sending no result message") s.logger.Warn("failed to retrieve guild sending no result message")
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
@@ -1411,6 +1357,15 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
guild, err := GetGuildInfoByCharacterId(s, s.charID) guild, err := GetGuildInfoByCharacterId(s, s.charID)
if guild == nil && s.prevGuildID != 0 {
guild, err = GetGuildInfoByID(s, s.prevGuildID)
s.prevGuildID = 0
if guild == nil || err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
}
if err != nil { if err != nil {
s.logger.Warn("failed to respond to manage rights message") s.logger.Warn("failed to respond to manage rights message")
return return
@@ -1432,7 +1387,8 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
for _, member := range members { for _, member := range members {
bf.WriteUint32(member.CharID) bf.WriteUint32(member.CharID)
bf.WriteUint32(0x0) bf.WriteBool(member.Recruiter)
bf.WriteBytes(make([]byte, 3))
} }
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
@@ -1924,9 +1880,17 @@ func handleMsgMhfGenerateUdGuildMap(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfUpdateGuild(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfUpdateGuild(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfSetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSetGuildManageRight)
s.server.db.Exec("UPDATE guild_characters SET recruiter=$1 WHERE character_id=$2", pkt.Allowed, pkt.CharID)
// TODO: What is this supposed to return? This works for now
doAckBufSucceed(s, pkt.AckHandle, []byte{0x01})
}
func handleMsgMhfEnumerateInvGuild(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfEnumerateInvGuild(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateInvGuild)
stubEnumerateNoResults(s, pkt.AckHandle)
}
func handleMsgMhfOperationInvGuild(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfOperationInvGuild(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -16,6 +16,7 @@ type GuildMember struct {
IsApplicant bool `db:"is_applicant"` IsApplicant bool `db:"is_applicant"`
OrderIndex uint8 `db:"order_index"` OrderIndex uint8 `db:"order_index"`
LastLogin uint32 `db:"last_login"` LastLogin uint32 `db:"last_login"`
Recruiter bool `db:"recruiter"`
AvoidLeadership bool `db:"avoid_leadership"` AvoidLeadership bool `db:"avoid_leadership"`
IsLeader bool `db:"is_leader"` IsLeader bool `db:"is_leader"`
HRP uint16 `db:"hrp"` HRP uint16 `db:"hrp"`
@@ -43,18 +44,15 @@ func (gm *GuildMember) Save(s *Session) error {
return nil return nil
} }
//TODO add the recruiter permission to this check when it exists
func (gm *GuildMember) IsRecruiter() bool {
return gm.IsLeader || gm.IsSubLeader()
}
const guildMembersSelectSQL = ` const guildMembersSelectSQL = `
SELECT g.id as guild_id, SELECT
g.id as guild_id,
joined_at, joined_at,
c.name, c.name,
character.character_id, character.character_id,
coalesce(gc.order_index, 0) as order_index, coalesce(gc.order_index, 0) as order_index,
c.last_login, c.last_login,
coalesce(gc.recruiter, false) as recruiter,
coalesce(gc.avoid_leadership, false) as avoid_leadership, coalesce(gc.avoid_leadership, false) as avoid_leadership,
c.hrp, c.hrp,
c.gr, c.gr,
@@ -62,7 +60,7 @@ SELECT g.id as guild_id,
c.weapon_type, c.weapon_type,
character.is_applicant, character.is_applicant,
CASE WHEN g.leader_id = c.id THEN 1 ELSE 0 END as is_leader CASE WHEN g.leader_id = c.id THEN 1 ELSE 0 END as is_leader
FROM ( FROM (
SELECT character_id, true as is_applicant, guild_id SELECT character_id, true as is_applicant, guild_id
FROM guild_applications ga FROM guild_applications ga
WHERE ga.application_type = 'applied' WHERE ga.application_type = 'applied'

View File

@@ -21,7 +21,7 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
panic(err) panic(err)
} }
if actorCharGuildData == nil || !actorCharGuildData.IsRecruiter() { if actorCharGuildData == nil || !actorCharGuildData.Recruiter {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return return
} }
@@ -104,7 +104,7 @@ func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
panic(err) panic(err)
} }
if guildCharData == nil || !guildCharData.IsRecruiter() { if guildCharData == nil || !guildCharData.Recruiter {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return return
} }
@@ -190,13 +190,15 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
guildInfo, err := GetGuildInfoByCharacterId(s, s.charID) guildInfo, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil { if guildInfo == nil && s.prevGuildID == 0 {
panic(err)
}
if guildInfo == nil {
doAckSimpleFail(s, pkt.AckHandle, nil) doAckSimpleFail(s, pkt.AckHandle, nil)
return return
} else {
guildInfo, err = GetGuildInfoByID(s, s.prevGuildID)
if guildInfo == nil || err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
} }
rows, err := s.server.db.Queryx(` rows, err := s.server.db.Queryx(`

View File

@@ -27,7 +27,7 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
} }
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
hunts := 0 hunts := 0
rows, _ := s.server.db.Queryx("SELECT id, host_id, destination, level, return, acquired, claimed, hunters, treasure, hunt_data FROM guild_hunts WHERE guild_id=$1", guild.ID) rows, _ := s.server.db.Queryx("SELECT id, host_id, destination, level, return, acquired, claimed, hunters, treasure, hunt_data FROM guild_hunts WHERE guild_id=$1 AND $2 < return+604800", guild.ID, Time_Current_Adjusted().Unix())
for rows.Next() { for rows.Next() {
hunt := &TreasureHunt{} hunt := &TreasureHunt{}
err = rows.StructScan(&hunt) err = rows.StructScan(&hunt)
@@ -51,6 +51,9 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
bf.WriteBytes(hunt.HuntData) bf.WriteBytes(hunt.HuntData)
break break
} else if pkt.MaxHunts == 30 && hunt.Acquired && hunt.Level == 2 { } else if pkt.MaxHunts == 30 && hunt.Acquired && hunt.Level == 2 {
if hunts == 30 {
break
}
hunts++ hunts++
bf.WriteUint32(hunt.HuntID) bf.WriteUint32(hunt.HuntID)
bf.WriteUint32(hunt.Destination) bf.WriteUint32(hunt.Destination)

View File

@@ -2,6 +2,7 @@ package channelserver
import ( import (
"database/sql" "database/sql"
"erupe-ce/common/stringsupport"
"time" "time"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
@@ -275,7 +276,7 @@ func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
body := s.clientContext.StrConv.MustEncode(mail.Body) body := stringsupport.UTF8ToSJIS(mail.Body)
bf.WriteNullTerminatedBytes(body) bf.WriteNullTerminatedBytes(body)
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
@@ -307,13 +308,11 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
s.mailAccIndex++ s.mailAccIndex++
itemAttached := m.AttachedItemID != 0 itemAttached := m.AttachedItemID != 0
subject := s.clientContext.StrConv.MustEncode(m.Subject)
sender := s.clientContext.StrConv.MustEncode(m.SenderName)
msg.WriteUint32(m.SenderID) msg.WriteUint32(m.SenderID)
msg.WriteUint32(uint32(m.CreatedAt.Unix())) msg.WriteUint32(uint32(m.CreatedAt.Unix()))
msg.WriteUint8(uint8(accIndex)) msg.WriteUint8(accIndex)
msg.WriteUint8(uint8(i)) msg.WriteUint8(uint8(i))
flags := uint8(0x00) flags := uint8(0x00)
@@ -329,9 +328,16 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
// System message, hides ID // System message, hides ID
// flags |= 0x04 // flags |= 0x04
// Workaround until EN mail items are patched
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DisableMailItems {
if itemAttached {
flags |= 0x08
}
} else {
if m.AttachedItemReceived { if m.AttachedItemReceived {
flags |= 0x08 flags |= 0x08
} }
}
if m.IsGuildInvite { if m.IsGuildInvite {
flags |= 0x10 flags |= 0x10
@@ -339,11 +345,10 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
msg.WriteUint8(flags) msg.WriteUint8(flags)
msg.WriteBool(itemAttached) msg.WriteBool(itemAttached)
msg.WriteUint8(uint8(len(subject) + 1)) msg.WriteUint8(16)
msg.WriteUint8(uint8(len(sender) + 1)) msg.WriteUint8(21)
msg.WriteNullTerminatedBytes(subject) msg.WriteBytes(stringsupport.PaddedString(m.Subject, 16, true))
msg.WriteNullTerminatedBytes(sender) msg.WriteBytes(stringsupport.PaddedString(m.SenderName, 21, true))
if itemAttached { if itemAttached {
msg.WriteUint16(m.AttachedItemAmount) msg.WriteUint16(m.AttachedItemAmount)
msg.WriteUint16(m.AttachedItemID) msg.WriteUint16(m.AttachedItemID)
@@ -358,34 +363,22 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) {
mail, err := GetMailByID(s, s.mailList[pkt.AccIndex]) mail, err := GetMailByID(s, s.mailList[pkt.AccIndex])
if err != nil { if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err) panic(err)
} }
switch mhfpacket.OperateMailOperation(pkt.Operation) {
switch pkt.Operation {
case mhfpacket.OPERATE_MAIL_DELETE: case mhfpacket.OPERATE_MAIL_DELETE:
err = mail.MarkDeleted(s) err = mail.MarkDeleted(s)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
case mhfpacket.OPERATE_MAIL_LOCK: case mhfpacket.OPERATE_MAIL_LOCK:
err = mail.MarkLocked(s, true) err = mail.MarkLocked(s, true)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
case mhfpacket.OPERATE_MAIL_UNLOCK: case mhfpacket.OPERATE_MAIL_UNLOCK:
err = mail.MarkLocked(s, false) err = mail.MarkLocked(s, false)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
case mhfpacket.OPERATE_MAIL_ACQUIRE_ITEM: case mhfpacket.OPERATE_MAIL_ACQUIRE_ITEM:
err = mail.MarkAcquired(s) err = mail.MarkAcquired(s)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
} }
if err != nil {
panic(err)
} }
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))

View File

@@ -2,11 +2,8 @@ package channelserver
import ( import (
"encoding/hex" "encoding/hex"
"fmt"
"strings"
"time" "time"
//"erupe-ce/common/stringsupport"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"github.com/lib/pq" "github.com/lib/pq"
@@ -14,16 +11,6 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func contains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}
return false
}
func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateShop) pkt := p.(*mhfpacket.MsgMhfEnumerateShop)
// SHOP TYPES: // SHOP TYPES:
@@ -162,27 +149,19 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
} }
} else { } else {
_, week := time.Now().ISOWeek() shopEntries, err := s.server.db.Query("SELECT itemhash,itemID,Points,TradeQuantity,rankReqLow,rankReqHigh,rankReqG,storeLevelReq,maximumQuantity,boughtQuantity,roadFloorsRequired,weeklyFatalisKills FROM normal_shop_items WHERE shoptype=$1 AND shopid=$2", pkt.ShopType, pkt.ShopID)
season := fmt.Sprintf("%d", week%4)
shopEntries, err := s.server.db.Query("SELECT itemhash,itemID,Points,TradeQuantity,rankReqLow,rankReqHigh,rankReqG,storeLevelReq,maximumQuantity,boughtQuantity,roadFloorsRequired,weeklyFatalisKills, COALESCE(enable_weeks, '') FROM normal_shop_items WHERE shoptype=$1 AND shopid=$2", pkt.ShopType, pkt.ShopID)
if err != nil { if err != nil {
panic(err) panic(err)
} }
var ItemHash, entryCount int var ItemHash, entryCount int
var itemID, Points, TradeQuantity, rankReqLow, rankReqHigh, rankReqG, storeLevelReq, maximumQuantity, boughtQuantity, roadFloorsRequired, weeklyFatalisKills, charQuantity uint16 var itemID, Points, TradeQuantity, rankReqLow, rankReqHigh, rankReqG, storeLevelReq, maximumQuantity, boughtQuantity, roadFloorsRequired, weeklyFatalisKills, charQuantity uint16
var itemWeeks string
resp := byteframe.NewByteFrame() resp := byteframe.NewByteFrame()
resp.WriteUint32(0) // total defs resp.WriteUint32(0) // total defs
for shopEntries.Next() { for shopEntries.Next() {
err = shopEntries.Scan(&ItemHash, &itemID, &Points, &TradeQuantity, &rankReqLow, &rankReqHigh, &rankReqG, &storeLevelReq, &maximumQuantity, &boughtQuantity, &roadFloorsRequired, &weeklyFatalisKills, &itemWeeks) err = shopEntries.Scan(&ItemHash, &itemID, &Points, &TradeQuantity, &rankReqLow, &rankReqHigh, &rankReqG, &storeLevelReq, &maximumQuantity, &boughtQuantity, &roadFloorsRequired, &weeklyFatalisKills)
if err != nil { if err != nil {
panic(err) panic(err)
} }
if len(itemWeeks) > 0 && !contains(strings.Split(itemWeeks, ","), season) {
continue
}
resp.WriteUint32(uint32(ItemHash)) resp.WriteUint32(uint32(ItemHash))
resp.WriteUint16(0) // unk, always 0 in existing packets resp.WriteUint16(0) // unk, always 0 in existing packets
resp.WriteUint16(itemID) resp.WriteUint16(itemID)
@@ -195,13 +174,9 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
resp.WriteUint16(storeLevelReq) resp.WriteUint16(storeLevelReq)
resp.WriteUint16(maximumQuantity) resp.WriteUint16(maximumQuantity)
if maximumQuantity > 0 { if maximumQuantity > 0 {
var itemWeek int err = s.server.db.QueryRow("SELECT COALESCE(usedquantity,0) FROM shop_item_state WHERE itemhash=$1 AND char_id=$2", ItemHash, s.charID).Scan(&charQuantity)
err = s.server.db.QueryRow("SELECT COALESCE(usedquantity,0), COALESCE(week,-1) FROM shop_item_state WHERE itemhash=$1 AND char_id=$2", ItemHash, s.charID).Scan(&charQuantity, &itemWeek)
if err != nil { if err != nil {
resp.WriteUint16(0) resp.WriteUint16(0)
} else if pkt.ShopID == 7 && itemWeek >= 0 && itemWeek != week {
clearShopItemState(s, s.charID, uint32(ItemHash))
resp.WriteUint16(0)
} else { } else {
resp.WriteUint16(charQuantity) resp.WriteUint16(charQuantity)
} }
@@ -224,7 +199,6 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
} }
func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
_, week := time.Now().ISOWeek()
// writing out to an editable shop enumeration // writing out to an editable shop enumeration
pkt := p.(*mhfpacket.MsgMhfAcquireExchangeShop) pkt := p.(*mhfpacket.MsgMhfAcquireExchangeShop)
if pkt.DataSize == 10 { if pkt.DataSize == 10 {
@@ -232,10 +206,10 @@ func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
_ = bf.ReadUint16() // unk, always 1 in examples _ = bf.ReadUint16() // unk, always 1 in examples
itemHash := bf.ReadUint32() itemHash := bf.ReadUint32()
buyCount := bf.ReadUint32() buyCount := bf.ReadUint32()
_, err := s.server.db.Exec(`INSERT INTO shop_item_state (char_id, itemhash, usedquantity, week) _, err := s.server.db.Exec(`INSERT INTO shop_item_state (char_id, itemhash, usedquantity)
VALUES ($1,$2,$3,$4) ON CONFLICT (char_id, itemhash) VALUES ($1,$2,$3) ON CONFLICT (char_id, itemhash)
DO UPDATE SET usedquantity = shop_item_state.usedquantity + $3 DO UPDATE SET usedquantity = shop_item_state.usedquantity + $3
WHERE EXCLUDED.char_id=$1 AND EXCLUDED.itemhash=$2`, s.charID, itemHash, buyCount, week) WHERE EXCLUDED.char_id=$1 AND EXCLUDED.itemhash=$2`, s.charID, itemHash, buyCount)
if err != nil { if err != nil {
s.logger.Fatal("Failed to update shop_item_state in db", zap.Error(err)) s.logger.Fatal("Failed to update shop_item_state in db", zap.Error(err))
} }
@@ -243,13 +217,6 @@ func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
} }
func clearShopItemState(s *Session, charId uint32, itemHash uint32) {
_, err := s.server.db.Exec(`DELETE FROM shop_item_state WHERE char_id=$1 AND itemhash=$2`, charId, itemHash)
if err != nil {
s.logger.Fatal("Failed to delete shop_item_state in db", zap.Error(err))
}
}
func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) {
// returns number of times the gacha was played, will need persistent db stuff // returns number of times the gacha was played, will need persistent db stuff
pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory) pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory)

View File

@@ -295,8 +295,11 @@ func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session)
} }
} }
func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) { func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session, ignoredChannel *Server) {
for _, c := range s.Channels { for _, c := range s.Channels {
if c == ignoredChannel {
continue
}
for _, session := range c.sessions { for _, session := range c.sessions {
if session == ignoredSession { if session == ignoredSession {
continue continue
@@ -357,7 +360,7 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
BroadcastType: BroadcastTypeServer, BroadcastType: BroadcastTypeServer,
MessageType: BinaryMessageTypeChat, MessageType: BinaryMessageTypeChat,
RawDataPayload: bf.Data(), RawDataPayload: bf.Data(),
}, nil) }, nil, s)
} }
func (s *Server) DiscordChannelSend(charName string, content string) { func (s *Server) DiscordChannelSend(charName string, content string) {

View File

@@ -33,6 +33,7 @@ type Session struct {
stage *Stage stage *Stage
reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet. reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet.
stagePass string // Temporary storage stagePass string // Temporary storage
prevGuildID uint32 // Stores the last GuildID used in InfoGuild
charID uint32 charID uint32
logKey []byte logKey []byte
sessionStart int64 sessionStart int64

View File

@@ -72,6 +72,15 @@
</div> </div>
<ul class="article"> <ul class="article">
<li> <li>
<div class="date">2022-08-02</div>
<div class="body">
<a
href="javascript:toggleModal('openLink',&quot;https://discord.com/channels/368424389416583169/929509970624532511/1003985850255818762&quot;);"
onclick="soundOk()">Server Update 9 Released!
</a>
</div>
</li>
<li>
<div class="date">2022-05-03</div> <div class="date">2022-05-03</div>
<div class="body"> <div class="body">
<a <a

View File

@@ -1,5 +1,6 @@
var __mhf_launcher = {}; var __mhf_launcher = {};
var loginScreen = true; var loginScreen = true;
var loggingIn = false;
var doingAuto = false; var doingAuto = false;
var uids; var uids;
var selectedUid; var selectedUid;
@@ -259,6 +260,11 @@ function switchPrompt() {
} }
function doLogin(option) { function doLogin(option) {
if (loggingIn) {
return;
} else {
loggingIn = true;
}
let username = document.getElementById('username').value; let username = document.getElementById('username').value;
let password = document.getElementById('password').value; let password = document.getElementById('password').value;
if (username == '') { if (username == '') {
@@ -289,6 +295,7 @@ function checkAuth() {
setTimeout(checkAuth, 10); setTimeout(checkAuth, 10);
return; return;
} else if (loginResult == 'AUTH_SUCCESS') { } else if (loginResult == 'AUTH_SUCCESS') {
loggingIn = false;
saveAccount(); saveAccount();
addLog('Connected.', 'good'); addLog('Connected.', 'good');
if (doingAuto) { if (doingAuto) {
@@ -300,6 +307,7 @@ function checkAuth() {
switchPrompt(); switchPrompt();
} }
} else { } else {
loggingIn = false;
addLog('Error logging in: '+loginResult+':'+window.external.getSignResult(), 'error'); addLog('Error logging in: '+loginResult+':'+window.external.getSignResult(), 'error');
} }
document.getElementById('processing').style.display = 'none'; document.getElementById('processing').style.display = 'none';
@@ -479,8 +487,26 @@ function doEval() {
function init() { function init() {
document.addEventListener('keypress', function(e) { document.addEventListener('keypress', function(e) {
if (e.key == '~') { switch (e.key) {
case '~':
document.getElementById('dev').style.display = 'block'; document.getElementById('dev').style.display = 'block';
break;
case 'Enter':
if (loginScreen) {
doLogin()
} else {
soundLogin();launch()
}
break;
case ',':
if (!loginScreen) {
soundOk();charselScrollUp()
}
break;
case '.':
if (!loginScreen) {
soundOk();charselScrollDown()
}
} }
}); });
let unselectable = document.getElementsByClassName('unselectable'); let unselectable = document.getElementsByClassName('unselectable');