Raviente Patch

This commit is contained in:
wishu
2022-05-04 04:38:10 +10:00
parent 50d46b47c8
commit 4faf8cf9b1
44 changed files with 3375 additions and 595 deletions

View File

@@ -11,9 +11,20 @@ import (
// MsgMhfAcquireUdItem represents the MSG_MHF_ACQUIRE_UD_ITEM
type MsgMhfAcquireUdItem struct {
AckHandle uint32
// Valid field size(s), not sure about the types.
Unk0 uint8
Unk1 uint32
// from gal
// daily = 0
// personal = 1
// personal rank = 2
// guild rank = 3
// gcp = 4
// from cat
// treasure achievement = 5
// personal achievement = 6
// guild achievement = 7
RewardType uint8
Unk2 uint8 // Number of uint32s to read?
Unk3 []byte
}
// Opcode returns the ID associated with this packet type.
@@ -25,9 +36,12 @@ func (m *MsgMhfAcquireUdItem) Opcode() network.PacketID {
func (m *MsgMhfAcquireUdItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8()
m.Unk1 = bf.ReadUint32()
m.RewardType = bf.ReadUint8()
m.Unk2 = bf.ReadUint8()
for i := uint8(0); i < m.Unk2; i++ {
bf.ReadUint32()
}
return nil
//return errors.New("NOT IMPLEMENTED")
}
// Build builds a binary packet from the current data.

View File

@@ -9,7 +9,10 @@ import (
)
// MsgMhfAddGuildWeeklyBonusExceptionalUser represents the MSG_MHF_ADD_GUILD_WEEKLY_BONUS_EXCEPTIONAL_USER
type MsgMhfAddGuildWeeklyBonusExceptionalUser struct{}
type MsgMhfAddGuildWeeklyBonusExceptionalUser struct {
AckHandle uint32
NumUsers uint8
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfAddGuildWeeklyBonusExceptionalUser) Opcode() network.PacketID {
@@ -18,7 +21,9 @@ func (m *MsgMhfAddGuildWeeklyBonusExceptionalUser) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfAddGuildWeeklyBonusExceptionalUser) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
m.NumUsers = bf.ReadUint8()
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -11,22 +11,25 @@ import (
type EnumerateGuildType uint8
const (
_ = iota
ENUMERATE_GUILD_TYPE_NAME
//Numbers correspond to order in guild search menu
ENUMERATE_GUILD_TYPE_6
ENUMERATE_GUILD_TYPE_LEADER_ID
ENUMERATE_GUILD_TYPE_3
ENUMERATE_GUILD_TYPE_2
ENUMERATE_GUILD_TYPE_7
ENUMERATE_GUILD_TYPE_8
ENUMERATE_GUILD_TYPE_NEW
ENUMERATE_GUILD_TYPE_GUILD_NAME = 0x01
ENUMERATE_GUILD_TYPE_LEADER_NAME = 0x02
ENUMERATE_GUILD_TYPE_LEADER_ID = 0x03
ENUMERATE_GUILD_TYPE_ORDER_MEMBERS = 0x04
ENUMERATE_GUILD_TYPE_ORDER_REGISTRATION = 0x05
ENUMERATE_GUILD_TYPE_ORDER_RANK = 0x06
ENUMERATE_GUILD_TYPE_MOTTO = 0x07
ENUMERATE_GUILD_TYPE_RECRUITING = 0x08
ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME = 0x09
ENUMERATE_ALLIANCE_TYPE_LEADER_NAME = 0x0A
ENUMERATE_ALLIANCE_TYPE_LEADER_ID = 0x0B
ENUMERATE_ALLIANCE_TYPE_ORDER_MEMBERS = 0x0C
ENUMERATE_ALLIANCE_TYPE_ORDER_REGISTRATION = 0x0D
)
// MsgMhfEnumerateGuild represents the MSG_MHF_ENUMERATE_GUILD
type MsgMhfEnumerateGuild struct {
AckHandle uint32
Type uint8
Type EnumerateGuildType
RawDataPayload []byte
}
@@ -38,9 +41,9 @@ func (m *MsgMhfEnumerateGuild) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfEnumerateGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Type = bf.ReadUint8()
m.Type = EnumerateGuildType(bf.ReadUint8())
m.RawDataPayload = bf.DataFromCurrent()
bf.Seek(int64(len(bf.Data()) - 2), 0)
return nil
}

View File

@@ -11,6 +11,8 @@ import (
// MsgMhfEnumerateGuildItem represents the MSG_MHF_ENUMERATE_GUILD_ITEM
type MsgMhfEnumerateGuildItem struct {
AckHandle uint32
GuildId uint32
Unk0 uint16
}
// Opcode returns the ID associated with this packet type.
@@ -21,7 +23,8 @@ func (m *MsgMhfEnumerateGuildItem) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfEnumerateGuildItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.GuildId = bf.ReadUint32()
m.Unk0 = bf.ReadUint16()
return nil
}

View File

@@ -0,0 +1,37 @@
package mhfpacket
import (
"errors"
"github.com/Solenataris/Erupe/network/clientctx"
"github.com/Solenataris/Erupe/network"
"github.com/Andoryuuta/byteframe"
)
// MsgMhfEnumerateGuildMessageBoard represents the MSG_MHF_ENUMERATE_GUILD_MESSAGE_BOARD
type MsgMhfEnumerateGuildMessageBoard struct{
AckHandle uint32
Unk0 uint32
MaxPosts uint32 // always 100, even on news (00000064)
// returning more than 4 news posts WILL softlock
BoardType uint32 // 0 => message, 1 => news
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfEnumerateGuildMessageBoard) Opcode() network.PacketID {
return network.MSG_MHF_ENUMERATE_GUILD_MESSAGE_BOARD
}
// Parse parses the packet from binary
func (m *MsgMhfEnumerateGuildMessageBoard) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint32()
m.MaxPosts = bf.ReadUint32()
m.BoardType = bf.ReadUint32()
return nil
}
// Build builds a binary packet from the current data.
func (m *MsgMhfEnumerateGuildMessageBoard) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
}

View File

@@ -9,7 +9,10 @@ import (
)
// MsgMhfEnumerateUnionItem represents the MSG_MHF_ENUMERATE_UNION_ITEM
type MsgMhfEnumerateUnionItem struct{}
type MsgMhfEnumerateUnionItem struct {
AckHandle uint32
Unk0 uint16
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfEnumerateUnionItem) Opcode() network.PacketID {
@@ -18,7 +21,10 @@ func (m *MsgMhfEnumerateUnionItem) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfEnumerateUnionItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint16()
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -11,9 +11,8 @@ import (
// MsgMhfGetAchievement represents the MSG_MHF_GET_ACHIEVEMENT
type MsgMhfGetAchievement struct{
AckHandle uint32
Unk0 uint16 // id?
Unk0 uint32 // id?
Unk1 uint32 // char?
Unk2 uint32 // pad?
}
// Opcode returns the ID associated with this packet type.
@@ -24,9 +23,8 @@ func (m *MsgMhfGetAchievement) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfGetAchievement) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint16()
m.Unk0 = bf.ReadUint32()
m.Unk1 = bf.ReadUint32()
m.Unk2 = bf.ReadUint32()
return nil
}

View File

@@ -11,6 +11,7 @@ import (
// MsgMhfGetUdRanking represents the MSG_MHF_GET_UD_RANKING
type MsgMhfGetUdRanking struct{
AckHandle uint32
Unk0 uint8
}
// Opcode returns the ID associated with this packet type.
@@ -21,6 +22,7 @@ func (m *MsgMhfGetUdRanking) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfGetUdRanking) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8()
return nil
}

View File

@@ -9,7 +9,10 @@ import (
)
// MsgMhfGetUdTacticsRanking represents the MSG_MHF_GET_UD_TACTICS_RANKING
type MsgMhfGetUdTacticsRanking struct{}
type MsgMhfGetUdTacticsRanking struct {
AckHandle uint32
GuildID uint32
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfGetUdTacticsRanking) Opcode() network.PacketID {
@@ -18,7 +21,9 @@ func (m *MsgMhfGetUdTacticsRanking) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfGetUdTacticsRanking) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
m.GuildID = bf.ReadUint32()
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -11,6 +11,7 @@ import (
// MsgMhfListMail represents the MSG_MHF_LIST_MAIL
type MsgMhfListMail struct {
AckHandle uint32
Unk0 uint32
}
// Opcode returns the ID associated with this packet type.
@@ -21,6 +22,7 @@ func (m *MsgMhfListMail) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfListMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint32()
return nil
}

View File

