mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
Merge pull request #41 from ZeruLight/feature/systems-rework
feature/systems-rework
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
"DisableSoftCrash": false,
|
||||
"devmode": true,
|
||||
"devmodeoptions": {
|
||||
"serverName" : "",
|
||||
"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.",
|
||||
@@ -47,6 +46,10 @@
|
||||
"name": "Reload",
|
||||
"enabled": true,
|
||||
"prefix": "!reload"
|
||||
}, {
|
||||
"name": "KeyQuest",
|
||||
"enabled": false,
|
||||
"prefix": "!kqf"
|
||||
}
|
||||
],
|
||||
"database": {
|
||||
|
||||
@@ -29,13 +29,11 @@ type Config struct {
|
||||
|
||||
// DevModeOptions holds various debug/temporary options for use while developing Erupe.
|
||||
type DevModeOptions struct {
|
||||
ServerName string // To get specific instance server about (Current Players/Event Week)
|
||||
EnableLauncherServer bool // Enables the launcher server to be served on port 80
|
||||
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 HR9 so that you can join non-beginner worlds.
|
||||
FixedStageID bool // Causes all move_stage to use the ID sl1Ns200p0a0u0 to get you into all stages
|
||||
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
|
||||
@@ -58,10 +56,7 @@ type SaveDumpOptions struct {
|
||||
type Discord struct {
|
||||
Enabled bool
|
||||
BotToken string
|
||||
ServerID string
|
||||
RealtimeChannelID string
|
||||
DevRoles []string
|
||||
DevMode bool
|
||||
}
|
||||
|
||||
// Command is a channelserver chat command
|
||||
|
||||
@@ -29,8 +29,10 @@ func (m *MsgMhfEnumerateHouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cli
|
||||
m.CharID = bf.ReadUint32()
|
||||
m.Method = bf.ReadUint8()
|
||||
m.Unk = bf.ReadUint16()
|
||||
_ = bf.ReadUint8() // len
|
||||
m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
|
||||
lenName := bf.ReadUint8()
|
||||
if lenName > 0 {
|
||||
m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package mhfpacket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"erupe-ce/network/clientctx"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/clientctx"
|
||||
)
|
||||
|
||||
// MsgMhfGetGemInfo represents the MSG_MHF_GET_GEM_INFO
|
||||
type MsgMhfGetGemInfo struct{}
|
||||
type MsgMhfGetGemInfo struct {
|
||||
AckHandle uint32
|
||||
Unk uint32
|
||||
Unk1 []byte
|
||||
}
|
||||
|
||||
// Opcode returns the ID associated with this packet type.
|
||||
func (m *MsgMhfGetGemInfo) Opcode() network.PacketID {
|
||||
@@ -18,7 +22,10 @@ func (m *MsgMhfGetGemInfo) Opcode() network.PacketID {
|
||||
|
||||
// Parse parses the packet from binary
|
||||
func (m *MsgMhfGetGemInfo) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
|
||||
return errors.New("NOT IMPLEMENTED")
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
m.Unk = bf.ReadUint32()
|
||||
m.Unk1 = bf.ReadBytes(24)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build builds a binary packet from the current data.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package mhfpacket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"erupe-ce/network/clientctx"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/clientctx"
|
||||
)
|
||||
|
||||
// The server sends different responses based on these values.
|
||||
@@ -13,7 +13,7 @@ const (
|
||||
TowerInfoTypeUnk0 = iota
|
||||
TowerInfoTypeTowerRankPoint
|
||||
TowerInfoTypeGetOwnTowerSkill
|
||||
TowerInfoTypeUnk3
|
||||
TowerInfoTypeGetOwnTowerLevelV3
|
||||
TowerInfoTypeTowerTouhaHistory
|
||||
TowerInfoTypeUnk5
|
||||
)
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package mhfpacket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"erupe-ce/network/clientctx"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/clientctx"
|
||||
)
|
||||
|
||||
// MsgMhfInfoJoint represents the MSG_MHF_INFO_JOINT
|
||||
type MsgMhfInfoJoint struct{}
|
||||
type MsgMhfInfoJoint struct {
|
||||
AckHandle uint32
|
||||
AllianceID uint32
|
||||
Unk uint32
|
||||
}
|
||||
|
||||
// Opcode returns the ID associated with this packet type.
|
||||
func (m *MsgMhfInfoJoint) Opcode() network.PacketID {
|
||||
@@ -18,7 +22,10 @@ func (m *MsgMhfInfoJoint) Opcode() network.PacketID {
|
||||
|
||||
// Parse parses the packet from binary
|
||||
func (m *MsgMhfInfoJoint) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
|
||||
return errors.New("NOT IMPLEMENTED")
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
m.AllianceID = bf.ReadUint32()
|
||||
m.Unk = bf.ReadUint32()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build builds a binary packet from the current data.
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
package mhfpacket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"errors"
|
||||
|
||||
"erupe-ce/network/clientctx"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/clientctx"
|
||||
)
|
||||
|
||||
type OperateGuildAction uint8
|
||||
|
||||
const (
|
||||
OPERATE_GUILD_DISBAND = 0x01
|
||||
OPERATE_GUILD_APPLY = 0x02
|
||||
OPERATE_GUILD_LEAVE = 0x03
|
||||
OPERATE_GUILD_RESIGN = 0x04
|
||||
OPERATE_GUILD_SET_APPLICATION_DENY = 0x05
|
||||
OPERATE_GUILD_SET_APPLICATION_ALLOW = 0x06
|
||||
OPERATE_GUILD_SET_AVOID_LEADERSHIP_TRUE = 0x07
|
||||
OPERATE_GUILD_SET_AVOID_LEADERSHIP_FALSE = 0x08
|
||||
OPERATE_GUILD_UPDATE_COMMENT = 0x09
|
||||
OPERATE_GUILD_DONATE_RANK = 0x0a
|
||||
OPERATE_GUILD_UPDATE_MOTTO = 0x0b
|
||||
OPERATE_GUILD_RENAME_PUGI_1 = 0x0c
|
||||
OPERATE_GUILD_RENAME_PUGI_2 = 0x0d
|
||||
OPERATE_GUILD_RENAME_PUGI_3 = 0x0e
|
||||
OPERATE_GUILD_CHANGE_PUGI_1 = 0x0f
|
||||
OPERATE_GUILD_CHANGE_PUGI_2 = 0x10
|
||||
OPERATE_GUILD_CHANGE_PUGI_3 = 0x11
|
||||
// pugi something
|
||||
OPERATE_GUILD_DONATE_EVENT = 0x15
|
||||
// pugi something
|
||||
OPERATE_GUILD_CHANGE_DIVA_PUGI_1 = 0x19
|
||||
OPERATE_GUILD_CHANGE_DIVA_PUGI_2 = 0x1a
|
||||
OPERATE_GUILD_CHANGE_DIVA_PUGI_3 = 0x1b
|
||||
OPERATE_GUILD_DISBAND = 0x01
|
||||
OPERATE_GUILD_APPLY = 0x02
|
||||
OPERATE_GUILD_LEAVE = 0x03
|
||||
OPERATE_GUILD_RESIGN = 0x04
|
||||
OPERATE_GUILD_SET_APPLICATION_DENY = 0x05
|
||||
OPERATE_GUILD_SET_APPLICATION_ALLOW = 0x06
|
||||
OPERATE_GUILD_SET_AVOID_LEADERSHIP_TRUE = 0x07
|
||||
OPERATE_GUILD_SET_AVOID_LEADERSHIP_FALSE = 0x08
|
||||
OPERATE_GUILD_UPDATE_COMMENT = 0x09
|
||||
OPERATE_GUILD_DONATE_RANK = 0x0a
|
||||
OPERATE_GUILD_UPDATE_MOTTO = 0x0b
|
||||
OPERATE_GUILD_RENAME_PUGI_1 = 0x0c
|
||||
OPERATE_GUILD_RENAME_PUGI_2 = 0x0d
|
||||
OPERATE_GUILD_RENAME_PUGI_3 = 0x0e
|
||||
OPERATE_GUILD_CHANGE_PUGI_1 = 0x0f
|
||||
OPERATE_GUILD_CHANGE_PUGI_2 = 0x10
|
||||
OPERATE_GUILD_CHANGE_PUGI_3 = 0x11
|
||||
// pugi something
|
||||
OPERATE_GUILD_DONATE_EVENT = 0x15
|
||||
OPERATE_GUILD_EVENT_EXCHANGE = 0x16
|
||||
OPERATE_GUILD_CHANGE_DIVA_PUGI_1 = 0x19
|
||||
OPERATE_GUILD_CHANGE_DIVA_PUGI_2 = 0x1a
|
||||
OPERATE_GUILD_CHANGE_DIVA_PUGI_3 = 0x1b
|
||||
)
|
||||
|
||||
// MsgMhfOperateGuild represents the MSG_MHF_OPERATE_GUILD
|
||||
@@ -41,7 +41,8 @@ type MsgMhfOperateGuild struct {
|
||||
AckHandle uint32
|
||||
GuildID uint32
|
||||
Action OperateGuildAction
|
||||
UnkData []byte
|
||||
Data1 *byteframe.ByteFrame
|
||||
Data2 *byteframe.ByteFrame
|
||||
}
|
||||
|
||||
// Opcode returns the ID associated with this packet type.
|
||||
@@ -54,8 +55,9 @@ func (m *MsgMhfOperateGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clien
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
m.GuildID = bf.ReadUint32()
|
||||
m.Action = OperateGuildAction(bf.ReadUint8())
|
||||
m.UnkData = bf.DataFromCurrent()
|
||||
bf.Seek(int64(len(bf.Data()) - 2), 0)
|
||||
dataLen := uint(bf.ReadUint8())
|
||||
m.Data1 = byteframe.NewByteFrameFromBytes(bf.ReadBytes(4))
|
||||
m.Data2 = byteframe.NewByteFrameFromBytes(bf.ReadBytes(dataLen))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
package mhfpacket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"errors"
|
||||
|
||||
"erupe-ce/network/clientctx"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/clientctx"
|
||||
)
|
||||
|
||||
type OperateJointAction uint8
|
||||
|
||||
const (
|
||||
OPERATE_JOINT_DISBAND = 0x01
|
||||
OPERATE_JOINT_LEAVE = 0x03
|
||||
OPERATE_JOINT_KICK = 0x09
|
||||
OPERATE_JOINT_DISBAND = 0x01
|
||||
OPERATE_JOINT_LEAVE = 0x03
|
||||
OPERATE_JOINT_KICK = 0x09
|
||||
)
|
||||
|
||||
// MsgMhfOperateJoint represents the MSG_MHF_OPERATE_JOINT
|
||||
type MsgMhfOperateJoint struct {
|
||||
AckHandle uint32
|
||||
AllianceID uint32
|
||||
GuildID uint32
|
||||
Action OperateJointAction
|
||||
UnkData []byte
|
||||
AckHandle uint32
|
||||
AllianceID uint32
|
||||
GuildID uint32
|
||||
Action OperateJointAction
|
||||
UnkData *byteframe.ByteFrame
|
||||
}
|
||||
|
||||
// Opcode returns the ID associated with this packet type.
|
||||
@@ -32,13 +32,13 @@ func (m *MsgMhfOperateJoint) Opcode() network.PacketID {
|
||||
|
||||
// Parse parses the packet from binary
|
||||
func (m *MsgMhfOperateJoint) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
m.AllianceID = bf.ReadUint32()
|
||||
m.GuildID = bf.ReadUint32()
|
||||
m.Action = OperateJointAction(bf.ReadUint8())
|
||||
m.UnkData = bf.DataFromCurrent()
|
||||
bf.Seek(int64(len(bf.Data()) - 2), 0)
|
||||
return nil
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
m.AllianceID = bf.ReadUint32()
|
||||
m.GuildID = bf.ReadUint32()
|
||||
m.Action = OperateJointAction(bf.ReadUint8())
|
||||
m.UnkData = byteframe.NewByteFrameFromBytes(bf.DataFromCurrent())
|
||||
bf.Seek(int64(len(bf.Data())-2), 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build builds a binary packet from the current data.
|
||||
|
||||
37
patch-schema/persistent-house.sql
Normal file
37
patch-schema/persistent-house.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
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;
|
||||
@@ -235,17 +235,11 @@ func logoutPlayer(s *Session) {
|
||||
|
||||
saveData, err := GetCharacterSaveData(s, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
s.logger.Error("Failed to get savedata")
|
||||
return
|
||||
}
|
||||
saveData.RP += uint16(rpGained)
|
||||
transaction, err := s.server.db.Begin()
|
||||
err = saveData.Save(s, transaction)
|
||||
if err != nil {
|
||||
transaction.Rollback()
|
||||
panic(err)
|
||||
} else {
|
||||
transaction.Commit()
|
||||
}
|
||||
saveData.Save(s)
|
||||
}
|
||||
|
||||
func handleMsgSysSetStatus(s *Session, p mhfpacket.MHFPacket) {}
|
||||
@@ -1749,6 +1743,7 @@ func handleMsgMhfUpdateEquipSkinHist(s *Session, p mhfpacket.MHFPacket) {
|
||||
byteInd := (bit / 8)
|
||||
bitInByte := bit % 8
|
||||
data[startByte+byteInd] |= bits.Reverse8((1 << uint(bitInByte)))
|
||||
dumpSaveData(s, data, "skinhist")
|
||||
_, err = s.server.db.Exec("UPDATE characters SET skin_hist=$1 WHERE id=$2", data, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -1778,6 +1773,7 @@ func handleMsgMhfGetEnhancedMinidata(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfSetEnhancedMinidata(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSetEnhancedMinidata)
|
||||
dumpSaveData(s, pkt.RawDataPayload, "minidata")
|
||||
_, err := s.server.db.Exec("UPDATE characters SET minidata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update minidata in db", zap.Error(err))
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/config"
|
||||
"erupe-ce/network/binpacket"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/config"
|
||||
"erupe-ce/network/binpacket"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -259,6 +259,27 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(chatMessage.Message, commands["KeyQuest"].Prefix) {
|
||||
if commands["KeyQuest"].Enabled {
|
||||
if strings.HasPrefix(chatMessage.Message, "!kqf get") {
|
||||
sendServerChatMessage(s, fmt.Sprintf("KQF: %x", s.kqf))
|
||||
} else if strings.HasPrefix(chatMessage.Message, "!kqf set") {
|
||||
var hexs string
|
||||
n, numerr := fmt.Sscanf(chatMessage.Message, "!kqf set %s", &hexs)
|
||||
if numerr != nil || n != 1 || len(hexs) != 16 {
|
||||
sendServerChatMessage(s, "Error in command. Format: !kqf set xxxxxxxxxxxxxxxx")
|
||||
} else {
|
||||
hexd, _ := hex.DecodeString(hexs)
|
||||
s.kqf = hexd
|
||||
s.kqfOverride = true
|
||||
sendServerChatMessage(s, "KQF set, please switch Land/World")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["KeyQuest"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(chatMessage.Message, commands["Rights"].Prefix) {
|
||||
// Set account rights
|
||||
if commands["Rights"].Enabled {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/binary"
|
||||
|
||||
"erupe-ce/network/mhfpacket"
|
||||
@@ -10,120 +9,149 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
CharacterSaveRPPointer = 0x22D16
|
||||
pointerGender = 0x81 // +1
|
||||
pointerRP = 0x22D16 // +2
|
||||
pointerHouseTier = 0x1FB6C // +5
|
||||
pointerHouseData = 0x1FE01 // +195
|
||||
pointerBookshelfData = 0x22298 // +5576
|
||||
// Gallery data also exists at 0x21578, is this the contest submission?
|
||||
pointerGalleryData = 0x22320 // +1748
|
||||
pointerToreData = 0x1FCB4 // +240
|
||||
pointerGardenData = 0x22C58 // +68
|
||||
pointerWeaponType = 0x1F715 // +1
|
||||
pointerWeaponID = 0x1F60A // +2
|
||||
pointerHRP = 0x1FDF6 // +2
|
||||
pointerGRP = 0x1FDFC // +4
|
||||
pointerKQF = 0x23D20 // +8
|
||||
)
|
||||
|
||||
type CharacterSaveData struct {
|
||||
CharID uint32
|
||||
Name string
|
||||
RP uint16
|
||||
IsNewCharacter bool
|
||||
|
||||
// Use provided setter/getter
|
||||
baseSaveData []byte
|
||||
Gender bool
|
||||
RP uint16
|
||||
HouseTier []byte
|
||||
HouseData []byte
|
||||
BookshelfData []byte
|
||||
GalleryData []byte
|
||||
ToreData []byte
|
||||
GardenData []byte
|
||||
WeaponType uint8
|
||||
WeaponID uint16
|
||||
HRP uint16
|
||||
GR uint16
|
||||
KQF []byte
|
||||
|
||||
compSave []byte
|
||||
decompSave []byte
|
||||
}
|
||||
|
||||
func GetCharacterSaveData(s *Session, charID uint32) (*CharacterSaveData, error) {
|
||||
result, err := s.server.db.Query("SELECT id, savedata, is_new_character, name FROM characters WHERE id = $1", charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve save data for character", zap.Error(err), zap.Uint32("charID", charID))
|
||||
s.logger.Error("Failed to get savedata", zap.Error(err), zap.Uint32("charID", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer result.Close()
|
||||
if !result.Next() {
|
||||
s.logger.Error("No savedata found", zap.Uint32("charID", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saveData := &CharacterSaveData{}
|
||||
var compressedBaseSave []byte
|
||||
|
||||
if !result.Next() {
|
||||
s.logger.Error("no results found for character save data", zap.Uint32("charID", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = result.Scan(&saveData.CharID, &compressedBaseSave, &saveData.IsNewCharacter, &saveData.Name)
|
||||
|
||||
err = result.Scan(&saveData.CharID, &saveData.compSave, &saveData.IsNewCharacter, &saveData.Name)
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to retrieve save data for character",
|
||||
zap.Error(err),
|
||||
zap.Uint32("charID", charID),
|
||||
)
|
||||
|
||||
s.logger.Error("Failed to scan savedata", zap.Error(err), zap.Uint32("charID", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if compressedBaseSave == nil {
|
||||
if saveData.compSave == nil {
|
||||
return saveData, nil
|
||||
}
|
||||
|
||||
decompressedBaseSave, err := nullcomp.Decompress(compressedBaseSave)
|
||||
|
||||
err = saveData.Decompress()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to decompress savedata from db", zap.Error(err))
|
||||
s.logger.Error("Failed to decompress savedata", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saveData.SetBaseSaveData(decompressedBaseSave)
|
||||
saveData.updateStructWithSaveData()
|
||||
|
||||
return saveData, nil
|
||||
}
|
||||
|
||||
func (save *CharacterSaveData) Save(s *Session, transaction *sql.Tx) error {
|
||||
// We need to update the save data byte array before we save it back to the DB
|
||||
func (save *CharacterSaveData) Save(s *Session) {
|
||||
if !s.kqfOverride {
|
||||
s.kqf = save.KQF
|
||||
} else {
|
||||
save.KQF = s.kqf
|
||||
}
|
||||
|
||||
save.updateSaveDataWithStruct()
|
||||
|
||||
compressedData, err := save.CompressedBaseData(s)
|
||||
|
||||
err := save.Compress()
|
||||
if err != nil {
|
||||
return err
|
||||
s.logger.Error("Failed to compress savedata", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
updateSQL := "UPDATE characters SET savedata=$1, is_new_character=$3 WHERE id=$2"
|
||||
|
||||
if transaction != nil {
|
||||
_, err = transaction.Exec(updateSQL, compressedData, save.CharID, save.IsNewCharacter)
|
||||
} else {
|
||||
_, err = s.server.db.Exec(updateSQL, compressedData, save.CharID, save.IsNewCharacter)
|
||||
}
|
||||
_, err = s.server.db.Exec(`UPDATE characters SET savedata=$1, is_new_character=$2, hrp=$3, gr=$4, is_female=$5, weapon_type=$6, weapon_id=$7 WHERE id=$8
|
||||
`, save.compSave, save.IsNewCharacter, save.HRP, save.GR, save.Gender, save.WeaponType, save.WeaponID, save.CharID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update savedata", zap.Error(err), zap.Uint32("charID", save.CharID))
|
||||
}
|
||||
|
||||
s.server.db.Exec(`UPDATE user_binary SET house_tier=$1, house_data=$2, bookshelf=$3, gallery=$4, tore=$5, garden=$6 WHERE id=$7
|
||||
`, save.HouseTier, save.HouseData, save.BookshelfData, save.GalleryData, save.ToreData, save.GardenData, s.charID)
|
||||
}
|
||||
|
||||
func (save *CharacterSaveData) Compress() error {
|
||||
var err error
|
||||
save.compSave, err = nullcomp.Compress(save.decompSave)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to save character data", zap.Error(err), zap.Uint32("charID", save.CharID))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (save *CharacterSaveData) CompressedBaseData(s *Session) ([]byte, error) {
|
||||
compressedData, err := nullcomp.Compress(save.baseSaveData)
|
||||
|
||||
func (save *CharacterSaveData) Decompress() error {
|
||||
var err error
|
||||
save.decompSave, err = nullcomp.Decompress(save.compSave)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to compress saveData", zap.Error(err), zap.Uint32("charID", save.CharID))
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
return compressedData, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (save *CharacterSaveData) BaseSaveData() []byte {
|
||||
return save.baseSaveData
|
||||
}
|
||||
|
||||
func (save *CharacterSaveData) SetBaseSaveData(data []byte) {
|
||||
save.baseSaveData = data
|
||||
// After setting the new save byte array, we can extract the values to update our struct
|
||||
// This will be useful when we save it back, we use the struct values to overwrite the saveData
|
||||
save.updateStructWithSaveData()
|
||||
}
|
||||
|
||||
// This will update the save struct with the values stored in the raw savedata arrays
|
||||
// This will update the character save with the values stored in the save struct
|
||||
func (save *CharacterSaveData) updateSaveDataWithStruct() {
|
||||
rpBytes := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(rpBytes, save.RP)
|
||||
copy(save.baseSaveData[CharacterSaveRPPointer:CharacterSaveRPPointer+2], rpBytes)
|
||||
copy(save.decompSave[pointerRP:pointerRP+2], rpBytes)
|
||||
copy(save.decompSave[pointerKQF:pointerKQF+8], save.KQF)
|
||||
}
|
||||
|
||||
// This will update the character save struct with the values stored in the raw savedata arrays
|
||||
// This will update the save struct with the values stored in the character save
|
||||
func (save *CharacterSaveData) updateStructWithSaveData() {
|
||||
save.RP = binary.LittleEndian.Uint16(save.baseSaveData[CharacterSaveRPPointer : CharacterSaveRPPointer+2])
|
||||
if save.decompSave[pointerGender] == 1 {
|
||||
save.Gender = true
|
||||
} else {
|
||||
save.Gender = false
|
||||
}
|
||||
save.RP = binary.LittleEndian.Uint16(save.decompSave[pointerRP : pointerRP+2])
|
||||
save.HouseTier = save.decompSave[pointerHouseTier : pointerHouseTier+5]
|
||||
save.HouseData = save.decompSave[pointerHouseData : pointerHouseData+195]
|
||||
save.BookshelfData = save.decompSave[pointerBookshelfData : pointerBookshelfData+5576]
|
||||
save.GalleryData = save.decompSave[pointerGalleryData : pointerGalleryData+1748]
|
||||
save.ToreData = save.decompSave[pointerToreData : pointerToreData+240]
|
||||
save.GardenData = save.decompSave[pointerGardenData : pointerGardenData+68]
|
||||
save.WeaponType = save.decompSave[pointerWeaponType]
|
||||
save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[pointerWeaponID : pointerWeaponID+2])
|
||||
save.HRP = binary.LittleEndian.Uint16(save.decompSave[pointerHRP : pointerHRP+2])
|
||||
save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[pointerGRP : pointerGRP+4]))
|
||||
save.KQF = save.decompSave[pointerKQF : pointerKQF+8]
|
||||
}
|
||||
|
||||
func handleMsgMhfSexChanger(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"fmt"
|
||||
@@ -26,7 +25,6 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
// Var to hold the decompressed savedata for updating the launcher response fields.
|
||||
var decompressedData []byte
|
||||
if pkt.SaveType == 1 {
|
||||
// Diff-based update.
|
||||
// diffs themselves are also potentially compressed
|
||||
@@ -36,77 +34,23 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
// Perform diff.
|
||||
s.logger.Info("Diffing...")
|
||||
characterSaveData.SetBaseSaveData(deltacomp.ApplyDataDiff(diff, characterSaveData.BaseSaveData()))
|
||||
characterSaveData.decompSave = deltacomp.ApplyDataDiff(diff, characterSaveData.decompSave)
|
||||
} else {
|
||||
dumpSaveData(s, pkt.RawDataPayload, "savedata")
|
||||
// Regular blob update.
|
||||
saveData, err := nullcomp.Decompress(pkt.RawDataPayload)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to decompress savedata from packet", zap.Error(err))
|
||||
}
|
||||
s.logger.Info("Updating save with blob")
|
||||
characterSaveData.SetBaseSaveData(saveData)
|
||||
characterSaveData.decompSave = saveData
|
||||
}
|
||||
characterSaveData.IsNewCharacter = false
|
||||
characterBaseSaveData := characterSaveData.BaseSaveData()
|
||||
// Make a copy for updating the launcher fields.
|
||||
decompressedData = make([]byte, len(characterBaseSaveData))
|
||||
copy(decompressedData, characterBaseSaveData)
|
||||
err = characterSaveData.Save(s, nil)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update savedata in db", zap.Error(err))
|
||||
}
|
||||
characterSaveData.Save(s)
|
||||
s.logger.Info("Wrote recompressed savedata back to DB.")
|
||||
dumpSaveData(s, pkt.RawDataPayload, "savedata")
|
||||
|
||||
_, err = s.server.db.Exec("UPDATE characters SET weapon_type=$1 WHERE id=$2", uint16(decompressedData[128789]), s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to character weapon type in db", zap.Error(err))
|
||||
}
|
||||
|
||||
s.myseries.houseTier = decompressedData[129900:129905] // 0x1FB6C + 5
|
||||
s.myseries.houseData = decompressedData[130561:130756] // 0x1FE01 + 195
|
||||
s.myseries.bookshelfData = decompressedData[139928:145504] // 0x22298 + 5576
|
||||
// Gallery data also exists at 0x21578, is this the contest submission?
|
||||
s.myseries.galleryData = decompressedData[140064:141812] // 0x22320 + 1748
|
||||
s.myseries.toreData = decompressedData[130228:130468] // 0x1FCB4 + 240
|
||||
s.myseries.gardenData = decompressedData[142424:142492] // 0x22C58 + 68
|
||||
|
||||
isFemale := decompressedData[81] // 0x51
|
||||
if isFemale == 1 {
|
||||
_, err = s.server.db.Exec("UPDATE characters SET is_female=true WHERE id=$1", s.charID)
|
||||
} else {
|
||||
_, err = s.server.db.Exec("UPDATE characters SET is_female=false WHERE id=$1", s.charID)
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to character gender in db", zap.Error(err))
|
||||
}
|
||||
|
||||
weaponId := binary.LittleEndian.Uint16(decompressedData[128522:128524]) // 0x1F60A
|
||||
_, err = s.server.db.Exec("UPDATE characters SET weapon_id=$1 WHERE id=$2", weaponId, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update character weapon id in db", zap.Error(err))
|
||||
}
|
||||
|
||||
hrp := binary.LittleEndian.Uint16(decompressedData[130550:130552]) // 0x1FDF6
|
||||
_, err = s.server.db.Exec("UPDATE characters SET hrp=$1 WHERE id=$2", hrp, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update character hrp in db", zap.Error(err))
|
||||
}
|
||||
|
||||
grp := binary.LittleEndian.Uint32(decompressedData[130556:130560]) // 0x1FDFC
|
||||
var gr uint16
|
||||
if grp > 0 {
|
||||
gr = grpToGR(grp)
|
||||
} else {
|
||||
gr = 0
|
||||
}
|
||||
_, err = s.server.db.Exec("UPDATE characters SET gr=$1 WHERE id=$2", gr, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update character gr in db", zap.Error(err))
|
||||
}
|
||||
|
||||
characterName := s.clientContext.StrConv.MustDecode(bfutil.UpToNull(decompressedData[88:100]))
|
||||
_, err = s.server.db.Exec("UPDATE characters SET name=$1 WHERE id=$2", characterName, s.charID)
|
||||
characterSaveData.Name = s.clientContext.StrConv.MustDecode(bfutil.UpToNull(characterSaveData.decompSave[88:100]))
|
||||
_, err = s.server.db.Exec("UPDATE characters SET name=$1 WHERE id=$2", characterSaveData.Name, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update character name in db", zap.Error(err))
|
||||
}
|
||||
@@ -275,8 +219,8 @@ func dumpSaveData(s *Session, data []byte, suffix string) {
|
||||
if !s.server.erupeConfig.DevModeOptions.SaveDumps.Enabled {
|
||||
return
|
||||
} else {
|
||||
dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d_%s", s.charID, s.Name))
|
||||
path := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d_%s", s.charID, s.Name), fmt.Sprintf("%d_%s_%s.bin", s.charID, s.Name, suffix))
|
||||
dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID))
|
||||
path := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID), fmt.Sprintf("%d_%s.bin", s.charID, suffix))
|
||||
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
os.Mkdir(dir, os.ModeDir)
|
||||
@@ -320,7 +264,8 @@ func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfSaveScenarioData(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveScenarioData)
|
||||
_, err := s.server.db.Exec("UPDATE characters SET scenariodata = $1 WHERE characters.id = $2", pkt.RawDataPayload, int(s.charID))
|
||||
dumpSaveData(s, pkt.RawDataPayload, "scenario")
|
||||
_, err := s.server.db.Exec("UPDATE characters SET scenariodata = $1 WHERE id = $2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update scenario data in db", zap.Error(err))
|
||||
}
|
||||
@@ -337,7 +282,7 @@ func handleMsgMhfLoadScenarioData(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadScenarioData)
|
||||
var scenarioData []byte
|
||||
bf := byteframe.NewByteFrame()
|
||||
err := s.server.db.QueryRow("SELECT scenariodata FROM characters WHERE characters.id = $1", int(s.charID)).Scan(&scenarioData)
|
||||
err := s.server.db.QueryRow("SELECT scenariodata FROM characters WHERE id = $1", s.charID).Scan(&scenarioData)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get scenario data contents in db", zap.Error(err))
|
||||
} else {
|
||||
|
||||
@@ -164,7 +164,7 @@ func (guild *Guild) Save(s *Session) error {
|
||||
|
||||
func (guild *Guild) CreateApplication(s *Session, charID uint32, applicationType GuildApplicationType, transaction *sql.Tx) error {
|
||||
|
||||
sql := `
|
||||
query := `
|
||||
INSERT INTO guild_applications (guild_id, character_id, actor_id, application_type)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
`
|
||||
@@ -172,9 +172,9 @@ func (guild *Guild) CreateApplication(s *Session, charID uint32, applicationType
|
||||
var err error
|
||||
|
||||
if transaction == nil {
|
||||
_, err = s.server.db.Exec(sql, guild.ID, charID, s.charID, applicationType)
|
||||
_, err = s.server.db.Exec(query, guild.ID, charID, s.charID, applicationType)
|
||||
} else {
|
||||
_, err = transaction.Exec(sql, guild.ID, charID, s.charID, applicationType)
|
||||
_, err = transaction.Exec(query, guild.ID, charID, s.charID, applicationType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -222,7 +222,7 @@ func (guild *Guild) Disband(s *Session) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec("UPDATE guild_alliances SET sub1_id=NULL WHERE sub1_id=$1", guild.ID)
|
||||
_, err = transaction.Exec("UPDATE guild_alliances SET sub1_id=sub2_id, sub2_id=NULL WHERE sub1_id=$1", guild.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to remove guild from alliance", zap.Error(err), zap.Uint32("guildID", guild.ID))
|
||||
@@ -634,11 +634,8 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
bf.WriteUint32(uint32(response))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
case mhfpacket.OPERATE_GUILD_RESIGN:
|
||||
guildMembers, err := GetGuildMembers(s, guild.ID, false)
|
||||
success := false
|
||||
if err == nil {
|
||||
sort.Slice(guildMembers[:], func(i, j int) bool {
|
||||
return guildMembers[i].OrderIndex < guildMembers[j].OrderIndex
|
||||
@@ -651,29 +648,17 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
guildMembers[0].Save(s)
|
||||
guildMembers[i].Save(s)
|
||||
bf.WriteUint32(guildMembers[i].CharID)
|
||||
success = true
|
||||
break
|
||||
}
|
||||
}
|
||||
guild.Save(s)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
if !success {
|
||||
bf.WriteUint32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
return
|
||||
case mhfpacket.OPERATE_GUILD_APPLY:
|
||||
err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil)
|
||||
|
||||
if err != nil {
|
||||
// All successful acks return 0x01, assuming 0x00 is failure
|
||||
bf.WriteUint32(0x00)
|
||||
} else {
|
||||
if err == nil {
|
||||
bf.WriteUint32(guild.LeaderCharID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
case mhfpacket.OPERATE_GUILD_LEAVE:
|
||||
var err error
|
||||
|
||||
@@ -698,10 +683,8 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
bf.WriteUint32(uint32(response))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
case mhfpacket.OPERATE_GUILD_DONATE_RANK:
|
||||
handleDonateRP(s, pkt, bf, guild, false)
|
||||
bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, false))
|
||||
case mhfpacket.OPERATE_GUILD_SET_APPLICATION_DENY:
|
||||
s.server.db.Exec("UPDATE guilds SET recruiting=false WHERE id=$1", guild.ID)
|
||||
case mhfpacket.OPERATE_GUILD_SET_APPLICATION_ALLOW:
|
||||
@@ -711,46 +694,51 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_FALSE:
|
||||
handleAvoidLeadershipUpdate(s, pkt, false)
|
||||
case mhfpacket.OPERATE_GUILD_UPDATE_COMMENT:
|
||||
pbf := byteframe.NewByteFrameFromBytes(pkt.UnkData)
|
||||
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
_ = pbf.ReadUint8() // len
|
||||
_ = pbf.ReadUint32()
|
||||
guild.Comment = stringsupport.SJISToUTF8(pbf.ReadNullTerminatedBytes())
|
||||
guild.Comment = stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())
|
||||
guild.Save(s)
|
||||
case mhfpacket.OPERATE_GUILD_UPDATE_MOTTO:
|
||||
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
guild.SubMotto = pkt.UnkData[3]
|
||||
guild.MainMotto = pkt.UnkData[4]
|
||||
_ = pkt.Data1.ReadUint16()
|
||||
guild.SubMotto = pkt.Data1.ReadUint8()
|
||||
guild.MainMotto = pkt.Data1.ReadUint8()
|
||||
guild.Save(s)
|
||||
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_1:
|
||||
handleRenamePugi(s, pkt.UnkData, guild, 1)
|
||||
handleRenamePugi(s, pkt.Data2, guild, 1)
|
||||
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_2:
|
||||
handleRenamePugi(s, pkt.UnkData, guild, 2)
|
||||
handleRenamePugi(s, pkt.Data2, guild, 2)
|
||||
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_3:
|
||||
handleRenamePugi(s, pkt.UnkData, guild, 3)
|
||||
handleRenamePugi(s, pkt.Data2, guild, 3)
|
||||
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_1:
|
||||
// TODO: decode guild poogie outfits
|
||||
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_2:
|
||||
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_3:
|
||||
case mhfpacket.OPERATE_GUILD_DONATE_EVENT:
|
||||
handleDonateRP(s, pkt, bf, guild, true)
|
||||
bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, true))
|
||||
case mhfpacket.OPERATE_GUILD_EVENT_EXCHANGE:
|
||||
rp := uint16(pkt.Data1.ReadUint32())
|
||||
saveData, _ := GetCharacterSaveData(s, s.charID)
|
||||
saveData.RP -= rp
|
||||
saveData.Save(s)
|
||||
bf.WriteUint32(uint32(saveData.RP))
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled operate guild action '%d'", pkt.Action))
|
||||
}
|
||||
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
if len(bf.Data()) > 0 {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
} else {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
}
|
||||
|
||||
func handleRenamePugi(s *Session, data []byte, guild *Guild, num int) {
|
||||
bf := byteframe.NewByteFrameFromBytes(data)
|
||||
_ = bf.ReadUint8() // len
|
||||
_ = bf.ReadUint32() // unk
|
||||
func handleRenamePugi(s *Session, bf *byteframe.ByteFrame, guild *Guild, num int) {
|
||||
name := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
|
||||
switch num {
|
||||
case 1:
|
||||
@@ -763,33 +751,23 @@ func handleRenamePugi(s *Session, data []byte, guild *Guild, num int) {
|
||||
guild.Save(s)
|
||||
}
|
||||
|
||||
func handleDonateRP(s *Session, pkt *mhfpacket.MsgMhfOperateGuild, bf *byteframe.ByteFrame, guild *Guild, isEvent bool) error {
|
||||
rp := binary.BigEndian.Uint16(pkt.UnkData[3:5])
|
||||
func handleDonateRP(s *Session, amount uint16, guild *Guild, isEvent bool) []byte {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(0)
|
||||
saveData, err := GetCharacterSaveData(s, s.charID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
saveData.RP -= rp
|
||||
transaction, err := s.server.db.Begin()
|
||||
err = saveData.Save(s, transaction)
|
||||
if err != nil {
|
||||
transaction.Rollback()
|
||||
return err
|
||||
return bf.Data()
|
||||
}
|
||||
saveData.RP -= amount
|
||||
saveData.Save(s)
|
||||
updateSQL := "UPDATE guilds SET rank_rp = rank_rp + $1 WHERE id = $2"
|
||||
if isEvent {
|
||||
updateSQL = "UPDATE guilds SET event_rp = event_rp + $1 WHERE id = $2"
|
||||
}
|
||||
_, err = s.server.db.Exec(updateSQL, rp, guild.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to donate rank RP to guild", zap.Error(err), zap.Uint32("guildID", guild.ID))
|
||||
transaction.Rollback()
|
||||
return err
|
||||
} else {
|
||||
transaction.Commit()
|
||||
}
|
||||
s.server.db.Exec(updateSQL, amount, guild.ID)
|
||||
bf.Seek(0, 0)
|
||||
bf.WriteUint32(uint32(saveData.RP))
|
||||
return nil
|
||||
return bf.Data()
|
||||
}
|
||||
|
||||
func handleAvoidLeadershipUpdate(s *Session, pkt *mhfpacket.MsgMhfOperateGuild, avoidLeadership bool) {
|
||||
@@ -1027,15 +1005,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
applicants, err := GetGuildMembers(s, guild.ID, true)
|
||||
|
||||
if err != nil {
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0) // Count
|
||||
resp.WriteUint8(0) // Unk, read if count == 0.
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
if err != nil || characterGuildData.IsApplicant {
|
||||
bf.WriteUint16(0)
|
||||
} else {
|
||||
bf.WriteUint16(uint16(len(applicants)))
|
||||
@@ -1049,7 +1019,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
}
|
||||
|
||||
bf.WriteUint16(0x0000)
|
||||
bf.WriteUint16(0x0000) // lenAllianceApplications
|
||||
|
||||
/*
|
||||
alliance application format
|
||||
@@ -1095,6 +1065,7 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateGuild)
|
||||
|
||||
var guilds []*Guild
|
||||
var alliances []*GuildAlliance
|
||||
var rows *sqlx.Rows
|
||||
var err error
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
@@ -1189,41 +1160,112 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME:
|
||||
//
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_NAME:
|
||||
//
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_ID:
|
||||
//
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ORDER_MEMBERS:
|
||||
//
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ORDER_REGISTRATION:
|
||||
//
|
||||
default:
|
||||
panic(fmt.Sprintf("no handler for guild search type '%d'", pkt.Type))
|
||||
}
|
||||
|
||||
if err != nil || guilds == nil {
|
||||
if pkt.Type > 8 {
|
||||
var tempAlliances []*GuildAlliance
|
||||
rows, err = s.server.db.Queryx(allianceInfoSelectQuery)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
alliance, _ := buildAllianceObjectFromDbResult(rows, err, s)
|
||||
tempAlliances = append(tempAlliances, alliance)
|
||||
}
|
||||
}
|
||||
switch pkt.Type {
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME:
|
||||
bf.ReadBytes(10)
|
||||
searchTerm := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
|
||||
for _, alliance := range tempAlliances {
|
||||
if strings.Contains(alliance.Name, searchTerm) {
|
||||
alliances = append(alliances, alliance)
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_NAME:
|
||||
bf.ReadBytes(10)
|
||||
searchTerm := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
|
||||
for _, alliance := range tempAlliances {
|
||||
if strings.Contains(alliance.ParentGuild.LeaderName, searchTerm) {
|
||||
alliances = append(alliances, alliance)
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_ID:
|
||||
bf.ReadBytes(2)
|
||||
ID := bf.ReadUint32()
|
||||
for _, alliance := range tempAlliances {
|
||||
if alliance.ParentGuild.LeaderCharID == ID {
|
||||
alliances = append(alliances, alliance)
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ORDER_MEMBERS:
|
||||
sorting := bf.ReadBool()
|
||||
if sorting {
|
||||
sort.Slice(tempAlliances, func(i, j int) bool {
|
||||
return tempAlliances[i].TotalMembers > tempAlliances[j].TotalMembers
|
||||
})
|
||||
} else {
|
||||
sort.Slice(tempAlliances, func(i, j int) bool {
|
||||
return tempAlliances[i].TotalMembers < tempAlliances[j].TotalMembers
|
||||
})
|
||||
}
|
||||
alliances = tempAlliances
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ORDER_REGISTRATION:
|
||||
sorting := bf.ReadBool()
|
||||
if sorting {
|
||||
sort.Slice(tempAlliances, func(i, j int) bool {
|
||||
return tempAlliances[i].CreatedAt.Unix() > tempAlliances[j].CreatedAt.Unix()
|
||||
})
|
||||
} else {
|
||||
sort.Slice(tempAlliances, func(i, j int) bool {
|
||||
return tempAlliances[i].CreatedAt.Unix() < tempAlliances[j].CreatedAt.Unix()
|
||||
})
|
||||
}
|
||||
alliances = tempAlliances
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil || (guilds == nil && alliances == nil) {
|
||||
stubEnumerateNoResults(s, pkt.AckHandle)
|
||||
return
|
||||
}
|
||||
|
||||
bf = byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(len(guilds)))
|
||||
|
||||
bf.WriteUint8(0x01) // Unk
|
||||
|
||||
for _, guild := range guilds {
|
||||
bf.WriteUint32(guild.ID)
|
||||
bf.WriteUint32(guild.LeaderCharID)
|
||||
bf.WriteUint16(guild.MemberCount)
|
||||
bf.WriteUint16(0x0000) // Unk
|
||||
bf.WriteUint16(guild.Rank) // OR guilds in alliance
|
||||
bf.WriteUint32(uint32(guild.CreatedAt.Unix()))
|
||||
ps.Uint8(bf, guild.Name, true)
|
||||
ps.Uint8(bf, guild.LeaderName, true)
|
||||
if pkt.Type > 8 {
|
||||
bf.WriteUint16(uint16(len(alliances)))
|
||||
bf.WriteUint8(0x00) // Unk
|
||||
for _, alliance := range alliances {
|
||||
bf.WriteUint32(alliance.ID)
|
||||
bf.WriteUint32(alliance.ParentGuild.LeaderCharID)
|
||||
bf.WriteUint16(alliance.TotalMembers)
|
||||
bf.WriteUint16(0x0000)
|
||||
if alliance.SubGuild1ID == 0 && alliance.SubGuild2ID == 0 {
|
||||
bf.WriteUint16(1)
|
||||
} else if alliance.SubGuild1ID > 0 && alliance.SubGuild2ID == 0 || alliance.SubGuild1ID == 0 && alliance.SubGuild2ID > 0 {
|
||||
bf.WriteUint16(2)
|
||||
} else {
|
||||
bf.WriteUint16(3)
|
||||
}
|
||||
bf.WriteUint32(uint32(alliance.CreatedAt.Unix()))
|
||||
ps.Uint8(bf, alliance.Name, true)
|
||||
ps.Uint8(bf, alliance.ParentGuild.LeaderName, true)
|
||||
bf.WriteUint8(0x01) // Unk
|
||||
bf.WriteBool(true) // TODO: Enable GuildAlliance applications
|
||||
}
|
||||
} else {
|
||||
bf.WriteUint8(0x01) // Unk
|
||||
bf.WriteBool(!guild.Recruiting)
|
||||
bf.WriteUint16(uint16(len(guilds)))
|
||||
for _, guild := range guilds {
|
||||
bf.WriteUint32(guild.ID)
|
||||
bf.WriteUint32(guild.LeaderCharID)
|
||||
bf.WriteUint16(guild.MemberCount)
|
||||
bf.WriteUint16(0x0000) // Unk
|
||||
bf.WriteUint16(guild.Rank) // OR guilds in alliance
|
||||
bf.WriteUint32(uint32(guild.CreatedAt.Unix()))
|
||||
ps.Uint8(bf, guild.Name, true)
|
||||
ps.Uint8(bf, guild.LeaderName, true)
|
||||
bf.WriteUint8(0x01) // Unk
|
||||
bf.WriteBool(!guild.Recruiting)
|
||||
}
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
@@ -1712,9 +1754,11 @@ func handleMsgMhfGetGuildWeeklyBonusMaster(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
func handleMsgMhfGetGuildWeeklyBonusActiveCount(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildWeeklyBonusActiveCount)
|
||||
|
||||
// Values taken from brand new guild capture
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x03))
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(0x3C) // Active count
|
||||
bf.WriteUint8(0x3C) // Current active count
|
||||
bf.WriteUint8(0x00) // New active count
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGuildHuntdata(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -139,8 +141,15 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
case mhfpacket.OPERATE_JOINT_LEAVE:
|
||||
if guild.LeaderCharID == s.charID {
|
||||
// delete alliance application
|
||||
// or leave alliance
|
||||
if guild.ID == alliance.SubGuild1ID && alliance.SubGuild2ID > 0 {
|
||||
s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = sub2_id, sub2_id = NULL WHERE id = $1`, alliance.ID)
|
||||
} else if guild.ID == alliance.SubGuild1ID && alliance.SubGuild2ID == 0 {
|
||||
s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = NULL WHERE id = $1`, alliance.ID)
|
||||
} else {
|
||||
s.server.db.Exec(`UPDATE guild_alliances SET sub2_id = NULL WHERE id = $1`, alliance.ID)
|
||||
}
|
||||
// TODO: Handle deleting Alliance applications
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
s.logger.Warn(
|
||||
"Non-owner of guild attempted alliance leave",
|
||||
@@ -148,10 +157,75 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
)
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
case mhfpacket.OPERATE_JOINT_KICK:
|
||||
if alliance.ParentGuild.LeaderCharID == s.charID {
|
||||
_ = pkt.UnkData.ReadUint8()
|
||||
kickedGuildID := pkt.UnkData.ReadUint32()
|
||||
if kickedGuildID == alliance.SubGuild1ID && alliance.SubGuild2ID > 0 {
|
||||
s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = sub2_id, sub2_id = NULL WHERE id = $1`, alliance.ID)
|
||||
} else if kickedGuildID == alliance.SubGuild1ID && alliance.SubGuild2ID == 0 {
|
||||
s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = NULL WHERE id = $1`, alliance.ID)
|
||||
} else {
|
||||
s.server.db.Exec(`UPDATE guild_alliances SET sub2_id = NULL WHERE id = $1`, alliance.ID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
s.logger.Warn(
|
||||
"Non-owner of alliance attempted kick",
|
||||
zap.Uint32("CharID", s.charID),
|
||||
zap.Uint32("AllyID", alliance.ID),
|
||||
)
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Unhandled operate joint action '%d'", pkt.Action))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
panic(fmt.Sprintf("Unhandled operate joint action '%d'", pkt.Action))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfInfoJoint(s *Session, p mhfpacket.MHFPacket) {}
|
||||
func handleMsgMhfInfoJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfInfoJoint)
|
||||
bf := byteframe.NewByteFrame()
|
||||
alliance, err := GetAllianceData(s, pkt.AllianceID)
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
bf.WriteUint32(alliance.ID)
|
||||
bf.WriteUint32(uint32(alliance.CreatedAt.Unix()))
|
||||
bf.WriteUint16(alliance.TotalMembers)
|
||||
bf.WriteUint16(0x0000) // Unk
|
||||
ps.Uint16(bf, alliance.Name, true)
|
||||
if alliance.SubGuild1ID > 0 {
|
||||
if alliance.SubGuild2ID > 0 {
|
||||
bf.WriteUint8(3)
|
||||
} else {
|
||||
bf.WriteUint8(2)
|
||||
}
|
||||
} else {
|
||||
bf.WriteUint8(1)
|
||||
}
|
||||
bf.WriteUint32(alliance.ParentGuildID)
|
||||
bf.WriteUint32(alliance.ParentGuild.LeaderCharID)
|
||||
bf.WriteUint16(alliance.ParentGuild.Rank)
|
||||
bf.WriteUint16(alliance.ParentGuild.MemberCount)
|
||||
ps.Uint16(bf, alliance.ParentGuild.Name, true)
|
||||
ps.Uint16(bf, alliance.ParentGuild.LeaderName, true)
|
||||
if alliance.SubGuild1ID > 0 {
|
||||
bf.WriteUint32(alliance.SubGuild1ID)
|
||||
bf.WriteUint32(alliance.SubGuild1.LeaderCharID)
|
||||
bf.WriteUint16(alliance.SubGuild1.Rank)
|
||||
bf.WriteUint16(alliance.SubGuild1.MemberCount)
|
||||
ps.Uint16(bf, alliance.SubGuild1.Name, true)
|
||||
ps.Uint16(bf, alliance.SubGuild1.LeaderName, true)
|
||||
}
|
||||
if alliance.SubGuild2ID > 0 {
|
||||
bf.WriteUint32(alliance.SubGuild2ID)
|
||||
bf.WriteUint32(alliance.SubGuild2.LeaderCharID)
|
||||
bf.WriteUint16(alliance.SubGuild2.Rank)
|
||||
bf.WriteUint16(alliance.SubGuild2.MemberCount)
|
||||
ps.Uint16(bf, alliance.SubGuild2.Name, true)
|
||||
ps.Uint16(bf, alliance.SubGuild2.LeaderName, true)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,24 +38,26 @@ FROM warehouse
|
||||
|
||||
func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateInterior)
|
||||
_, err := s.server.db.Exec("UPDATE characters SET house=$1 WHERE id=$2", pkt.InteriorData, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.server.db.Exec(`UPDATE user_binary SET house_furniture=$1 WHERE id=$2`, pkt.InteriorData, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
type HouseData struct {
|
||||
CharID uint32 `db:"id"`
|
||||
HRP uint16 `db:"hrp"`
|
||||
GR uint16 `db:"gr"`
|
||||
Name string `db:"name"`
|
||||
CharID uint32 `db:"id"`
|
||||
HRP uint16 `db:"hrp"`
|
||||
GR uint16 `db:"gr"`
|
||||
Name string `db:"name"`
|
||||
HouseState uint8 `db:"house_state"`
|
||||
HousePassword string `db:"house_password"`
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateHouse)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(0)
|
||||
var houses []HouseData
|
||||
houseQuery := `SELECT c.id, hrp, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
|
||||
FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE c.id=$1`
|
||||
switch pkt.Method {
|
||||
case 1:
|
||||
var friendsList string
|
||||
@@ -63,17 +65,15 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
cids := stringsupport.CSVElems(friendsList)
|
||||
for _, cid := range cids {
|
||||
house := HouseData{}
|
||||
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE id=$1", cid)
|
||||
row := s.server.db.QueryRowx(houseQuery, cid)
|
||||
err := row.StructScan(&house)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
if err == nil {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil {
|
||||
if err != nil || guild == nil {
|
||||
break
|
||||
}
|
||||
guildMembers, err := GetGuildMembers(s, guild.ID, false)
|
||||
@@ -82,58 +82,48 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
for _, member := range guildMembers {
|
||||
house := HouseData{}
|
||||
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE id=$1", member.CharID)
|
||||
err := row.StructScan(&house)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
row := s.server.db.QueryRowx(houseQuery, member.CharID)
|
||||
err = row.StructScan(&house)
|
||||
if err == nil {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
houseQuery = `SELECT c.id, hrp, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
|
||||
FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE name ILIKE $1`
|
||||
house := HouseData{}
|
||||
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE name ILIKE $1", fmt.Sprintf(`%%%s%%`, pkt.Name))
|
||||
err := row.StructScan(&house)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
houses = append(houses, house)
|
||||
rows, _ := s.server.db.Queryx(houseQuery, fmt.Sprintf(`%%%s%%`, pkt.Name))
|
||||
for rows.Next() {
|
||||
err := rows.StructScan(&house)
|
||||
if err == nil {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
house := HouseData{}
|
||||
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE id=$1", pkt.CharID)
|
||||
row := s.server.db.QueryRowx(houseQuery, pkt.CharID)
|
||||
err := row.StructScan(&house)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
if err == nil {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
case 5: // Recent visitors
|
||||
break
|
||||
}
|
||||
var exists int
|
||||
for _, house := range houses {
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == house.CharID {
|
||||
exists++
|
||||
bf.WriteUint32(house.CharID)
|
||||
bf.WriteUint8(session.myseries.state)
|
||||
if len(session.myseries.password) > 0 {
|
||||
bf.WriteUint8(3)
|
||||
} else {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
bf.WriteUint16(house.HRP)
|
||||
bf.WriteUint16(house.GR)
|
||||
ps.Uint8(bf, house.Name, true)
|
||||
break
|
||||
}
|
||||
bf.WriteUint32(house.CharID)
|
||||
bf.WriteUint8(house.HouseState)
|
||||
if len(house.HousePassword) > 0 {
|
||||
bf.WriteUint8(3)
|
||||
} else {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
bf.WriteUint16(house.HRP)
|
||||
bf.WriteUint16(house.GR)
|
||||
ps.Uint8(bf, house.Name, true)
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint16(uint16(exists))
|
||||
resp.WriteBytes(bf.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
bf.Seek(0, 0)
|
||||
bf.WriteUint16(uint16(len(houses)))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -143,72 +133,89 @@ func handleMsgMhfUpdateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
// 03 = open friends
|
||||
// 04 = open guild
|
||||
// 05 = open friends+guild
|
||||
s.myseries.state = pkt.State
|
||||
s.myseries.password = pkt.Password
|
||||
s.server.db.Exec(`UPDATE user_binary SET house_state=$1, house_password=$2 WHERE id=$3`, pkt.State, pkt.Password, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadHouse)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
var state uint8
|
||||
var password string
|
||||
s.server.db.QueryRow(`SELECT COALESCE(house_state, 2) as house_state, COALESCE(house_password, '') as house_password FROM user_binary WHERE id=$1
|
||||
`, pkt.CharID).Scan(&state, &password)
|
||||
|
||||
if pkt.Destination != 9 && len(pkt.Password) > 0 && pkt.CheckPass {
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == pkt.CharID && pkt.Password != session.myseries.password {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
if pkt.Password != password {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var furniture []byte
|
||||
err := s.server.db.QueryRow("SELECT house FROM characters WHERE id=$1", pkt.CharID).Scan(&furniture)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if pkt.Destination != 9 && state > 2 {
|
||||
allowed := false
|
||||
|
||||
// Friends list verification
|
||||
if state == 3 || state == 5 {
|
||||
var friendsList string
|
||||
s.server.db.QueryRow(`SELECT friends FROM characters WHERE id=$1`, pkt.CharID).Scan(&friendsList)
|
||||
cids := stringsupport.CSVElems(friendsList)
|
||||
for _, cid := range cids {
|
||||
if uint32(cid) == s.charID {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Guild verification
|
||||
if state > 3 {
|
||||
ownGuild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
isApplicant, _ := ownGuild.HasApplicationForCharID(s, s.charID)
|
||||
if err == nil && ownGuild != nil {
|
||||
othersGuild, err := GetGuildInfoByCharacterId(s, pkt.CharID)
|
||||
if err == nil && othersGuild != nil {
|
||||
if othersGuild.ID == ownGuild.ID && !isApplicant {
|
||||
allowed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
}
|
||||
if furniture == nil {
|
||||
furniture = make([]byte, 20)
|
||||
|
||||
var houseTier, houseData, houseFurniture, bookshelf, gallery, tore, garden []byte
|
||||
s.server.db.QueryRow(`SELECT house_tier, house_data, house_furniture, bookshelf, gallery, tore, garden FROM user_binary WHERE id=$1
|
||||
`, pkt.CharID).Scan(&houseTier, &houseData, &houseFurniture, &bookshelf, &gallery, &tore, &garden)
|
||||
if houseFurniture == nil {
|
||||
houseFurniture = make([]byte, 20)
|
||||
}
|
||||
|
||||
switch pkt.Destination {
|
||||
case 3: // Others house
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == pkt.CharID {
|
||||
bf.WriteBytes(session.myseries.houseTier)
|
||||
bf.WriteBytes(session.myseries.houseData)
|
||||
bf.WriteBytes(make([]byte, 19)) // Padding?
|
||||
bf.WriteBytes(furniture)
|
||||
}
|
||||
}
|
||||
bf.WriteBytes(houseTier)
|
||||
bf.WriteBytes(houseData)
|
||||
bf.WriteBytes(make([]byte, 19)) // Padding?
|
||||
bf.WriteBytes(houseFurniture)
|
||||
case 4: // Bookshelf
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == pkt.CharID {
|
||||
bf.WriteBytes(session.myseries.bookshelfData)
|
||||
}
|
||||
}
|
||||
bf.WriteBytes(bookshelf)
|
||||
case 5: // Gallery
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == pkt.CharID {
|
||||
bf.WriteBytes(session.myseries.galleryData)
|
||||
}
|
||||
}
|
||||
bf.WriteBytes(gallery)
|
||||
case 8: // Tore
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == pkt.CharID {
|
||||
bf.WriteBytes(session.myseries.toreData)
|
||||
}
|
||||
}
|
||||
bf.WriteBytes(tore)
|
||||
case 9: // Own house
|
||||
bf.WriteBytes(furniture)
|
||||
bf.WriteBytes(houseFurniture)
|
||||
case 10: // Garden
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == pkt.CharID {
|
||||
bf.WriteBytes(session.myseries.gardenData)
|
||||
c, d := getGookData(s, pkt.CharID)
|
||||
bf.WriteUint16(c)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteBytes(d)
|
||||
}
|
||||
}
|
||||
bf.WriteBytes(garden)
|
||||
c, d := getGookData(s, pkt.CharID)
|
||||
bf.WriteUint16(c)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteBytes(d)
|
||||
}
|
||||
if len(bf.Data()) == 0 {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -219,26 +226,18 @@ func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfGetMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetMyhouseInfo)
|
||||
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT trophy FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.server.db.QueryRow(`SELECT mission FROM user_binary WHERE id=$1`, s.charID).Scan(&data)
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 9))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateMyhouseInfo)
|
||||
|
||||
_, err := s.server.db.Exec("UPDATE characters SET trophy=$1 WHERE id=$2", pkt.Unk0, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.server.db.Exec("UPDATE user_binary SET mission=$1 WHERE id=$2", pkt.Unk0, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
@@ -311,6 +310,7 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
loadData[1] = savedSets // update set count
|
||||
}
|
||||
dumpSaveData(s, loadData, "decomyset")
|
||||
_, err := s.server.db.Exec("UPDATE characters SET decomyset=$1 WHERE id=$2", loadData, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update decomyset savedata in db", zap.Error(err))
|
||||
|
||||
@@ -104,6 +104,7 @@ func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
s.logger.Info("Wrote recompressed hunternavi back to DB.")
|
||||
} else {
|
||||
dumpSaveData(s, pkt.RawDataPayload, "hunternavi")
|
||||
// simply update database, no extra processing
|
||||
_, err := s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
@@ -162,6 +163,7 @@ func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveMercenary)
|
||||
dumpSaveData(s, pkt.MercData, "mercenary")
|
||||
if len(pkt.MercData) > 0 {
|
||||
s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", pkt.MercData, s.charID)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func handleMsgMhfLoadPlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSavePlateData)
|
||||
|
||||
dumpSaveData(s, pkt.RawDataPayload, "_platedata")
|
||||
dumpSaveData(s, pkt.RawDataPayload, "platedata")
|
||||
|
||||
if pkt.IsDataDiff {
|
||||
var data []byte
|
||||
@@ -90,7 +90,7 @@ func handleMsgMhfLoadPlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSavePlateBox)
|
||||
|
||||
dumpSaveData(s, pkt.RawDataPayload, "_platebox")
|
||||
dumpSaveData(s, pkt.RawDataPayload, "platebox")
|
||||
|
||||
if pkt.IsDataDiff {
|
||||
var data []byte
|
||||
@@ -156,7 +156,7 @@ func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSavePlateMyset)
|
||||
// looks to always return the full thing, simply update database, no extra processing
|
||||
|
||||
dumpSaveData(s, pkt.RawDataPayload, "platemyset")
|
||||
_, err := s.server.db.Exec("UPDATE characters SET platemyset=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platemyset savedata in db", zap.Error(err))
|
||||
|
||||
@@ -54,6 +54,7 @@ func handleMsgMhfLoadFavoriteQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfSaveFavoriteQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveFavoriteQuest)
|
||||
dumpSaveData(s, pkt.Data, "favquest")
|
||||
s.server.db.Exec("UPDATE characters SET savefavoritequest=$1 WHERE id=$2", pkt.Data, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
||||
// saved every floor on road, holds values such as floors progressed, points etc.
|
||||
// can be safely handled by the client
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveRengokuData)
|
||||
dumpSaveData(s, pkt.RawDataPayload, "rengoku")
|
||||
_, err := s.server.db.Exec("UPDATE characters SET rengokudata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update rengokudata savedata in db", zap.Error(err))
|
||||
@@ -270,8 +271,20 @@ func handleMsgMhfEnumerateRengokuRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteBytes(make([]byte, 11))
|
||||
}
|
||||
}
|
||||
bf.WriteUint8(uint8(i) - 1)
|
||||
bf.WriteBytes(scoreData.Data())
|
||||
if i == 1 {
|
||||
bf.WriteUint32(1)
|
||||
bf.WriteUint32(0)
|
||||
ps.Uint8(bf, s.Name, true)
|
||||
ps.Uint8(bf, "", false)
|
||||
bf.WriteUint8(1)
|
||||
bf.WriteUint32(1)
|
||||
bf.WriteUint32(0)
|
||||
ps.Uint8(bf, s.Name, true)
|
||||
ps.Uint8(bf, "", false)
|
||||
} else {
|
||||
bf.WriteUint8(uint8(i) - 1)
|
||||
bf.WriteBytes(scoreData.Data())
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
type:
|
||||
1 == TOWER_RANK_POINT,
|
||||
2 == GET_OWN_TOWER_SKILL
|
||||
3 == ?
|
||||
3 == GET_OWN_TOWER_LEVEL_V3
|
||||
4 == TOWER_TOUHA_HISTORY
|
||||
5 = ?
|
||||
|
||||
@@ -39,8 +39,8 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
case mhfpacket.TowerInfoTypeGetOwnTowerSkill:
|
||||
//data, err = hex.DecodeString("0A218EAD000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
data, err = hex.DecodeString("0A218EAD0000000000000000000000010000001C0000000500050000000000020000000000000000000000000000000000030003000000000003000500050000000300030003000300030003000200030001000300020002000300010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
case mhfpacket.TowerInfoTypeUnk3:
|
||||
panic("No known response values for TowerInfoTypeUnk3")
|
||||
case mhfpacket.TowerInfoTypeGetOwnTowerLevelV3:
|
||||
panic("No known response values for GetOwnTowerLevelV3")
|
||||
case mhfpacket.TowerInfoTypeTowerTouhaHistory:
|
||||
data, err = hex.DecodeString("0A218EAD0000000000000000000000010000000000000000000000000000000000000000")
|
||||
case mhfpacket.TowerInfoTypeUnk5:
|
||||
@@ -58,6 +58,9 @@ func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {}
|
||||
func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGemInfo)
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 8))
|
||||
}
|
||||
|
||||
func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
@@ -17,12 +17,12 @@ func handleMsgSysSetUserBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.server.userBinaryPartsLock.Unlock()
|
||||
|
||||
var exists []byte
|
||||
err := s.server.db.QueryRow("SELECT type2 FROM user_binaries WHERE id=$1", s.charID).Scan(&exists)
|
||||
err := s.server.db.QueryRow("SELECT type2 FROM user_binary WHERE id=$1", s.charID).Scan(&exists)
|
||||
if err != nil {
|
||||
s.server.db.Exec("INSERT INTO user_binaries (id) VALUES ($1)", s.charID)
|
||||
s.server.db.Exec("INSERT INTO user_binary (id) VALUES ($1)", s.charID)
|
||||
}
|
||||
|
||||
s.server.db.Exec(fmt.Sprintf("UPDATE user_binaries SET type%d=$1 WHERE id=$2", pkt.BinaryType), pkt.RawDataPayload, s.charID)
|
||||
s.server.db.Exec(fmt.Sprintf("UPDATE user_binary SET type%d=$1 WHERE id=$2", pkt.BinaryType), pkt.RawDataPayload, s.charID)
|
||||
|
||||
msg := &mhfpacket.MsgSysNotifyUserBinary{
|
||||
CharID: s.charID,
|
||||
@@ -42,7 +42,7 @@ func handleMsgSysGetUserBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
// If we can't get the real data, try to get it from the database.
|
||||
if !ok {
|
||||
err := s.server.db.QueryRow(fmt.Sprintf("SELECT type%d FROM user_binaries WHERE id=$1", pkt.BinaryType), pkt.CharID).Scan(&data)
|
||||
err := s.server.db.QueryRow(fmt.Sprintf("SELECT type%d FROM user_binary WHERE id=$1", pkt.BinaryType), pkt.CharID).Scan(&data)
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
|
||||
@@ -39,6 +39,8 @@ type Session struct {
|
||||
sessionStart int64
|
||||
rights uint32
|
||||
token string
|
||||
kqf []byte
|
||||
kqfOverride bool
|
||||
|
||||
semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet.
|
||||
|
||||
|
||||
@@ -112,14 +112,22 @@ func (s *Session) makeSignInResp(uid int) []byte {
|
||||
bf.WriteUint32(s.server.getLastCID(uid))
|
||||
bf.WriteUint32(s.server.getUserRights(uid))
|
||||
ps.Uint16(bf, "", false) // filters
|
||||
bf.WriteUint32(0xCA104E20)
|
||||
ps.Uint16(bf, "", false) // encryption
|
||||
bf.WriteUint16(0xCA10)
|
||||
bf.WriteUint16(0x4E20)
|
||||
ps.Uint16(bf, "", false) // unk key
|
||||
bf.WriteUint8(0x00)
|
||||
bf.WriteUint32(0xCA110001)
|
||||
bf.WriteUint32(0x4E200000)
|
||||
bf.WriteUint32(uint32(returnExpiry.Unix()))
|
||||
bf.WriteUint16(0xCA11)
|
||||
bf.WriteUint16(0x0001)
|
||||
bf.WriteUint16(0x4E20)
|
||||
ps.Uint16(bf, "", false) // unk ipv4
|
||||
if returnExpiry.Before(time.Now()) {
|
||||
// Hack to make Return work while having a non-adjusted expiry
|
||||
bf.WriteUint32(0)
|
||||
} else {
|
||||
bf.WriteUint32(uint32(returnExpiry.Unix()))
|
||||
}
|
||||
bf.WriteUint32(0x00000000)
|
||||
bf.WriteUint32(0x0A5197DF)
|
||||
bf.WriteUint32(0x0A5197DF) // unk id
|
||||
|
||||
mezfes := s.server.erupeConfig.DevModeOptions.MezFesEvent
|
||||
alt := s.server.erupeConfig.DevModeOptions.MezFesAlt
|
||||
|
||||
Reference in New Issue
Block a user