@@ -11,14 +11,29 @@ import (
type OperateGuildAction uint8
const (
OPERATE_GUILD_ACTION_DISBAND = 0x01
OPERATE_GUILD_ACTION_APPLY = 0x02
OPERATE_GUILD_ACTION_LEAVE = 0x03
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_ACTION_UPDATE_COMMENT = 0x09
OPERATE_GUILD_ACTION_DONATE = 0x0a
OPERATE_GUILD_ACTION_UPDATE_MOTTO = 0x0b
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
)
// MsgMhfOperateGuild represents the MSG_MHF_OPERATE_GUILD
@@ -40,7 +55,7 @@ func (m *MsgMhfOperateGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clien
m.GuildID = bf.ReadUint32()
m.Action = OperateGuildAction(bf.ReadUint8())
m.UnkData = bf.DataFromCurrent()
bf.Seek(int64(len(bf.Data()) - 2), 0)
return nil
}

View File

@@ -8,8 +8,22 @@ import (
"github.com/Andoryuuta/byteframe"
)
type OperateJointAction uint8
const (
OPERATE_JOINT_DISBAND = 0x01
OPERATE_JOINT_LEAVE = 0x03
OPERATE_JOINT_KICK = 0x09
)
// MsgMhfOperateJoint represents the MSG_MHF_OPERATE_JOINT
type MsgMhfOperateJoint struct{}
type MsgMhfOperateJoint struct {
AckHandle uint32
AllianceID uint32
GuildID uint32
Action OperateJointAction
UnkData []byte
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfOperateJoint) Opcode() network.PacketID {
@@ -18,7 +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 {
return errors.New("NOT IMPLEMENTED")
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
}
// Build builds a binary packet from the current data.

View File

@@ -11,7 +11,8 @@ import (
type OperateMailOperation uint8
const (
OperateMailOperationDelete OperateMailOperation = 0x01
OPERATE_MAIL_DELETE = 0x01
OPERATE_MAIL_ACQUIRE_ITEM = 0x05
)
// MsgMhfOprtMail represents the MSG_MHF_OPRT_MAIL
@@ -19,7 +20,11 @@ type MsgMhfOprtMail struct {
AckHandle uint32
AccIndex uint8
Index uint8
Operation uint8
Operation OperateMailOperation
Unk0 uint8
Data []byte
Amount uint16
ItemID uint16
}
// Opcode returns the ID associated with this packet type.
@@ -32,7 +37,13 @@ func (m *MsgMhfOprtMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCon
m.AckHandle = bf.ReadUint32()
m.AccIndex = bf.ReadUint8()
m.Index = bf.ReadUint8()
m.Operation = bf.ReadUint8()
m.Operation = OperateMailOperation(bf.ReadUint8())
m.Unk0 = bf.ReadUint8()
switch m.Operation {
case OPERATE_MAIL_ACQUIRE_ITEM:
m.Amount = bf.ReadUint16()
m.ItemID = bf.ReadUint16()
}
return nil
}

View File

@@ -19,6 +19,7 @@ type MsgMhfReadMail struct {
// This is the index within the current mail list
Index uint8
Unk0 uint16
}
// Opcode returns the ID associated with this packet type.
@@ -31,6 +32,7 @@ func (m *MsgMhfReadMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCon
m.AckHandle = bf.ReadUint32()
m.AccIndex = bf.ReadUint8()
m.Index = bf.ReadUint8()
m.Unk0 = bf.ReadUint16()
return nil
}

View File

@@ -9,7 +9,16 @@ import (
)
// MsgMhfSendMail represents the MSG_MHF_SEND_MAIL
type MsgMhfSendMail struct{}
type MsgMhfSendMail struct {
AckHandle uint32
RecipientID uint32
SubjectLength uint16
BodyLength uint16
Quantity uint32
ItemID uint16
Subject []byte
Body []byte
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfSendMail) Opcode() network.PacketID {
@@ -18,7 +27,15 @@ func (m *MsgMhfSendMail) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfSendMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
m.RecipientID = bf.ReadUint32()
m.SubjectLength = bf.ReadUint16()
m.BodyLength = bf.ReadUint16()
m.Quantity = bf.ReadUint32()
m.ItemID = bf.ReadUint16()
m.Subject = bf.ReadNullTerminatedBytes()
m.Body = bf.ReadNullTerminatedBytes()
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -9,7 +9,10 @@ import (
)
// MsgMhfSetGuildMissionTarget represents the MSG_MHF_SET_GUILD_MISSION_TARGET
type MsgMhfSetGuildMissionTarget struct{}
type MsgMhfSetGuildMissionTarget struct {
AckHandle uint32
MissionID uint32
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfSetGuildMissionTarget) Opcode() network.PacketID {
@@ -18,7 +21,9 @@ func (m *MsgMhfSetGuildMissionTarget) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfSetGuildMissionTarget) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
m.MissionID = bf.ReadUint32()
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -11,11 +11,12 @@ import (
type GuildIconMsgPart struct {
Index uint16
ID uint16
Unk0 uint8
Page uint8
Size uint8
Rotation uint8
Unk1 uint8
Unk2 uint16
Red uint8
Green uint8
Blue uint8
PosX uint16
PosY uint16
}
@@ -47,11 +48,12 @@ func (m *MsgMhfUpdateGuildIcon) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cl
m.IconParts[i] = GuildIconMsgPart{
Index: bf.ReadUint16(),
ID: bf.ReadUint16(),
Unk0: bf.ReadUint8(),
Page: bf.ReadUint8(),
Size: bf.ReadUint8(),
Rotation: bf.ReadUint8(),
Unk1: bf.ReadUint8(),
Unk2: bf.ReadUint16(),
Red: bf.ReadUint8(),
Green: bf.ReadUint8(),
Blue: bf.ReadUint8(),
PosX: bf.ReadUint16(),
PosY: bf.ReadUint16(),
}

View File

@@ -8,8 +8,21 @@ import (
"github.com/Andoryuuta/byteframe"
)
type Item struct{
Unk0 uint32
ItemId uint16
Amount uint16
Unk1 uint32
}
// MsgMhfUpdateGuildItem represents the MSG_MHF_UPDATE_GUILD_ITEM
type MsgMhfUpdateGuildItem struct{}
type MsgMhfUpdateGuildItem struct{
AckHandle uint32
GuildId uint32
Amount uint16
Unk1 uint16 // 0x00 0x00
Items []Item // Array of updated item IDs
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfUpdateGuildItem) Opcode() network.PacketID {
@@ -18,7 +31,20 @@ func (m *MsgMhfUpdateGuildItem) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfUpdateGuildItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
m.GuildId = bf.ReadUint32()
m.Amount = bf.ReadUint16()
m.Unk1 = bf.ReadUint16()
m.Items = make([]Item, int(m.Amount))
for i := 0; i < int(m.Amount); i++ {
m.Items[i].Unk0 = bf.ReadUint32()
m.Items[i].ItemId = bf.ReadUint16()
m.Items[i].Amount = bf.ReadUint16()
m.Items[i].Unk1 = bf.ReadUint32()
}
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -0,0 +1,37 @@
package mhfpacket
import (
"errors"
"github.com/Solenataris/Erupe/network/clientctx"
"github.com/Solenataris/Erupe/network"
"github.com/Andoryuuta/byteframe"
)
// MsgMhfUpdateGuildMessageBoard represents the MSG_MHF_UPDATE_GUILD_MESSAGE_BOARD
type MsgMhfUpdateGuildMessageBoard struct {
AckHandle uint32
MessageOp uint32
Request []byte
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfUpdateGuildMessageBoard) Opcode() network.PacketID {
return network.MSG_MHF_UPDATE_GUILD_MESSAGE_BOARD
}
// Parse parses the packet from binary
func (m *MsgMhfUpdateGuildMessageBoard) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.MessageOp = bf.ReadUint32()
if m.MessageOp != 5 {
m.Request = bf.DataFromCurrent()
bf.Seek(int64(len(bf.Data()) - 2), 0)
}
return nil
}
// Build builds a binary packet from the current data.
func (m *MsgMhfUpdateGuildMessageBoard) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
}

View File

@@ -9,7 +9,12 @@ import (
)
// MsgMhfUpdateUnionItem represents the MSG_MHF_UPDATE_UNION_ITEM
type MsgMhfUpdateUnionItem struct{}
type MsgMhfUpdateUnionItem struct {
AckHandle uint32
Amount uint16
Unk1 uint16 // 0x00 0x00
Items []Item // Array of updated item IDs
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfUpdateUnionItem) Opcode() network.PacketID {
@@ -18,7 +23,19 @@ func (m *MsgMhfUpdateUnionItem) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfUpdateUnionItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
m.Amount = bf.ReadUint16()
m.Unk1 = bf.ReadUint16()
m.Items = make([]Item, int(m.Amount))
for i := 0; i < int(m.Amount); i++ {
m.Items[i].Unk0 = bf.ReadUint32()
m.Items[i].ItemId = bf.ReadUint16()
m.Items[i].Amount = bf.ReadUint16()
m.Items[i].Unk1 = bf.ReadUint32()
}
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -1,27 +1 @@
package mhfpacket
import (
"errors"
"github.com/Solenataris/Erupe/network/clientctx"
"github.com/Solenataris/Erupe/network"
"github.com/Andoryuuta/byteframe"
)
// MsgSysReserve202 represents the MSG_SYS_reserve202
type MsgSysReserve202 struct{}
// Opcode returns the ID associated with this packet type.
func (m *MsgSysReserve202) Opcode() network.PacketID {
return network.MSG_SYS_reserve202
}
// Parse parses the packet from binary
func (m *MsgSysReserve202) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
}
// Build builds a binary packet from the current data.
func (m *MsgSysReserve202) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
}

View File

@@ -1,36 +1 @@
package mhfpacket
import (
"errors"
"github.com/Solenataris/Erupe/network/clientctx"
"github.com/Solenataris/Erupe/network"
"github.com/Andoryuuta/byteframe"
)
// TODO(Andoryuuta): Make up a name for this packet, not reserved anymore. Called "Is_update_guild_msg_board"
// MsgSysReserve203 represents the MSG_SYS_reserve203
type MsgSysReserve203 struct {
AckHandle uint32
Unk0 uint16 // Hardcoded 0x0000 in the binary
Unk1 uint16 // Hardcoded 0x0500 in the binary.
}
// Opcode returns the ID associated with this packet type.
func (m *MsgSysReserve203) Opcode() network.PacketID {
return network.MSG_SYS_reserve203
}
// Parse parses the packet from binary
func (m *MsgSysReserve203) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint16()
m.Unk1 = bf.ReadUint16()
return nil
}
// Build builds a binary packet from the current data.
func (m *MsgSysReserve203) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
}

View File

@@ -9,7 +9,12 @@ import (
)
// MsgSysReserve205 represents the MSG_SYS_reserve205
type MsgSysReserve205 struct{}
type MsgSysReserve205 struct {
AckHandle uint32
Unk0 uint32
Unk1 uint32
Unk2 uint32
}
// Opcode returns the ID associated with this packet type.
func (m *MsgSysReserve205) Opcode() network.PacketID {
@@ -18,7 +23,11 @@ func (m *MsgSysReserve205) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgSysReserve205) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint32()
m.Unk1 = bf.ReadUint32()
m.Unk2 = bf.ReadUint32()
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -841,10 +841,10 @@ func FromOpcode(opcode network.PacketID) MHFPacket {
return &MsgMhfUpdateForceGuildRank{}
case network.MSG_MHF_RESET_TITLE:
return &MsgMhfResetTitle{}
case network.MSG_SYS_reserve202:
return &MsgSysReserve202{}
case network.MSG_SYS_reserve203:
return &MsgSysReserve203{}
case network.MSG_MHF_ENUMERATE_GUILD_MESSAGE_BOARD:
return &MsgMhfEnumerateGuildMessageBoard{}
case network.MSG_MHF_UPDATE_GUILD_MESSAGE_BOARD:
return &MsgMhfUpdateGuildMessageBoard{}
case network.MSG_SYS_reserve204:
return &MsgSysReserve204{}
case network.MSG_SYS_reserve205:

View File

@@ -423,8 +423,8 @@ const (
MSG_SYS_reserve19F
MSG_MHF_UPDATE_FORCE_GUILD_RANK
MSG_MHF_RESET_TITLE
MSG_SYS_reserve202
MSG_SYS_reserve203
MSG_MHF_ENUMERATE_GUILD_MESSAGE_BOARD
MSG_MHF_UPDATE_GUILD_MESSAGE_BOARD
MSG_SYS_reserve204
MSG_SYS_reserve205
MSG_SYS_reserve206

File diff suppressed because one or more lines are too long

View File

@@ -3,6 +3,7 @@ package channelserver
import (
"bytes"
"encoding/hex"
"encoding/binary"
"io/ioutil"
"math/bits"
"math/rand"
@@ -74,16 +75,7 @@ func doAckSimpleFail(s *Session, ackHandle uint32, data []byte) {
func updateRights(s *Session) {
update := &mhfpacket.MsgSysUpdateRight{
ClientRespAckHandle: 0,
Unk1: 0x0E, //0e with normal sub 4e when having premium it's probably a bitfield?
// 01 = Character can take quests at allows
// 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
// some connection to unk1 above for these maybe?
// 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
// no timestamp after 08 or 1E while active
// 0C = N Boost course, ultra luxury course that ruins the game if in use but also gives a
Unk1: s.rights,
Rights: []mhfpacket.ClientRight{
{
ID: 1,
@@ -143,10 +135,25 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysLogin)
name := ""
rights := uint32(0x0E)
// 0e with normal sub 4e when having premium
// 01 = Character can take quests at allows
// 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 {
panic(err)
}
s.server.db.QueryRow("SELECT name FROM characters WHERE id = $1", pkt.CharID0).Scan(&name)
s.Lock()
s.Name = name
s.charID = pkt.CharID0
s.rights = rights
s.Unlock()
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // Unix timestamp
@@ -158,7 +165,7 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
}
}
_, err := s.server.db.Exec("UPDATE characters SET last_login=$1 WHERE id=$2", Time_Current().Unix(), s.charID)
_, err = s.server.db.Exec("UPDATE characters SET last_login=$1 WHERE id=$2", Time_Current().Unix(), s.charID)
if err != nil {
panic(err)
}
@@ -191,6 +198,46 @@ func logoutPlayer(s *Session) {
}
removeSessionFromStage(s)
if _, exists := s.server.semaphore["hs_l0u3B51J9k3"]; exists {
if _, ok := s.server.semaphore["hs_l0u3B51J9k3"].reservedClientSlots[s.charID]; ok {
removeSessionFromSemaphore(s)
}
}
var timePlayed int
err := s.server.db.QueryRow("SELECT time_played FROM characters WHERE id = $1", s.charID).Scan(&timePlayed)
timePlayed = (int(Time_Current_Adjusted().Unix()) - int(s.sessionStart)) + timePlayed
var rpGained int
if s.rights == 0x08091e4e || s.rights == 0x08091e0e { // N Course
rpGained = timePlayed / 900
timePlayed = timePlayed % 900
} else {
rpGained = timePlayed / 1800
timePlayed = timePlayed % 1800
}
_, err = s.server.db.Exec("UPDATE characters SET time_played = $1 WHERE id = $2", timePlayed, s.charID)
if err != nil {
panic(err)
}
saveData, err := GetCharacterSaveData(s, s.charID)
if err != nil {
panic(err)
}
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()
}
}
func handleMsgSysSetStatus(s *Session, p mhfpacket.MHFPacket) {}
@@ -208,6 +255,8 @@ func handleMsgSysTime(s *Session, p mhfpacket.MHFPacket) {
Timestamp: uint32(Time_Current_Adjusted().Unix()), // JP timezone
}
s.QueueSendMHF(resp)
s.notifyticker()
}
func handleMsgSysIssueLogkey(s *Session, p mhfpacket.MHFPacket) {
@@ -356,15 +405,92 @@ func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateUnionItem)
var boxContents []byte
bf := byteframe.NewByteFrame()
err := s.server.db.QueryRow("SELECT item_box FROM users, characters WHERE characters.id = $1 AND users.id = characters.user_id", int(s.charID)).Scan(&boxContents)
if err != nil {
s.logger.Fatal("Failed to get shared item box contents from db", zap.Error(err))
} else {
if len(boxContents) == 0 {
bf.WriteUint32(0x00)
} else {
amount := len(boxContents) / 4
bf.WriteUint16(uint16(amount))
bf.WriteUint32(0x00)
bf.WriteUint16(0x00)
for i := 0; i < amount; i++ {
bf.WriteUint32(binary.BigEndian.Uint32(boxContents[i*4 : i*4+4]))
if i+1 != amount {
bf.WriteUint64(0x00)
}
}
}
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateUnionItem)
// Get item cache from DB
var boxContents []byte
var oldItems []Item
func handleMsgMhfCreateJoint(s *Session, p mhfpacket.MHFPacket) {}
err := s.server.db.QueryRow("SELECT item_box FROM users, characters WHERE characters.id = $1 AND users.id = characters.user_id", int(s.charID)).Scan(&boxContents)
if err != nil {
s.logger.Fatal("Failed to get shared item box contents from db", zap.Error(err))
} else {
amount := len(boxContents) / 4
oldItems = make([]Item, amount)
for i := 0; i < amount; i++ {
oldItems[i].ItemId = binary.BigEndian.Uint16(boxContents[i*4 : i*4+2])
oldItems[i].Amount = binary.BigEndian.Uint16(boxContents[i*4+2 : i*4+4])
}
}
func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {}
// Update item stacks
newItems := make([]Item, len(oldItems))
copy(newItems, oldItems)
for i := 0; i < int(pkt.Amount); i++ {
for j := 0; j <= len(oldItems); j++ {
if j == len(oldItems) {
var newItem Item
newItem.ItemId = pkt.Items[i].ItemId
newItem.Amount = pkt.Items[i].Amount
newItems = append(newItems, newItem)
break
}
if pkt.Items[i].ItemId == oldItems[j].ItemId {
newItems[j].Amount = pkt.Items[i].Amount
break
}
}
}
func handleMsgMhfInfoJoint(s *Session, p mhfpacket.MHFPacket) {}
// Delete empty item stacks
for i := len(newItems) - 1; i >= 0; i-- {
if int(newItems[i].Amount) == 0 {
copy(newItems[i:], newItems[i+1:])
newItems[len(newItems)-1] = make([]Item, 1)[0]
newItems = newItems[:len(newItems)-1]
}
}
// Create new item cache
bf := byteframe.NewByteFrame()
for i := 0; i < len(newItems); i++ {
bf.WriteUint16(newItems[i].ItemId)
bf.WriteUint16(newItems[i].Amount)
}
// Upload new item cache
_, err = s.server.db.Exec("UPDATE users SET item_box = $1 FROM characters WHERE users.id = characters.user_id AND characters.id = $2", bf.Data(), int(s.charID))
if err != nil {
s.logger.Fatal("Failed to update shared item box contents in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireCafeItem)

View File

@@ -2,8 +2,8 @@ package channelserver
import (
"fmt"
"math"
"strings"
"math"
"github.com/Andoryuuta/byteframe"
"github.com/Solenataris/Erupe/network/binpacket"
@@ -22,6 +22,7 @@ const (
const (
BroadcastTypeTargeted = 0x01
BroadcastTypeStage = 0x03
BroadcastTypeRavi = 0x06
BroadcastTypeWorld = 0x0a
)
@@ -52,7 +53,6 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x03 && len(pkt.RawDataPayload) == 0x10 {
tmp := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 {
_ = tmp.ReadBytes(9)
tmp.SetLE()
@@ -93,6 +93,11 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
s.server.BroadcastMHF(resp, s)
case BroadcastTypeStage:
s.stage.BroadcastMHF(resp, s)
case BroadcastTypeRavi:
if pkt.MessageType == 1 {
session := s.server.semaphore["hs_l0u3B51J9k3"]
(*session).BroadcastMHF(resp, s)
}
case BroadcastTypeTargeted:
for _, targetID := range (*msgBinTargeted).TargetCharIDs {
char := s.server.FindSessionByCharID(targetID)
@@ -129,6 +134,114 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
s.server.discordSession.ChannelMessageSend(s.server.erupeConfig.Discord.ChannelID, message)
}
// RAVI COMMANDS
if _, exists := s.server.semaphore["hs_l0u3B51J9k3"]; exists {
s.server.semaphoreLock.Lock()
getSemaphore := s.server.semaphore["hs_l0u3B51J9k3"]
s.server.semaphoreLock.Unlock()
if _, exists := getSemaphore.reservedClientSlots[s.charID]; exists {
if strings.HasPrefix(chatMessage.Message, "!ravistart") {
row := s.server.db.QueryRow("SELECT raviposttime, ravistarted FROM raviregister WHERE refid = 12")
var raviPosted, raviStarted uint32
err := row.Scan(&raviPosted, &raviStarted)
if err != nil {
panic(err)
return
}
if raviStarted == 0 {
sendServerChatMessage(s, fmt.Sprintf("Raviente will start in less than 10 seconds"))
s.server.db.Exec("UPDATE raviregister SET ravistarted = $1", raviPosted)
} else {
sendServerChatMessage(s, fmt.Sprintf("Raviente has already started"))
}
}
if strings.HasPrefix(chatMessage.Message, "!bressend") {
row := s.server.db.QueryRow("SELECT unknown20 FROM ravistate WHERE refid = 29")
var berserkRes uint32
err := row.Scan(&berserkRes)
if err != nil {
panic(err)
return
}
if berserkRes > 0 {
sendServerChatMessage(s, fmt.Sprintf("Sending ressurection support"))
s.server.db.Exec("UPDATE ravistate SET unknown20 = $1", 0)
} else {
sendServerChatMessage(s, fmt.Sprintf("Ressurection support has not been requested"))
}
}
if strings.HasPrefix(chatMessage.Message, "!bsedsend") {
hprow := s.server.db.QueryRow("SELECT phase1hp, phase2hp, phase3hp, phase4hp, phase5hp FROM ravistate WHERE refid = 29")
var phase1HP, phase2HP, phase3HP, phase4HP, phase5HP uint32
hperr := hprow.Scan(&phase1HP, &phase2HP, &phase3HP, &phase4HP, &phase5HP)
if hperr != nil {
panic(hperr)
return
}
row := s.server.db.QueryRow("SELECT support2 FROM ravisupport WHERE refid = 25")
var berserkTranq uint32
err := row.Scan(&berserkTranq)
if err != nil {
panic(err)
return
}
sendServerChatMessage(s, fmt.Sprintf("Sending sedation support if requested"))
s.server.db.Exec("UPDATE ravisupport SET support2 = $1", (phase1HP + phase2HP + phase3HP + phase4HP + phase5HP))
}
if strings.HasPrefix(chatMessage.Message, "!bsedreq") {
hprow := s.server.db.QueryRow("SELECT phase1hp, phase2hp, phase3hp, phase4hp, phase5hp FROM ravistate WHERE refid = 29")
var phase1HP, phase2HP, phase3HP, phase4HP, phase5HP uint32
hperr := hprow.Scan(&phase1HP, &phase2HP, &phase3HP, &phase4HP, &phase5HP)
if hperr != nil {
panic(hperr)
return
}
row := s.server.db.QueryRow("SELECT support2 FROM ravisupport WHERE refid = 25")
var berserkTranq uint32
err := row.Scan(&berserkTranq)
if err != nil {
panic(err)
return
}
sendServerChatMessage(s, fmt.Sprintf("Requesting sedation support"))
s.server.db.Exec("UPDATE ravisupport SET support2 = $1", ((phase1HP + phase2HP + phase3HP + phase4HP + phase5HP) + 12))
}
if strings.HasPrefix(chatMessage.Message, "!setmultiplier ") {
var num uint8
n, numerr := fmt.Sscanf(chatMessage.Message, "!setmultiplier %d", &num)
row := s.server.db.QueryRow("SELECT damagemultiplier FROM ravistate WHERE refid = 29")
var damageMultiplier uint32
err := row.Scan(&damageMultiplier)
if err != nil {
panic(err)
return
}
if numerr != nil || n != 1 {
sendServerChatMessage(s, fmt.Sprintf("Please use the format !setmultiplier x"))
} else if damageMultiplier == 1 {
if num > 20 {
sendServerChatMessage(s, fmt.Sprintf("Max multiplier for Ravi is 20, setting to this value"))
s.server.db.Exec("UPDATE ravistate SET damagemultiplier = $1", 20)
} else {
sendServerChatMessage(s, fmt.Sprintf("Setting Ravi damage multiplier to %d", num))
s.server.db.Exec("UPDATE ravistate SET damagemultiplier = $1", num)
}
} else {
sendServerChatMessage(s, fmt.Sprintf("Multiplier can only be set once, please restart Ravi to set again"))
}
}
if strings.HasPrefix(chatMessage.Message, "!checkmultiplier") {
var damageMultiplier uint32
row := s.server.db.QueryRow("SELECT damagemultiplier FROM ravistate WHERE refid = 29").Scan(&damageMultiplier)
if row != nil {
return
}
sendServerChatMessage(s, fmt.Sprintf("Ravi's current damage multiplier is %d", damageMultiplier))
}
}
}
// END OF RAVI COMMANDS
if strings.HasPrefix(chatMessage.Message, "!tele ") {
var x, y int16
n, err := fmt.Sscanf(chatMessage.Message, "!tele %d %d", &x, &y)

View File

@@ -59,7 +59,11 @@ func handleMsgMhfListMember(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfOprMember(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfOprMember(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfListMember)
// TODO: add targetid(uint32) to charid(uint32)'s database under new field
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfShutClient(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -2,6 +2,7 @@ package channelserver
import (
"encoding/hex"
"encoding/binary"
"fmt"
"io/ioutil"
"os"
@@ -54,21 +55,46 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
}
s.logger.Info("Wrote recompressed savedata back to DB.")
dumpSaveData(s, pkt.RawDataPayload, "")
// Temporary server launcher response stuff
// 0x1F715 Weapon Class
// 0x1FDF6 HR (small_gr_level)
// 0x88 Character Name
_, err = s.server.db.Exec("UPDATE characters SET weapon=$1 WHERE id=$2", uint16(decompressedData[128789]), s.charID)
_, 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 in db", zap.Error(err))
s.logger.Fatal("Failed to character weapon type in db", zap.Error(err))
}
isMale := uint8(decompressedData[80]) // 0x50
if isMale == 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)
}
gr := uint16(decompressedData[130550])<<8 | uint16(decompressedData[130551])
s.logger.Info("Setting db field", zap.Uint16("gr_override_level", gr))
// We have to use `gr_override_level` (uint16), not `small_gr_level` (uint8) to store this.
_, err = s.server.db.Exec("UPDATE characters SET gr_override_mode=true, gr_override_level=$1 WHERE id=$2", gr, s.charID)
if err != nil {
s.logger.Fatal("Failed to update character gr_override_level in db", zap.Error(err))
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)
if err != nil {
@@ -77,6 +103,53 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func grpToGR(n uint32) uint16 {
var gr uint16
gr = 1
switch grp := int(n); {
case grp < 208750: // Up to 50
i := 0
for {
grp -= 500
if grp <= 500 {
if grp < 0 {
i--
}
break
} else {
i++
for j := 0; j < i; j++ {
grp -= 150
}
}
}
gr = uint16(i + 2); break
case grp < 593400: // 51-99
grp -= 208750; i := 51; for { if grp < 7850 {break}; i++; grp -= 7850 }; gr = uint16(i); break
case grp < 993400: // 100-149
grp -= 593400; i := 100; for { if grp < 8000 {break}; i++; grp -= 8000 }; gr = uint16(i); break
case grp < 1400900: // 150-199
grp -= 993400; i := 150; for { if grp < 8150 {break}; i++; grp -= 8150 }; gr = uint16(i); break
case grp < 2315900: // 200-299
grp -= 1400900; i := 200; for { if grp < 9150 {break}; i++; grp -= 9150 }; gr = uint16(i); break
case grp < 3340900: // 300-399
grp -= 2315900; i := 300; for { if grp < 10250 {break}; i++; grp -= 10250 }; gr = uint16(i); break
case grp < 4505900: // 400-499
grp -= 3340900; i := 400; for { if grp < 11650 {break}; i++; grp -= 11650 }; gr = uint16(i); break
case grp < 5850900: // 500-599
grp -= 4505900; i := 500; for { if grp < 13450 {break}; i++; grp -= 13450 }; gr = uint16(i); break
case grp < 7415900: // 600-699
grp -= 5850900; i := 600; for { if grp < 15650 {break}; i++; grp -= 15650 }; gr = uint16(i); break
case grp < 9230900: // 700-799
grp -= 7415900; i := 700; for { if grp < 18150 {break}; i++; grp -= 18150 }; gr = uint16(i); break
case grp < 11345900: // 800-899
grp -= 9230900; i := 800; for { if grp < 21150 {break}; i++; grp -= 21150 }; gr = uint16(i); break
default: // 900+
grp -= 11345900; i := 900; for { if grp < 23950 {break}; i++; grp -= 23950 }; gr = uint16(i); break
}
return gr
}
func dumpSaveData(s *Session, data []byte, suffix string) {
if !s.server.erupeConfig.DevModeOptions.SaveDumps.Enabled {
return

View File

@@ -196,7 +196,10 @@ func handleMsgMhfAcquireUdItem(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetUdRanking(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetUdRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdRanking)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetUdMyRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdMyRanking)

View File

@@ -10,6 +10,8 @@ import (
"fmt"
"sort"
"time"
"strings"
"strconv"
"github.com/Andoryuuta/byteframe"
"github.com/Solenataris/Erupe/common/bfutil"
@@ -47,10 +49,11 @@ type Guild struct {
SubMotto uint8 `db:"sub_motto"`
CreatedAt time.Time `db:"created_at"`
MemberCount uint16 `db:"member_count"`
RP uint32 `db:"rp"`
RankRP uint32 `db:"rank_rp"`
EventRP uint32 `db:"event_rp"`
Comment string `db:"comment"`
FestivalColour FestivalColour `db:"festival_colour"`
GuildHallType uint16 `db:"guild_hall"`
Rank uint16 `db:"rank"`
Icon *GuildIcon `db:"icon"`
GuildLeader
@@ -64,8 +67,12 @@ type GuildLeader struct {
type GuildIconPart struct {
Index uint16
ID uint16
Page uint8
Size uint8
Rotation uint8
Red uint8
Green uint8
Blue uint8
PosX uint16
PosY uint16
}
@@ -99,9 +106,11 @@ func (gi *GuildIcon) Value() (valuer driver.Value, err error) {
}
const guildInfoSelectQuery = `
SELECT g.id,
SELECT
g.id,
g.name,
g.rp,
g.rank_rp,
g.event_rp,
g.main_motto,
g.sub_motto,
created_at,
@@ -109,7 +118,14 @@ SELECT g.id,
lc.name as leader_name,
comment,
festival_colour,
guild_hall,
CASE
WHEN g.rank_rp <= 48 THEN g.rank_rp/24
WHEN g.rank_rp <= 288 THEN g.rank_rp/48+1
WHEN g.rank_rp <= 504 THEN g.rank_rp/72+3
WHEN g.rank_rp <= 1080 THEN (g.rank_rp-24)/96+5
WHEN g.rank_rp < 1200 THEN 16
ELSE 17
END rank,
icon,
(
SELECT count(1) FROM guild_characters gc WHERE gc.guild_id = g.id
@@ -328,28 +344,6 @@ func (guild *Guild) ArrangeCharacters(s *Session, charIDs []uint32) error {
return nil
}
func (guild *Guild) DonateRP(s *Session, rp uint16, transaction *sql.Tx) (err error) {
updateSQL := "UPDATE guilds SET rp = rp + $1 WHERE id = $2"
if transaction != nil {
_, err = transaction.Exec(updateSQL, rp, guild.ID)
} else {
_, err = s.server.db.Exec(updateSQL, rp, guild.ID)
}
if err != nil {
s.logger.Error(
"failed to donate RP to guild",
zap.Error(err),
zap.Uint32("guildID", guild.ID),
)
return err
}
return nil
}
func (guild *Guild) GetApplicationForCharID(s *Session, charID uint32, applicationType GuildApplicationType) (*GuildApplication, error) {
row := s.server.db.QueryRowx(`
SELECT * from guild_applications WHERE character_id = $1 AND guild_id = $2 AND application_type = $3
@@ -415,8 +409,8 @@ func CreateGuild(s *Session, guildName string) (int32, error) {
}
guildResult, err := transaction.Query(
"INSERT INTO guilds (name, leader_id, rp, guild_hall) VALUES ($1, $2, $3, $4) RETURNING id",
guildName, s.charID, 48, 17,
"INSERT INTO guilds (name, leader_id) VALUES ($1, $2) RETURNING id",
guildName, s.charID,
)
if err != nil {
@@ -617,7 +611,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame()
switch pkt.Action {
case mhfpacket.OPERATE_GUILD_ACTION_DISBAND:
case mhfpacket.OPERATE_GUILD_DISBAND:
if guild.LeaderCharID != s.charID {
s.logger.Warn(fmt.Sprintf("character '%d' is attempting to manage guild '%d' without permission", s.charID, guild.ID))
return
@@ -632,7 +626,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
}
bf.WriteUint32(uint32(response))
case mhfpacket.OPERATE_GUILD_ACTION_APPLY:
case mhfpacket.OPERATE_GUILD_APPLY:
err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil)
if err != nil {
@@ -641,7 +635,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
} else {
bf.WriteUint32(guild.LeaderCharID)
}
case mhfpacket.OPERATE_GUILD_ACTION_LEAVE:
case mhfpacket.OPERATE_GUILD_LEAVE:
var err error
if characterGuildInfo.IsApplicant {
@@ -651,24 +645,27 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
}
response := 0x01
if err != nil {
// All successful acks return 0x01, assuming 0x00 is failure
response = 0x00
}
bf.WriteUint32(uint32(response))
case mhfpacket.OPERATE_GUILD_ACTION_DONATE:
err := handleOperateGuildActionDonate(s, guild, pkt, bf)
if err != nil {
case mhfpacket.OPERATE_GUILD_DONATE_RANK:
handleDonateRP(s, pkt, bf, guild, false)
case mhfpacket.OPERATE_GUILD_SET_APPLICATION_DENY:
// TODO: close applications for guild
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_SET_APPLICATION_ALLOW:
// TODO: open applications for guild
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_TRUE:
handleAvoidLeadershipUpdate(s, pkt, true)
case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_FALSE:
handleAvoidLeadershipUpdate(s, pkt, false)
case mhfpacket.OPERATE_GUILD_ACTION_UPDATE_COMMENT:
case mhfpacket.OPERATE_GUILD_UPDATE_COMMENT:
pbf := byteframe.NewByteFrameFromBytes(pkt.UnkData)
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
@@ -695,7 +692,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
}
bf.WriteUint32(0x00)
case mhfpacket.OPERATE_GUILD_ACTION_UPDATE_MOTTO:
case mhfpacket.OPERATE_GUILD_UPDATE_MOTTO:
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
@@ -710,6 +707,26 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_1:
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_2:
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_3:
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_1:
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_2:
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_3:
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_DONATE_EVENT:
handleDonateRP(s, pkt, bf, guild, true)
default:
panic(fmt.Sprintf("unhandled operate guild action '%d'", pkt.Action))
}
@@ -717,6 +734,35 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleDonateRP(s *Session, pkt *mhfpacket.MsgMhfOperateGuild, bf *byteframe.ByteFrame, guild *Guild, isEvent bool) error {
rp := binary.BigEndian.Uint16(pkt.UnkData[3:5])
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
}
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()
}
bf.WriteUint32(uint32(saveData.RP))
return nil
}
func handleAvoidLeadershipUpdate(s *Session, pkt *mhfpacket.MsgMhfOperateGuild, avoidLeadership bool) {
characterGuildData, err := GetCharacterGuildData(s, s.charID)
@@ -737,93 +783,6 @@ func handleAvoidLeadershipUpdate(s *Session, pkt *mhfpacket.MsgMhfOperateGuild,
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleOperateGuildActionDonate(s *Session, guild *Guild, pkt *mhfpacket.MsgMhfOperateGuild, bf *byteframe.ByteFrame) error {
var rpDB uint16
var guildHallLvl uint16
rp := binary.BigEndian.Uint16(pkt.UnkData[3:5])
saveData, err := GetCharacterSaveData(s, s.charID)
if err != nil {
return err
}
if saveData.RP < rp {
s.logger.Warn(
"character attempting to donate more RP than they own",
zap.Uint32("charID", s.charID),
zap.Uint16("rp", rp),
)
return err
}
saveData.RP -= rp
transaction, err := s.server.db.Begin()
if err != nil {
s.logger.Error("failed to start db transaction", zap.Error(err))
return err
}
err = saveData.Save(s, transaction)
if err != nil {
err = transaction.Rollback()
if err != nil {
s.logger.Error("failed to rollback transaction", zap.Error(err))
}
return err
}
err = guild.DonateRP(s, rp, transaction)
if err != nil {
err = transaction.Rollback()
if err != nil {
s.logger.Error("failed to rollback transaction", zap.Error(err))
}
return err
}
err = transaction.Commit()
if err != nil {
s.logger.Error("failed to commit transaction", zap.Error(err))
return err
}
bf.WriteUint32(uint32(saveData.RP)) // Points remaining
errSelectSQL := s.server.db.QueryRow("SELECT rp FROM guilds WHERE id=$1", guild.ID).Scan(&rpDB)
if errSelectSQL != nil {
s.logger.Fatal("Failed to get rp from db", zap.Error(errSelectSQL))
}
guildHallLvl = rpDB / 70
if guildHallLvl >= 17 {
guildHallLvl = 17
}
fmt.Printf("\n\nrpDB = %d\n", rpDB)
fmt.Printf("guildHallLvl = %d\n", guildHallLvl)
_, errUpdateSQL := s.server.db.Exec("UPDATE guilds SET guild_hall=$1 WHERE id=$2", guildHallLvl, guild.ID)
if errUpdateSQL == nil {
s.logger.Error("failed to donate RP to guild", zap.Error(errUpdateSQL), zap.Uint32("guildID", guild.ID))
return err
}
return nil
}
func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOperateGuildMember)
@@ -924,11 +883,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(guild.ID)
bf.WriteUint32(guild.LeaderCharID)
// Unk 0x09 = Guild Hall available, maybe guild hall type?
// Guild hall available on at least
// 0x09 0x08 0x02
// Should just be outright guild level for guild hall features, 17 gives everything
bf.WriteUint16(guild.GuildHallType)
bf.WriteUint16(guild.Rank)
bf.WriteUint16(guild.MemberCount)
bf.WriteUint8(guild.MainMotto)
@@ -939,7 +894,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
if characterGuildData == nil || characterGuildData.IsApplicant {
bf.WriteUint16(0x00)
} else if characterGuildData.IsSubLeader() || guild.LeaderCharID == s.charID {
} else if guild.LeaderCharID == s.charID {
bf.WriteUint16(0x01)
} else {
bf.WriteUint16(0x02)
@@ -955,13 +910,13 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint8(uint8(len(leaderName) + 1))
bf.WriteBytes(guildName)
bf.WriteBytes(guildComment)
bf.WriteUint8(FestivalColourCodes[guild.FestivalColour])
bf.WriteUint32(guild.RP)
bf.WriteUint32(guild.RankRP)
bf.WriteNullTerminatedBytes(leaderName)
//bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00}) // Unk
bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00, 0x18, 0xBD}) // Level 17 guild's version
bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00}) // Unk
bf.WriteBool(false) // isReturnGuild
bf.WriteBytes([]byte{0x01, 0x02, 0x02}) // Unk
bf.WriteUint32(guild.EventRP)
// Pugi's names, probably expected as null until you have them with levels? Null gives them a default japanese name
for i := 0; i < 3; i++ {
@@ -983,30 +938,6 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(0x00) // Alliance ID
// TODO add alliance parts here
//
//if (AllianceID != 0) {
// uint16 AllianceDataUnk;
// uint16 AllianceDataUnk;
// uint16 AllianceNameLength;
// char AllianceName[AllianceNameLength];
//
// byte NumAllianceMembers;
//
// struct AllianceMember {
// uint32 Unk;
// uint32 Unk;
// uint16 Unk;
// uint16 Unk;
// uint16 Unk;
// uint16 GuildNameLength;
// char GuildName[GuildNameLength];
// uint16 GuildLeaderNameLength;
// char GuildLeaderName[GuildLeaderNameLength];
//
// } member[NumAllianceMembers] <optimize=false>;
//}
applicants, err := GetGuildMembers(s, guild.ID, true)
if err != nil {
@@ -1028,15 +959,6 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteNullTerminatedBytes(applicantName)
}
// This is guild icon data
// temp canned bytes to avoid crashing when a guild has more than 1-2 members and a guild hall
//bf.WriteBytes([]byte{0x00, 0x05, 0x00, 0x03, 0x00, 0x38, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x00, 0x04, 0x00, 0x03,
// 0x00, 0x02, 0x00, 0x38, 0x01, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x00, 0x69, 0x00, 0x60, 0x00, 0x01,
// 0x00, 0x38, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x6A, 0x00, 0x03, 0x00, 0x00, 0x00, 0x38,
// 0x01, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x63, 0x00, 0x06, 0x00, 0x35, 0x01, 0x03,
// 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x11, 0x00, 0x01, 0x00,
//})
// Unk bool? if true +3 bytes after this
bf.WriteUint8(0x00)
@@ -1046,11 +968,12 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
for _, p := range guild.Icon.Parts {
bf.WriteUint16(p.Index)
bf.WriteUint16(p.ID)
bf.WriteUint8(0x01)
bf.WriteUint8(p.Page)
bf.WriteUint8(p.Size)
bf.WriteUint8(p.Rotation)
bf.WriteUint8(0xFF)
bf.WriteUint16(0xFFFF)
bf.WriteUint8(p.Red)
bf.WriteUint8(p.Green)
bf.WriteUint8(p.Blue)
bf.WriteUint16(p.PosX)
bf.WriteUint16(p.PosY)
}
@@ -1073,24 +996,123 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateGuild)
var guilds []*Guild
var rows *sqlx.Rows
var err error
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
switch pkt.Type {
case mhfpacket.ENUMERATE_GUILD_TYPE_NAME:
// I have no idea if is really little endian, but it seems too weird to have a random static
// 0x00 before the string
searchTermLength := binary.LittleEndian.Uint16(pkt.RawDataPayload[9:11])
searchTerm := pkt.RawDataPayload[11 : 11+searchTermLength]
case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME:
bf.ReadBytes(8)
searchTermLength := bf.ReadUint16()
bf.ReadBytes(1)
searchTerm := bf.ReadBytes(uint(searchTermLength))
var searchTermSafe string
searchTermSafe, err = s.clientContext.StrConv.Decode(bfutil.UpToNull(searchTerm))
if err != nil {
panic(err)
}
guilds, err = FindGuildsByName(s, searchTermSafe)
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME:
bf.ReadBytes(8)
searchTermLength := bf.ReadUint16()
bf.ReadBytes(1)
searchTerm := bf.ReadBytes(uint(searchTermLength))
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() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_ID:
bf.ReadBytes(3)
ID := bf.ReadUint32()
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE leader_id = $1`, guildInfoSelectQuery), ID)
if err != nil {
s.logger.Error("Failed to retrieve guild by leader ID", zap.Error(err))
} else {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_MEMBERS:
sorting := bf.ReadUint16()
if sorting == 1 {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count DESC`, guildInfoSelectQuery))
} else {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count ASC`, guildInfoSelectQuery))
}
if err != nil {
s.logger.Error("Failed to retrieve guild by member count", zap.Error(err))
} else {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_REGISTRATION:
sorting := bf.ReadUint16()
if sorting == 1 {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id DESC`, guildInfoSelectQuery))
} else {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id ASC`, guildInfoSelectQuery))
}
if err != nil {
s.logger.Error("Failed to retrieve guild by registration date", zap.Error(err))
} else {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_RANK:
sorting := bf.ReadUint16()
if sorting == 1 {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp DESC`, guildInfoSelectQuery))
} else {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp ASC`, guildInfoSelectQuery))
}
if err != nil {
s.logger.Error("Failed to retrieve guild by rank", zap.Error(err))
} else {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_MOTTO:
bf.ReadBytes(3)
mainMotto := 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)
if err != nil {
s.logger.Error("Failed to retrieve guild by motto", zap.Error(err))
} else {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_RECRUITING:
//
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))
}
@@ -1100,7 +1122,7 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
return
}
bf := byteframe.NewByteFrame()
bf = byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(guilds)))
for _, guild := range guilds {
@@ -1113,12 +1135,12 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(guild.MemberCount)
bf.WriteUint8(0x00) // Unk
bf.WriteUint8(0x00) // Unk
bf.WriteUint16(0x00) // Rank
bf.WriteUint16(guild.Rank)
bf.WriteUint32(uint32(guild.CreatedAt.Unix()))
bf.WriteUint8(uint8(len(guildName)))
bf.WriteBytes(guildName)
bf.WriteUint8(uint8(len(leaderName)))
bf.WriteBytes(leaderName)
bf.WriteUint8(uint8(len(guildName)+1))
bf.WriteNullTerminatedBytes(guildName)
bf.WriteUint8(uint8(len(leaderName)+1))
bf.WriteNullTerminatedBytes(leaderName)
bf.WriteUint8(0x01) // Unk
}
@@ -1203,12 +1225,14 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
name := s.clientContext.StrConv.MustEncode(member.Name)
bf.WriteUint32(member.CharID)
// Exp, HR[x] is split by 0, 1, 30, 50, 99, 299, 998, 999
bf.WriteUint16(member.Exp) // Rank flags
bf.WriteUint16(0x00) // Grank
bf.WriteUint16(0x00) // Unk
bf.WriteUint16(0x00) // Some rank?
bf.WriteUint16(member.HRP)
bf.WriteUint16(member.GR)
bf.WriteUint16(member.WeaponID)
if member.WeaponType == 1 || member.WeaponType == 5 || member.WeaponType == 10 { // If weapon is ranged
bf.WriteUint16(0x0700)
} else {
bf.WriteUint16(0x0600)
}
bf.WriteUint8(member.OrderIndex)
bf.WriteUint16(uint16(len(name) + 1))
bf.WriteNullTerminatedBytes(name)
@@ -1298,10 +1322,95 @@ func handleMsgMhfGetGuildTargetMemberNum(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfEnumerateGuildItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildItem)
var boxContents []byte
bf := byteframe.NewByteFrame()
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", int(pkt.GuildId)).Scan(&boxContents)
if err != nil {
s.logger.Fatal("Failed to get guild item box contents from db", zap.Error(err))
} else {
if len(boxContents) == 0 {
bf.WriteUint32(0x00)
} else {
amount := len(boxContents) / 4
bf.WriteUint16(uint16(amount))
bf.WriteUint32(0x00)
bf.WriteUint16(0x00)
for i := 0; i < amount; i++ {
bf.WriteUint32(binary.BigEndian.Uint32(boxContents[i*4:i*4+4]))
if i + 1 != amount {
bf.WriteUint64(0x00)
}
}
}
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
data, _ := hex.DecodeString("000100004cfa00010017000300000000")
type Item struct{
ItemId uint16
Amount uint16
}
doAckBufSucceed(s, pkt.AckHandle, data)
func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateGuildItem)
// Get item cache from DB
var boxContents []byte
var oldItems []Item
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", int(pkt.GuildId)).Scan(&boxContents)
if err != nil {
s.logger.Fatal("Failed to get guild item box contents from db", zap.Error(err))
} else {
amount := len(boxContents) / 4
oldItems = make([]Item, amount)
for i := 0; i < amount; i++ {
oldItems[i].ItemId = binary.BigEndian.Uint16(boxContents[i*4:i*4+2])
oldItems[i].Amount = binary.BigEndian.Uint16(boxContents[i*4+2:i*4+4])
}
}
// Update item stacks
newItems := make([]Item, len(oldItems))
copy(newItems, oldItems)
for i := 0; i < int(pkt.Amount); i++ {
for j := 0; j <= len(oldItems); j++ {
if j == len(oldItems) {
var newItem Item
newItem.ItemId = pkt.Items[i].ItemId
newItem.Amount = pkt.Items[i].Amount
newItems = append(newItems, newItem)
break
}
if pkt.Items[i].ItemId == oldItems[j].ItemId {
newItems[j].Amount = pkt.Items[i].Amount
break
}
}
}
// Delete empty item stacks
for i := len(newItems) - 1; i >= 0; i-- {
if int(newItems[i].Amount) == 0 {
copy(newItems[i:], newItems[i + 1:])
newItems[len(newItems) - 1] = make([]Item, 1)[0]
newItems = newItems[:len(newItems) - 1]
}
}
// Create new item cache
bf := byteframe.NewByteFrame()
for i := 0; i < len(newItems); i++ {
bf.WriteUint16(newItems[i].ItemId)
bf.WriteUint16(newItems[i].Amount)
}
// Upload new item cache
_, err = s.server.db.Exec("UPDATE guilds SET item_box = $1 WHERE id = $2", bf.Data(), int(pkt.GuildId))
if err != nil {
s.logger.Fatal("Failed to update guild item box contents in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfUpdateGuildIcon(s *Session, p mhfpacket.MHFPacket) {
@@ -1337,8 +1446,12 @@ func handleMsgMhfUpdateGuildIcon(s *Session, p mhfpacket.MHFPacket) {
icon.Parts[i] = GuildIconPart{
Index: p.Index,
ID: p.ID,
Page: p.Page,
Size: p.Size,
Rotation: p.Rotation,
Red: p.Red,
Green: p.Green,
Blue: p.Blue,
PosX: p.PosX,
PosY: p.PosY,
}
@@ -1434,16 +1547,182 @@ func handleMsgMhfGetGuildWeeklyBonusActiveCount(s *Session, p mhfpacket.MHFPacke
func handleMsgMhfGuildHuntdata(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGuildHuntdata)
data := []byte{0x01, 0xFE}
doAckBufSucceed(s, pkt.AckHandle, data)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
// "Enumrate_guild_msg_board"
func handleMsgSysReserve202(s *Session, p mhfpacket.MHFPacket) {}
type MessageBoardPost struct {
Type uint32 `db:"post_type"`
StampID uint32 `db:"stamp_id"`
Title string `db:"title"`
Body string `db:"body"`
AuthorID uint32 `db:"author_id"`
Timestamp uint64 `db:"created_at"`
LikedBy string `db:"liked_by"`
}
// "Is_update_guild_msg_board"
func handleMsgSysReserve203(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysReserve203)
func handleMsgMhfEnumerateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildMessageBoard)
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
msgs, err := s.server.db.Queryx("SELECT post_type, stamp_id, title, body, author_id, (EXTRACT(epoch FROM created_at)::int) as created_at, liked_by FROM guild_posts WHERE guild_id = $1 AND post_type = $2 ORDER BY created_at DESC", guild.ID, int(pkt.BoardType))
if err != nil {
s.logger.Fatal("Failed to get guild messages from db", zap.Error(err))
}
bf := byteframe.NewByteFrame()
noMsgs := true
postCount := 0
for msgs.Next() {
noMsgs = false
postCount++
postData := &MessageBoardPost{}
err = msgs.StructScan(&postData)
if err != nil {
s.logger.Error("Failed to read guild message board post")
}
bf.WriteUint32(postData.Type)
bf.WriteUint32(postData.AuthorID)
bf.WriteUint64(postData.Timestamp)
liked := false
likedBySlice := strings.Split(postData.LikedBy, ",")
for i := 0; i < len(likedBySlice); i++ {
j, _ := strconv.ParseInt(likedBySlice[i], 10, 64)
if int(j) == int(s.charID) {
liked = true; break
}
}
if likedBySlice[0] == "" {
bf.WriteUint32(0)
} else {
bf.WriteUint32(uint32(len(likedBySlice)))
}
bf.WriteBool(liked)
bf.WriteUint32(postData.StampID)
bf.WriteUint32(uint32(len(postData.Title)))
bf.WriteBytes([]byte(postData.Title))
bf.WriteUint32(uint32(len(postData.Body)))
bf.WriteBytes([]byte(postData.Body))
}
if noMsgs {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
} else {
data := byteframe.NewByteFrame()
data.WriteUint32(uint32(postCount))
data.WriteBytes(bf.Data())
doAckBufSucceed(s, pkt.AckHandle, data.Data())
}
}
func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateGuildMessageBoard)
bf := byteframe.NewByteFrameFromBytes(pkt.Request)
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
if guild == nil {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
switch pkt.MessageOp {
case 0: // Create message
postType := bf.ReadUint32() // 0 = message, 1 = news
stampId := bf.ReadUint32()
titleLength := bf.ReadUint32()
bodyLength := bf.ReadUint32()
title := bf.ReadBytes(uint(titleLength))
body := bf.ReadBytes(uint(bodyLength))
_, err := s.server.db.Exec("INSERT INTO guild_posts (guild_id, author_id, stamp_id, post_type, title, body) VALUES ($1, $2, $3, $4, $5, $6)", guild.ID, s.charID, int(stampId), int(postType), string(title), string(body))
if err != nil {
s.logger.Fatal("Failed to add new guild message to db", zap.Error(err))
}
// TODO: if there are too many messages, purge excess
_, err = s.server.db.Exec("")
if err != nil {
s.logger.Fatal("Failed to remove excess guild messages from db", zap.Error(err))
}
case 1: // Delete message
postType := bf.ReadUint32()
timestamp := bf.ReadUint64()
_, err := s.server.db.Exec("DELETE FROM guild_posts WHERE post_type = $1 AND (EXTRACT(epoch FROM created_at)::int) = $2 AND guild_id = $3", int(postType), int(timestamp), guild.ID)
if err != nil {
s.logger.Fatal("Failed to delete guild message from db", zap.Error(err))
}
case 2: // Update message
postType := bf.ReadUint32()
timestamp := bf.ReadUint64()
titleLength := bf.ReadUint32()
bodyLength := bf.ReadUint32()
title := bf.ReadBytes(uint(titleLength))
body := bf.ReadBytes(uint(bodyLength))
_, err := s.server.db.Exec("UPDATE guild_posts SET title = $1, body = $2 WHERE post_type = $3 AND (EXTRACT(epoch FROM created_at)::int) = $4 AND guild_id = $5", string(title), string(body), int(postType), int(timestamp), guild.ID)
if err != nil {
s.logger.Fatal("Failed to update guild message in db", zap.Error(err))
}
case 3: // Update stamp
postType := bf.ReadUint32()
timestamp := bf.ReadUint64()
stampId := bf.ReadUint32()
_, err := s.server.db.Exec("UPDATE guild_posts SET stamp_id = $1 WHERE post_type = $2 AND (EXTRACT(epoch FROM created_at)::int) = $3 AND guild_id = $4", int(stampId), int(postType), int(timestamp), guild.ID)
if err != nil {
s.logger.Fatal("Failed to update guild message stamp in db", zap.Error(err))
}
case 4: // Like message
postType := bf.ReadUint32()
timestamp := bf.ReadUint64()
likeState := bf.ReadBool()
var likedBy string
err := s.server.db.QueryRow("SELECT liked_by FROM guild_posts WHERE post_type = $1 AND (EXTRACT(epoch FROM created_at)::int) = $2 AND guild_id = $3", int(postType), int(timestamp), guild.ID).Scan(&likedBy)
if err != nil {
s.logger.Fatal("Failed to get guild message like data from db", zap.Error(err))
} else {
if likeState {
if len(likedBy) == 0 {
likedBy = strconv.Itoa(int(s.charID))
} else {
likedBy += "," + strconv.Itoa(int(s.charID))
}
_, err := s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE post_type = $2 AND (EXTRACT(epoch FROM created_at)::int) = $3 AND guild_id = $4", likedBy, int(postType), int(timestamp), guild.ID)
if err != nil {
s.logger.Fatal("Failed to like guild message in db", zap.Error(err))
}
} else {
likedBySlice := strings.Split(likedBy, ",")
for i, e := range likedBySlice {
if e == strconv.Itoa(int(s.charID)) {
likedBySlice[i] = likedBySlice[len(likedBySlice) - 1]
likedBySlice = likedBySlice[:len(likedBySlice) - 1]
}
}
likedBy = strings.Join(likedBySlice, ",")
_, err := s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE post_type = $2 AND (EXTRACT(epoch FROM created_at)::int) = $3 AND guild_id = $4", likedBy, int(postType), int(timestamp), guild.ID)
if err != nil {
s.logger.Fatal("Failed to unlike guild message in db", zap.Error(err))
}
}
}
case 5: // Check for new messages
var timeChecked int
var newPosts int
err := s.server.db.QueryRow("SELECT (EXTRACT(epoch FROM guild_post_checked)::int) FROM characters WHERE id = $1", s.charID).Scan(&timeChecked)
if err != nil {
s.logger.Fatal("Failed to get last guild post check timestamp from db", zap.Error(err))
} else {
_, err = s.server.db.Exec("UPDATE characters SET guild_post_checked = $1 WHERE id = $2", time.Now(), s.charID)
if err != nil {
s.logger.Fatal("Failed to update guild post check timestamp in db", zap.Error(err))
} else {
err = s.server.db.QueryRow("SELECT COUNT(*) FROM guild_posts WHERE guild_id = $1 AND (EXTRACT(epoch FROM created_at)::int) > $2", guild.ID, timeChecked).Scan(&newPosts)
if err != nil {
s.logger.Fatal("Failed to check for new guild posts in db", zap.Error(err))
} else {
if newPosts > 0 {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
return
}
}
}
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
@@ -1451,7 +1730,12 @@ func handleMsgMhfEntryRookieGuild(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfUpdateForceGuildRank(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAddGuildWeeklyBonusExceptionalUser(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAddGuildWeeklyBonusExceptionalUser(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAddGuildWeeklyBonusExceptionalUser)
// TODO: record pkt.NumUsers to DB
// must use addition
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfRegistGuildAdventure(s *Session, p mhfpacket.MHFPacket) {}
@@ -1461,7 +1745,10 @@ func handleMsgMhfChargeGuildAdventure(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAddGuildMissionCount(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetGuildMissionTarget(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetGuildMissionTarget(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSetGuildMissionTarget)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfCancelGuildMissionTarget(s *Session, p mhfpacket.MHFPacket) {}
@@ -1477,4 +1764,8 @@ func handleMsgMhfOperationInvGuild(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfUpdateGuildcard(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfCreateJoint(s *Session, p mhfpacket.MHFPacket) { }
func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) { }
func handleMsgMhfInfoJoint(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -18,7 +18,10 @@ type GuildMember struct {
LastLogin uint32 `db:"last_login"`
AvoidLeadership bool `db:"avoid_leadership"`
IsLeader bool `db:"is_leader"`
Exp uint16 `db:"exp"`
HRP uint16 `db:"hrp"`
GR uint16 `db:"gr"`
WeaponID uint16 `db:"weapon_id"`
WeaponType uint16 `db:"weapon_type"`
}
func (gm *GuildMember) IsSubLeader() bool {
@@ -53,7 +56,10 @@ SELECT g.id as guild_id,
coalesce(gc.order_index, 0) as order_index,
c.last_login,
coalesce(gc.avoid_leadership, false) as avoid_leadership,
c.exp,
c.hrp,
c.gr,
c.weapon_id,
c.weapon_type,
character.is_applicant,
CASE WHEN g.leader_id = c.id THEN 1 ELSE 0 END as is_leader
FROM (

View File

@@ -93,6 +93,23 @@ func (m *Mail) MarkDeleted(s *Session) error {
return nil
}
func (m *Mail) MarkAcquired(s *Session) error {
_, err := s.server.db.Exec(`
UPDATE mail SET attached_item_received = true WHERE id = $1
`, m.ID)
if err != nil {
s.logger.Error(
"failed to mark mail item as claimed",
zap.Error(err),
zap.Int("mailID", m.ID),
)
return err
}
return nil
}
func GetMailListForCharacter(s *Session, charID uint32) ([]Mail, error) {
rows, err := s.server.db.Queryx(`
SELECT
@@ -101,6 +118,7 @@ func GetMailListForCharacter(s *Session, charID uint32) ([]Mail, error) {
m.recipient_id,
m.subject,
m.read,
m.attached_item_received,
m.attached_item,
m.attached_item_amount,
m.created_at,
@@ -147,6 +165,7 @@ func GetMailByID(s *Session, ID int) (*Mail, error) {
m.subject,
m.read,
m.body,
m.attached_item_received,
m.attached_item,
m.attached_item_amount,
m.created_at,
@@ -235,9 +254,11 @@ func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) {
_ = mail.MarkRead(s)
bf := byteframe.NewByteFrame()
bodyBytes, _ := stringsupport.ConvertUTF8ToShiftJIS(mail.Body)
bf.WriteNullTerminatedBytes(bodyBytes)
doAckBufSucceed(s, pkt.AckHandle, bodyBytes)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
@@ -295,10 +316,10 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
msg.WriteUint8(flags)
msg.WriteBool(itemAttached)
msg.WriteUint8(uint8(len(subjectBytes)))
msg.WriteUint8(uint8(len(senderNameBytes)))
msg.WriteBytes(subjectBytes)
msg.WriteBytes(senderNameBytes)
msg.WriteUint8(uint8(len(subjectBytes)+1))
msg.WriteUint8(uint8(len(senderNameBytes)+1))
msg.WriteNullTerminatedBytes(subjectBytes)
msg.WriteNullTerminatedBytes(senderNameBytes)
if itemAttached {
msg.WriteInt16(m.AttachedItemAmount)
@@ -320,9 +341,14 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) {
}
switch mhfpacket.OperateMailOperation(pkt.Operation) {
case mhfpacket.OperateMailOperationDelete:
case mhfpacket.OPERATE_MAIL_DELETE:
err = mail.MarkDeleted(s)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
case mhfpacket.OPERATE_MAIL_ACQUIRE_ITEM:
err = mail.MarkAcquired(s)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
@@ -332,4 +358,34 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, nil)
}
func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSendMail)
query := `
INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite)
VALUES ($1, $2, $3, $4, $5, $6, $7)
`
if pkt.RecipientID == 0 { // Guild mail
g, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
s.logger.Fatal("Failed to get guild info for mail")
}
gm, err := GetGuildMembers(s, g.ID, false)
if err != nil {
s.logger.Fatal("Failed to get guild members for mail")
}
for i := 0; i < len(gm); i++ {
_, err := s.server.db.Exec(query, s.charID, gm[i].CharID, pkt.Subject, pkt.Body, 0, 0, false)
if err != nil {
s.logger.Fatal("Failed to send mail")
}
}
} else {
_, err := s.server.db.Exec(query, s.charID, pkt.RecipientID, pkt.Subject, pkt.Body, pkt.ItemID, pkt.Quantity, false)
if err != nil {
s.logger.Fatal("Failed to send mail")
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,13 +12,35 @@ func handleMsgSysCreateSemaphore(s *Session, p mhfpacket.MHFPacket) {
}
func handleMsgSysDeleteSemaphore(s *Session, p mhfpacket.MHFPacket) {
//pkt := p.(*mhfpacket.MsgSysDeleteSemaphore)
s.semaphore.Lock()
pkt := p.(*mhfpacket.MsgSysDeleteSemaphore)
sem := pkt.AckHandle
if s.server.semaphore != nil {
s.server.semaphoreLock.Lock()
for id := range s.server.semaphore {
switch sem {
case 917533:
if s.server.semaphore[id].id_semaphore == "hs_l0u3B51J9k3" {
delete(s.server.semaphore["hs_l0u3B51J9k3"].reservedClientSlots, s.charID)
delete(s.server.semaphore["hs_l0u3B51J9k3"].clients, s)
}
case 851997:
if s.server.semaphore[id].id_semaphore == "hs_l0u3B51J9k4" {
delete(s.server.semaphore["hs_l0u3B51J9k4"].reservedClientSlots, s.charID)
}
case 786461:
if s.server.semaphore[id].id_semaphore == "hs_l0u3B51J9k5" {
delete(s.server.semaphore["hs_l0u3B51J9k5"].reservedClientSlots, s.charID)
}
default:
if len(s.server.semaphore[id].reservedClientSlots) != 0 {
if s.server.semaphore[id].id_semaphore != "hs_l0u3B51J9k3" && s.server.semaphore[id].id_semaphore != "hs_l0u3B51J9k4" && s.server.semaphore[id].id_semaphore != "hs_l0u3B51J9k5" {
delete(s.server.semaphore[id].reservedClientSlots, s.charID)
}
s.semaphore.Unlock()
}
}
}
s.server.semaphoreLock.Unlock()
}
}
func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
@@ -49,11 +71,33 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
s.logger.Info("IS ALREADY EXIST !")
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x0F, 0x00, 0x1D})
} else if uint16(len(newSemaphore.reservedClientSlots)) < newSemaphore.maxPlayers {
switch SemaphoreID {
case "hs_l0u3B51J9k3":
newSemaphore.reservedClientSlots[s.charID] = nil
newSemaphore.clients[s] = s.charID
s.Lock()
s.semaphore = newSemaphore
s.Unlock()
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x0E, 0x00, 0x1D})
case "hs_l0u3B51J9k4":
newSemaphore.reservedClientSlots[s.charID] = nil
s.Lock()
s.semaphore = newSemaphore
s.Unlock()
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x0F, 0x00, 0x1D})
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x0D, 0x00, 0x1D})
case "hs_l0u3B51J9k5":
newSemaphore.reservedClientSlots[s.charID] = nil
s.Lock()
s.semaphore = newSemaphore
s.Unlock()
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x0C, 0x00, 0x1D})
default:
newSemaphore.reservedClientSlots[s.charID] = nil
s.Lock()
s.semaphore = newSemaphore
s.Unlock()
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x0F, 0x00, 0x25})
}
} else {
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
@@ -65,15 +109,26 @@ func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
func handleMsgSysReleaseSemaphore(s *Session, p mhfpacket.MHFPacket) {
//pkt := p.(*mhfpacket.MsgSysReleaseSemaphore)
for _, session := range s.server.sessions {
session.semaphore.Lock()
for id := range session.server.semaphore {
if _, exists := s.server.semaphore["hs_l0u3B51J9k3"]; exists {
reset := len(s.server.semaphore["hs_l0u3B51J9k3"].reservedClientSlots)
if reset == 0 {
s.server.db.Exec("CALL ravireset($1)", 0)
}
}
}
func removeSessionFromSemaphore(s *Session) {
s.server.semaphoreLock.Lock()
for id := range s.server.semaphore {
delete(s.server.semaphore[id].reservedClientSlots, s.charID)
if id == "hs_l0u3B51J9k3" {
delete(s.server.semaphore[id].clients, s)
} else {
continue
}
session.semaphore.Unlock()
}
//data, _ := hex.DecodeString("000180e703000d443b37ff006d00131809000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010627426400a936a93600000100cf330600cc31cc31d431000025000000000000000000010218330600bd3cbd3cbd3c01032c280600ee3dee3da9360104f3300600d231a936a93601054a310600e23ae23ae23a00000d0000000000004d814c0000000003008501d723b7334001e7038b3fd437d516113505000000e7030001000002000203000000000000fafafafafafafafafafafafafafa000000000000ecb2000060da0000000000000000000000000000000000000000000000000000000000000000000000000000181818187e2d00003b31702d662d402e000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000")
//doAckBufSucceed(s, pkt.AckHandle, data)
s.server.semaphoreLock.Unlock()
}
func handleMsgSysCheckSemaphore(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -429,8 +429,8 @@ func init() {
handlerTable[network.MSG_SYS_reserve19F] = handleMsgSysReserve19F
handlerTable[network.MSG_MHF_UPDATE_FORCE_GUILD_RANK] = handleMsgMhfUpdateForceGuildRank
handlerTable[network.MSG_MHF_RESET_TITLE] = handleMsgMhfResetTitle
handlerTable[network.MSG_SYS_reserve202] = handleMsgSysReserve202
handlerTable[network.MSG_SYS_reserve203] = handleMsgSysReserve203
handlerTable[network.MSG_MHF_ENUMERATE_GUILD_MESSAGE_BOARD] = handleMsgMhfEnumerateGuildMessageBoard
handlerTable[network.MSG_MHF_UPDATE_GUILD_MESSAGE_BOARD] = handleMsgMhfUpdateGuildMessageBoard
handlerTable[network.MSG_SYS_reserve204] = handleMsgSysReserve204
handlerTable[network.MSG_SYS_reserve205] = handleMsgSysReserve205
handlerTable[network.MSG_SYS_reserve206] = handleMsgSysReserve206

View File

@@ -48,7 +48,12 @@ func handleMsgMhfGetUdTacticsFirstQuestBonus(s *Session, p mhfpacket.MHFPacket)
func handleMsgMhfGetUdTacticsRemainingPoint(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetUdTacticsRanking(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetUdTacticsRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdTacticsRanking)
// Temporary canned response
data, _ := hex.DecodeString("00000515000005150000CEB4000003CE000003CE0000CEB44D49444E494748542D414E47454C0000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfSetUdTacticsFollower(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -1,6 +1,9 @@
package channelserver
import (
"github.com/Andoryuuta/byteframe"
"github.com/Solenataris/Erupe/network/mhfpacket"
"sync"
)
@@ -11,6 +14,10 @@ type Semaphore struct {
// Stage ID string
id_semaphore string
// Map of session -> charID.
// These are clients that are CURRENTLY in the stage
clients map[*Session]uint32
// Map of charID -> interface{}, only the key is used, value is always nil.
reservedClientSlots map[uint32]interface{}
@@ -22,8 +29,46 @@ type Semaphore struct {
func NewSemaphore(ID string, MaxPlayers uint16) *Semaphore {
s := &Semaphore{
id_semaphore: ID,
clients: make(map[*Session]uint32),
reservedClientSlots: make(map[uint32]interface{}),
maxPlayers: MaxPlayers,
}
return s
}
func (s *Semaphore) BroadcastRavi(pkt mhfpacket.MHFPacket) {
// Broadcast the data.
for session := range s.clients {
// Make the header
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
// Build the packet onto the byteframe.
pkt.Build(bf, session.clientContext)
// Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full.
session.QueueSendNonBlocking(bf.Data())
}
}
// BroadcastMHF queues a MHFPacket to be sent to all sessions in the stage.
func (s *Semaphore) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
// Broadcast the data.
for session := range s.clients {
if session == ignoredSession {
continue
}
// Make the header
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
// Build the packet onto the byteframe.
pkt.Build(bf, session.clientContext)
// Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full.
session.QueueSendNonBlocking(bf.Data())
}
}

View File

@@ -32,6 +32,8 @@ type Session struct {
reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet.
charID uint32
logKey []byte
sessionStart int64
rights uint32
semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet.
@@ -62,6 +64,7 @@ func NewSession(server *Server, conn net.Conn) *Session {
Encoding: japanese.ShiftJIS,
},
},
sessionStart: Time_Current_Adjusted().Unix(),
stageMoveStack: stringstack.New(),
}
return s
@@ -177,6 +180,7 @@ func (s *Session) handlePacketGroup(pktGroup []byte) {
opcode != network.MSG_SYS_PING &&
opcode != network.MSG_SYS_NOP &&
opcode != network.MSG_SYS_TIME &&
opcode != network.MSG_SYS_POSITION_OBJECT &&
opcode != network.MSG_SYS_EXTEND_THRESHOLD {
fmt.Printf("[%s] send to Server\n", s.Name)
fmt.Printf("Opcode: %s\n", opcode)

View File

@@ -26,9 +26,9 @@ func (s *Server) newUserChara(username string) error {
_, err = s.db.Exec(`
INSERT INTO characters (
user_id, is_female, is_new_character, small_gr_level, gr_override_mode, name, unk_desc_string,
gr_override_level, gr_override_unk0, gr_override_unk1, exp, weapon, last_login)
VALUES($1, False, True, 0, True, '', '', 0, 0, 0, 0, 0, $2)`,
user_id, is_female, is_new_character, name, unk_desc_string,
hrp, gr, weapon_type, last_login)
VALUES($1, False, True, '', '', 1, 0, 0, $2)`,
id,
uint32(time.Now().Unix()),
)
@@ -60,9 +60,9 @@ func (s *Server) registerDBAccount(username string, password string) error {
// Create a base new character.
_, err = s.db.Exec(`
INSERT INTO characters (
user_id, is_female, is_new_character, small_gr_level, gr_override_mode, name, unk_desc_string,
gr_override_level, gr_override_unk0, gr_override_unk1, exp, weapon, last_login)
VALUES($1, False, True, 0, True, '', '', 0, 0, 0, 0, 0, $2)`,
user_id, is_female, is_new_character, name, unk_desc_string,
hrp, gr, weapon_type, last_login)
VALUES($1, False, True, '', '', 1, 0, 0, $2)`,
id,
uint32(time.Now().Unix()),
)
@@ -77,21 +77,17 @@ type character struct {
ID uint32 `db:"id"`
IsFemale bool `db:"is_female"`
IsNewCharacter bool `db:"is_new_character"`
SmallGRLevel uint8 `db:"small_gr_level"`
GROverrideMode bool `db:"gr_override_mode"`
Name string `db:"name"`
UnkDescString string `db:"unk_desc_string"`
GROverrideLevel uint16 `db:"gr_override_level"`
GROverrideUnk0 uint8 `db:"gr_override_unk0"`
GROverrideUnk1 uint8 `db:"gr_override_unk1"`
Exp uint16 `db:"exp"`
Weapon uint16 `db:"weapon"`
HRP uint16 `db:"hrp"`
GR uint16 `db:"gr"`
WeaponType uint16 `db:"weapon_type"`
LastLogin uint32 `db:"last_login"`
}
func (s *Server) getCharactersForUser(uid int) ([]character, error) {
characters := []character{}
err := s.db.Select(&characters, "SELECT id, is_female, is_new_character, small_gr_level, gr_override_mode, name, unk_desc_string, gr_override_level, gr_override_unk0, gr_override_unk1, exp, weapon, last_login FROM characters WHERE user_id = $1", uid)
err := s.db.Select(&characters, "SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE user_id = $1", uid)
if err != nil {
return nil, err
}

View File

@@ -2,6 +2,8 @@ package signserver
import (
"fmt"
"math/rand"
"time"
"github.com/Andoryuuta/byteframe"
"go.uber.org/zap"
@@ -32,6 +34,15 @@ func makeSignInFailureResp(respID RespID) []byte {
return bf.Data()
}
func randSeq(n int) string {
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
func (s *Session) makeSignInResp(uid int) []byte {
// Get the characters from the DB.
chars, err := s.server.getCharactersForUser(uid)
@@ -39,6 +50,10 @@ func (s *Session) makeSignInResp(uid int) []byte {
s.logger.Warn("Error getting characters from DB", zap.Error(err))
}
rand.Seed(time.Now().UnixNano())
token := randSeq(16)
// TODO: register token to db, users table
bf := byteframe.NewByteFrame()
bf.WriteUint8(1) // resp_code
@@ -46,7 +61,7 @@ func (s *Session) makeSignInResp(uid int) []byte {
bf.WriteUint8(4) // entrance server count
bf.WriteUint8(uint8(len(chars))) // character count
bf.WriteUint32(0xFFFFFFFF) // login_token_number
bf.WriteBytes(paddedString("logintokenstrng", 16)) // login_token (16 byte padded string)
bf.WriteBytes(paddedString(token, 16)) // login_token (16 byte padded string)
bf.WriteUint32(1576761190)
uint8PascalString(bf, fmt.Sprintf("%s:%d", s.server.erupeConfig.HostIP, s.server.erupeConfig.Entrance.Port))
uint8PascalString(bf, "")
@@ -60,24 +75,18 @@ func (s *Session) makeSignInResp(uid int) []byte {
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.MaxLauncherHR {
bf.WriteUint16(999)
} else {
bf.WriteUint16(char.Exp)
bf.WriteUint16(char.HRP)
}
bf.WriteUint16(char.Weapon) // Weapon, 0-13.
bf.WriteUint16(char.WeaponType) // Weapon, 0-13.
bf.WriteUint32(char.LastLogin) // Last login date, unix timestamp in seconds.
bf.WriteBool(char.IsFemale) // Sex, 0=male, 1=female.
bf.WriteBool(char.IsNewCharacter) // Is new character, 1 replaces character name with ?????.
bf.WriteUint8(char.SmallGRLevel) // GR level if grMode == 0
bf.WriteBool(char.GROverrideMode) // GR mode.
bf.WriteUint8(0) // Old GR
bf.WriteBool(true) // Use uint16 GR, no reason not to
bf.WriteBytes(paddedString(char.Name, 16)) // Character name
bf.WriteBytes(paddedString(char.UnkDescString, 32)) // unk str
if char.GROverrideMode {
bf.SetLE()
bf.WriteUint16(char.GROverrideLevel) // GR level override.
bf.SetBE()
bf.WriteUint8(char.GROverrideUnk0) // unk
bf.WriteUint8(char.GROverrideUnk1) // unk
}
bf.WriteUint16(char.GR)
bf.WriteUint16(0) // Unk
}
bf.WriteUint8(0) // friends_list_count

View File

@@ -72,11 +72,20 @@
</div>
<ul class="article">
<li>
<div class="date">2022-05-03</div>
<div class="body">
<a
href="javascript:toggleModal('openLink',&quot;https://discord.com/channels/368424389416583169/929509970624532511/969305400795078656&quot;);"
onclick="soundOk()">Eng 2.0 &amp; Ravi Patch Released!
</a>
</div>
</li>
<li>
<div class="date">2022-04-24</div>
<div class="body">
<a
href="javascript:toggleModal('openLink',&quot;https://discord.com/channels/368424389416583169/929509970624532511/964339905364918272&quot;);"
onclick="soundOk()">Launcher Patch v1.0 Released!
href="javascript:toggleModal('openLink',&quot;https://discord.com/channels/368424389416583169/929509970624532511/969286397301248050&quot;);"
onclick="soundOk()">Launcher Patch V1.0 Released!
</a>
</div>
</li>
@@ -92,7 +101,7 @@
<div class="body">
<a
href="javascript:toggleModal('openLink',&quot;https://discord.gg/CFnzbhQ&quot;);"
onclick="soundOk()">Join the community discord for updates!
onclick="soundOk()">Join the community Discord for updates!
</a>
</div>
</li>

View File

@@ -108,7 +108,7 @@ function createCharItem(name, uid, weapon, hr, gr, date, sex) {
icon = 'img/icons/ss.png';
break;
case '双剣':
weapon = 'Dual Blades';
weapon = 'Dual Swords';
icon = 'img/icons/db.png';
break;
case '大剣':
@@ -116,7 +116,7 @@ function createCharItem(name, uid, weapon, hr, gr, date, sex) {
icon = 'img/icons/gs.png';
break;
case '太刀':
weapon = 'Long Sword';
weapon = 'Longsword';
icon = 'img/icons/ls.png';
break;
case 'ハンマー':
@@ -139,7 +139,7 @@ function createCharItem(name, uid, weapon, hr, gr, date, sex) {
weapon = 'Tonfa';
icon = 'img/icons/tf.png';
break;
case 'スラッシュアックスF':
case 'スラッシュアックス':
weapon = 'Switch Axe F';
icon = 'img/icons/sa.png';
break;
@@ -272,7 +272,7 @@ function doLogin(option) {
addLog('Creating new character...', 'normal');
window.external.loginCog(username+'+', password, password);
} else {
window.external.loginCog(username, password, password);
window.external.loginCog(username, password, 'test');
}
} catch (e) {
addLog('Error on loginCog: '+e, 'error');