diff --git a/config.json b/config.json index d79aa2a51..4bc051030 100644 --- a/config.json +++ b/config.json @@ -83,11 +83,6 @@ "Password": "", "Database": "erupe" }, - "Launcher": { - "Enabled": false, - "Port": 80, - "UseOriginalLauncherFiles": false - }, "Sign": { "Enabled": true, "Port": 53312 diff --git a/config/config.go b/config/config.go index 749eaaa81..ebfa6d4d6 100644 --- a/config/config.go +++ b/config/config.go @@ -30,7 +30,6 @@ type Config struct { Commands []Command Courses []Course Database Database - Launcher Launcher Sign Sign SignV2 SignV2 Channel Channel @@ -90,13 +89,6 @@ type Database struct { Database string } -// Launcher holds the launcher server config. -type Launcher struct { - Enabled bool - Port int - UseOriginalLauncherFiles bool -} - // Sign holds the sign server config. type Sign struct { Enabled bool diff --git a/main.go b/main.go index a9f4845be..02f699b30 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,6 @@ import ( "erupe-ce/server/channelserver" "erupe-ce/server/discordbot" "erupe-ce/server/entranceserver" - "erupe-ce/server/launcherserver" "erupe-ce/server/signserver" "erupe-ce/server/signv2server" @@ -116,23 +115,6 @@ func main() { // Now start our server(s). - // Launcher HTTP server. - var launcherServer *launcherserver.Server - if config.ErupeConfig.Launcher.Enabled { - launcherServer = launcherserver.NewServer( - &launcherserver.Config{ - Logger: logger.Named("launcher"), - ErupeConfig: config.ErupeConfig, - DB: db, - UseOriginalLauncherFiles: config.ErupeConfig.Launcher.UseOriginalLauncherFiles, - }) - err = launcherServer.Start() - if err != nil { - preventClose(fmt.Sprintf("Failed to start launcher server: %s", err.Error())) - } - logger.Info("Started launcher server") - } - // Entrance server. var entranceServer *entranceserver.Server @@ -254,10 +236,6 @@ func main() { entranceServer.Shutdown() } - if config.ErupeConfig.Launcher.Enabled { - launcherServer.Shutdown() - } - time.Sleep(1 * time.Second) } diff --git a/network/mhfpacket/msg_mhf_contract_mercenary.go b/network/mhfpacket/msg_mhf_contract_mercenary.go index 24919981b..1dad97253 100644 --- a/network/mhfpacket/msg_mhf_contract_mercenary.go +++ b/network/mhfpacket/msg_mhf_contract_mercenary.go @@ -1,15 +1,20 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfContractMercenary represents the MSG_MHF_CONTRACT_MERCENARY -type MsgMhfContractMercenary struct{} +type MsgMhfContractMercenary struct { + AckHandle uint32 + PactMercID uint32 + CID uint32 + Op uint8 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfContractMercenary) Opcode() network.PacketID { @@ -18,7 +23,11 @@ func (m *MsgMhfContractMercenary) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfContractMercenary) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.PactMercID = bf.ReadUint32() + m.CID = bf.ReadUint32() + m.Op = bf.ReadUint8() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_get_box_gacha_info.go b/network/mhfpacket/msg_mhf_get_box_gacha_info.go index 54e7eb111..b944e6c11 100644 --- a/network/mhfpacket/msg_mhf_get_box_gacha_info.go +++ b/network/mhfpacket/msg_mhf_get_box_gacha_info.go @@ -1,17 +1,17 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfGetBoxGachaInfo represents the MSG_MHF_GET_BOX_GACHA_INFO -type MsgMhfGetBoxGachaInfo struct{ +type MsgMhfGetBoxGachaInfo struct { AckHandle uint32 - GachaHash uint32 + GachaID uint32 } // Opcode returns the ID associated with this packet type. @@ -22,7 +22,7 @@ func (m *MsgMhfGetBoxGachaInfo) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfGetBoxGachaInfo) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.GachaHash = bf.ReadUint32() + m.GachaID = bf.ReadUint32() return nil } diff --git a/network/mhfpacket/msg_mhf_get_stepup_status.go b/network/mhfpacket/msg_mhf_get_stepup_status.go index 1f04c7740..dcced8f4f 100644 --- a/network/mhfpacket/msg_mhf_get_stepup_status.go +++ b/network/mhfpacket/msg_mhf_get_stepup_status.go @@ -1,18 +1,18 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfGetStepupStatus represents the MSG_MHF_GET_STEPUP_STATUS -type MsgMhfGetStepupStatus struct{ +type MsgMhfGetStepupStatus struct { AckHandle uint32 - GachaHash uint32 - Unk uint8 + GachaID uint32 + Unk uint8 } // Opcode returns the ID associated with this packet type. @@ -23,7 +23,7 @@ func (m *MsgMhfGetStepupStatus) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfGetStepupStatus) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.GachaHash = bf.ReadUint32() + m.GachaID = bf.ReadUint32() m.Unk = bf.ReadUint8() return nil } diff --git a/network/mhfpacket/msg_mhf_get_weekly_seibatu_ranking_reward.go b/network/mhfpacket/msg_mhf_get_weekly_seibatu_ranking_reward.go index 3b8b07c01..2823ff525 100644 --- a/network/mhfpacket/msg_mhf_get_weekly_seibatu_ranking_reward.go +++ b/network/mhfpacket/msg_mhf_get_weekly_seibatu_ranking_reward.go @@ -1,15 +1,21 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfGetWeeklySeibatuRankingReward represents the MSG_MHF_GET_WEEKLY_SEIBATU_RANKING_REWARD -type MsgMhfGetWeeklySeibatuRankingReward struct{} +type MsgMhfGetWeeklySeibatuRankingReward struct { + AckHandle uint32 + Unk0 uint32 + Unk1 uint32 + Unk2 uint32 + Unk3 uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfGetWeeklySeibatuRankingReward) Opcode() network.PacketID { @@ -18,7 +24,12 @@ func (m *MsgMhfGetWeeklySeibatuRankingReward) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfGetWeeklySeibatuRankingReward) 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() + m.Unk3 = bf.ReadUint32() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_info_tournament.go b/network/mhfpacket/msg_mhf_info_tournament.go index d91f2d6f1..9da465add 100644 --- a/network/mhfpacket/msg_mhf_info_tournament.go +++ b/network/mhfpacket/msg_mhf_info_tournament.go @@ -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" ) // MsgMhfInfoTournament represents the MSG_MHF_INFO_TOURNAMENT -type MsgMhfInfoTournament struct{} +type MsgMhfInfoTournament struct { + AckHandle uint32 + Unk0 uint8 + Unk1 uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfInfoTournament) Opcode() network.PacketID { @@ -18,7 +22,10 @@ func (m *MsgMhfInfoTournament) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfInfoTournament) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.Unk0 = bf.ReadUint8() + m.Unk1 = bf.ReadUint32() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_play_box_gacha.go b/network/mhfpacket/msg_mhf_play_box_gacha.go index c47100e6f..f09c018f8 100644 --- a/network/mhfpacket/msg_mhf_play_box_gacha.go +++ b/network/mhfpacket/msg_mhf_play_box_gacha.go @@ -1,19 +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" ) // MsgMhfPlayBoxGacha represents the MSG_MHF_PLAY_BOX_GACHA -type MsgMhfPlayBoxGacha struct{ +type MsgMhfPlayBoxGacha struct { AckHandle uint32 - GachaHash uint32 - RollType uint8 - CurrencyMode uint8 + GachaID uint32 + RollType uint8 + GachaType uint8 } // Opcode returns the ID associated with this packet type. @@ -24,9 +24,9 @@ func (m *MsgMhfPlayBoxGacha) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfPlayBoxGacha) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.GachaHash = bf.ReadUint32() + m.GachaID = bf.ReadUint32() m.RollType = bf.ReadUint8() - m.CurrencyMode = bf.ReadUint8() + m.GachaType = bf.ReadUint8() return nil } diff --git a/network/mhfpacket/msg_mhf_play_normal_gacha.go b/network/mhfpacket/msg_mhf_play_normal_gacha.go index bc75f3ca1..46f2706a6 100644 --- a/network/mhfpacket/msg_mhf_play_normal_gacha.go +++ b/network/mhfpacket/msg_mhf_play_normal_gacha.go @@ -1,19 +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" ) // MsgMhfPlayNormalGacha represents the MSG_MHF_PLAY_NORMAL_GACHA -type MsgMhfPlayNormalGacha struct{ +type MsgMhfPlayNormalGacha struct { AckHandle uint32 - GachaHash uint32 - RollType uint8 - CurrencyMode uint8 + GachaID uint32 + RollType uint8 + GachaType uint8 } // Opcode returns the ID associated with this packet type. @@ -24,9 +24,9 @@ func (m *MsgMhfPlayNormalGacha) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfPlayNormalGacha) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.GachaHash = bf.ReadUint32() + m.GachaID = bf.ReadUint32() m.RollType = bf.ReadUint8() - m.CurrencyMode = bf.ReadUint8() + m.GachaType = bf.ReadUint8() return nil } diff --git a/network/mhfpacket/msg_mhf_play_stepup_gacha.go b/network/mhfpacket/msg_mhf_play_stepup_gacha.go index 653fc799f..83808ae1a 100644 --- a/network/mhfpacket/msg_mhf_play_stepup_gacha.go +++ b/network/mhfpacket/msg_mhf_play_stepup_gacha.go @@ -1,19 +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" ) // MsgMhfPlayStepupGacha represents the MSG_MHF_PLAY_STEPUP_GACHA -type MsgMhfPlayStepupGacha struct{ +type MsgMhfPlayStepupGacha struct { AckHandle uint32 - GachaHash uint32 - RollType uint8 - CurrencyMode uint8 + GachaID uint32 + RollType uint8 + GachaType uint8 } // Opcode returns the ID associated with this packet type. @@ -24,9 +24,9 @@ func (m *MsgMhfPlayStepupGacha) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfPlayStepupGacha) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.GachaHash = bf.ReadUint32() + m.GachaID = bf.ReadUint32() m.RollType = bf.ReadUint8() - m.CurrencyMode = bf.ReadUint8() + m.GachaType = bf.ReadUint8() return nil } diff --git a/network/mhfpacket/msg_mhf_post_tiny_bin.go b/network/mhfpacket/msg_mhf_post_tiny_bin.go index a43ed7b85..4c06a51e0 100644 --- a/network/mhfpacket/msg_mhf_post_tiny_bin.go +++ b/network/mhfpacket/msg_mhf_post_tiny_bin.go @@ -1,15 +1,18 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfPostTinyBin represents the MSG_MHF_POST_TINY_BIN -type MsgMhfPostTinyBin struct{} +type MsgMhfPostTinyBin struct { + AckHandle uint32 + Unk []byte +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfPostTinyBin) Opcode() network.PacketID { @@ -18,7 +21,9 @@ func (m *MsgMhfPostTinyBin) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfPostTinyBin) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.Unk = bf.ReadBytes(14) + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_present_box.go b/network/mhfpacket/msg_mhf_present_box.go index 77bdd0db2..c3da92e31 100644 --- a/network/mhfpacket/msg_mhf_present_box.go +++ b/network/mhfpacket/msg_mhf_present_box.go @@ -1,15 +1,26 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfPresentBox represents the MSG_MHF_PRESENT_BOX -type MsgMhfPresentBox struct{} +type MsgMhfPresentBox struct { + AckHandle uint32 + Unk0 uint32 + Unk1 uint32 + Unk2 uint32 + Unk3 uint32 + Unk4 uint32 + Unk5 uint32 + Unk6 uint32 + Unk7 uint32 + Unk8 uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfPresentBox) Opcode() network.PacketID { @@ -18,7 +29,17 @@ func (m *MsgMhfPresentBox) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfPresentBox) 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() + m.Unk3 = bf.ReadUint32() + m.Unk4 = bf.ReadUint32() + m.Unk5 = bf.ReadUint32() + m.Unk6 = bf.ReadUint32() + m.Unk7 = bf.ReadUint32() + m.Unk8 = bf.ReadUint32() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_read_mercenary_m.go b/network/mhfpacket/msg_mhf_read_mercenary_m.go index 75c973a74..957b35f3a 100644 --- a/network/mhfpacket/msg_mhf_read_mercenary_m.go +++ b/network/mhfpacket/msg_mhf_read_mercenary_m.go @@ -12,7 +12,7 @@ import ( type MsgMhfReadMercenaryM struct { AckHandle uint32 CharID uint32 - Unk0 uint32 + MercID uint32 } // Opcode returns the ID associated with this packet type. @@ -24,7 +24,7 @@ func (m *MsgMhfReadMercenaryM) Opcode() network.PacketID { func (m *MsgMhfReadMercenaryM) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.CharID = bf.ReadUint32() - m.Unk0 = bf.ReadUint32() + m.MercID = bf.ReadUint32() return nil } diff --git a/network/mhfpacket/msg_mhf_read_mercenary_w.go b/network/mhfpacket/msg_mhf_read_mercenary_w.go index c80afee14..3aa9597d9 100644 --- a/network/mhfpacket/msg_mhf_read_mercenary_w.go +++ b/network/mhfpacket/msg_mhf_read_mercenary_w.go @@ -11,7 +11,7 @@ import ( // MsgMhfReadMercenaryW represents the MSG_MHF_READ_MERCENARY_W type MsgMhfReadMercenaryW struct { AckHandle uint32 - Unk0 bool + Op uint8 Unk1 uint8 Unk2 uint16 // Hardcoded 0 in the binary } @@ -24,7 +24,7 @@ func (m *MsgMhfReadMercenaryW) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfReadMercenaryW) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadBool() + m.Op = bf.ReadUint8() m.Unk1 = bf.ReadUint8() m.Unk2 = bf.ReadUint16() return nil diff --git a/network/mhfpacket/msg_mhf_receive_gacha_item.go b/network/mhfpacket/msg_mhf_receive_gacha_item.go index 841c5deab..3f83d1d1b 100644 --- a/network/mhfpacket/msg_mhf_receive_gacha_item.go +++ b/network/mhfpacket/msg_mhf_receive_gacha_item.go @@ -1,17 +1,18 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfReceiveGachaItem represents the MSG_MHF_RECEIVE_GACHA_ITEM -type MsgMhfReceiveGachaItem struct{ - AckHandle uint32 - Unk0 uint16 +type MsgMhfReceiveGachaItem struct { + AckHandle uint32 + Max uint8 + Freeze bool } // Opcode returns the ID associated with this packet type. @@ -22,7 +23,8 @@ func (m *MsgMhfReceiveGachaItem) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfReceiveGachaItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() + m.Max = bf.ReadUint8() + m.Freeze = bf.ReadBool() return nil } diff --git a/network/mhfpacket/msg_mhf_reset_box_gacha_info.go b/network/mhfpacket/msg_mhf_reset_box_gacha_info.go index 233bc49d9..7a8c3ffcf 100644 --- a/network/mhfpacket/msg_mhf_reset_box_gacha_info.go +++ b/network/mhfpacket/msg_mhf_reset_box_gacha_info.go @@ -1,17 +1,17 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfResetBoxGachaInfo represents the MSG_MHF_RESET_BOX_GACHA_INFO -type MsgMhfResetBoxGachaInfo struct{ +type MsgMhfResetBoxGachaInfo struct { AckHandle uint32 - GachaHash uint32 + GachaID uint32 } // Opcode returns the ID associated with this packet type. @@ -22,7 +22,7 @@ func (m *MsgMhfResetBoxGachaInfo) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfResetBoxGachaInfo) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.GachaHash = bf.ReadUint32() + m.GachaID = bf.ReadUint32() return nil } diff --git a/network/mhfpacket/msg_mhf_save_mercenary.go b/network/mhfpacket/msg_mhf_save_mercenary.go index 3aa2b0311..8a9ddb9f8 100644 --- a/network/mhfpacket/msg_mhf_save_mercenary.go +++ b/network/mhfpacket/msg_mhf_save_mercenary.go @@ -10,11 +10,10 @@ import ( // MsgMhfSaveMercenary represents the MSG_MHF_SAVE_MERCENARY type MsgMhfSaveMercenary struct { - AckHandle uint32 - GCP uint32 - Unk0 uint32 - MercData []byte - Unk1 uint32 + AckHandle uint32 + GCP uint32 + PactMercID uint32 + MercData []byte } // Opcode returns the ID associated with this packet type. @@ -27,9 +26,10 @@ func (m *MsgMhfSaveMercenary) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clie m.AckHandle = bf.ReadUint32() bf.ReadUint32() // lenData m.GCP = bf.ReadUint32() - m.Unk0 = bf.ReadUint32() - m.MercData = bf.ReadBytes(uint(bf.ReadUint32())) - m.Unk1 = bf.ReadUint32() + m.PactMercID = bf.ReadUint32() + dataSize := bf.ReadUint32() + _ = bf.ReadUint32() // Merc index? + m.MercData = bf.ReadBytes(uint(dataSize)) return nil } diff --git a/network/mhfpacket/msg_mhf_stampcard_stamp.go b/network/mhfpacket/msg_mhf_stampcard_stamp.go index fd0e0ef1e..8d18ae971 100644 --- a/network/mhfpacket/msg_mhf_stampcard_stamp.go +++ b/network/mhfpacket/msg_mhf_stampcard_stamp.go @@ -1,27 +1,25 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfStampcardStamp represents the MSG_MHF_STAMPCARD_STAMP type MsgMhfStampcardStamp struct { - // Field-size accurate. AckHandle uint32 - Unk0 uint16 - Unk1 uint16 - Unk2 uint16 - Unk3 uint16 // Hardcoded 0 in binary - Unk4 uint32 - Unk5 uint32 - Unk6 uint32 - Unk7 uint32 - Unk8 uint32 - Unk9 uint32 + HR uint16 + GR uint16 + Stamps uint16 + Reward1 uint16 + Reward2 uint16 + Item1 uint16 + Item2 uint16 + Quantity1 uint16 + Quantity2 uint16 } // Opcode returns the ID associated with this packet type. @@ -32,16 +30,16 @@ func (m *MsgMhfStampcardStamp) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfStampcardStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() - m.Unk2 = bf.ReadUint16() - m.Unk3 = bf.ReadUint16() - m.Unk4 = bf.ReadUint32() - m.Unk5 = bf.ReadUint32() - m.Unk6 = bf.ReadUint32() - m.Unk7 = bf.ReadUint32() - m.Unk8 = bf.ReadUint32() - m.Unk9 = bf.ReadUint32() + m.HR = bf.ReadUint16() + m.GR = bf.ReadUint16() + m.Stamps = bf.ReadUint16() + _ = bf.ReadUint16() + m.Reward1 = uint16(bf.ReadUint32()) + m.Reward2 = uint16(bf.ReadUint32()) + m.Item1 = uint16(bf.ReadUint32()) + m.Item2 = uint16(bf.ReadUint32()) + m.Quantity1 = uint16(bf.ReadUint32()) + m.Quantity2 = uint16(bf.ReadUint32()) return nil } diff --git a/network/mhfpacket/msg_mhf_update_etc_point.go b/network/mhfpacket/msg_mhf_update_etc_point.go index 5110557ab..c73bf42ea 100644 --- a/network/mhfpacket/msg_mhf_update_etc_point.go +++ b/network/mhfpacket/msg_mhf_update_etc_point.go @@ -1,18 +1,18 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfUpdateEtcPoint represents the MSG_MHF_UPDATE_ETC_POINT type MsgMhfUpdateEtcPoint struct { AckHandle uint32 - Unk0 uint8 - Unk1 uint16 + PointType uint8 + Delta int16 } // Opcode returns the ID associated with this packet type. @@ -23,8 +23,8 @@ func (m *MsgMhfUpdateEtcPoint) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfUpdateEtcPoint) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint8() - m.Unk1 = bf.ReadUint16() + m.PointType = bf.ReadUint8() + m.Delta = bf.ReadInt16() return nil } diff --git a/patch-schema/etc-points.sql b/patch-schema/etc-points.sql new file mode 100644 index 000000000..88062922f --- /dev/null +++ b/patch-schema/etc-points.sql @@ -0,0 +1,9 @@ +BEGIN; + +ALTER TABLE characters ADD bonus_quests INT NOT NULL DEFAULT 0; + +ALTER TABLE characters ADD daily_quests INT NOT NULL DEFAULT 0; + +ALTER TABLE characters ADD promo_points INT NOT NULL DEFAULT 0; + +END; \ No newline at end of file diff --git a/patch-schema/gacha-db-2.sql b/patch-schema/gacha-db-2.sql new file mode 100644 index 000000000..d3faa1ed9 --- /dev/null +++ b/patch-schema/gacha-db-2.sql @@ -0,0 +1,77 @@ +BEGIN; + +ALTER TABLE characters + DROP COLUMN IF EXISTS gacha_prem; + +ALTER TABLE characters + DROP COLUMN IF EXISTS gacha_trial; + +ALTER TABLE characters + DROP COLUMN IF EXISTS frontier_points; + +ALTER TABLE users + ADD IF NOT EXISTS gacha_premium INT; + +ALTER TABLE users + ADD IF NOT EXISTS gacha_trial INT; + +ALTER TABLE users + ADD IF NOT EXISTS frontier_points INT; + +DROP TABLE IF EXISTS public.gacha_shop; + +CREATE TABLE IF NOT EXISTS public.gacha_shop ( + id SERIAL PRIMARY KEY, + min_gr INTEGER, + min_hr INTEGER, + name TEXT, + url_banner TEXT, + url_feature TEXT, + url_thumbnail TEXT, + wide BOOLEAN, + recommended BOOLEAN, + gacha_type INTEGER, + hidden BOOLEAN +); + +DROP TABLE IF EXISTS public.gacha_shop_items; + +CREATE TABLE IF NOT EXISTS public.gacha_entries ( + id SERIAL PRIMARY KEY, + gacha_id INTEGER, + entry_type INTEGER, + item_type INTEGER, + item_number INTEGER, + item_quantity INTEGER, + weight INTEGER, + rarity INTEGER, + rolls INTEGER, + frontier_points INTEGER, + daily_limit INTEGER +); + +CREATE TABLE IF NOT EXISTS public.gacha_items ( + id SERIAL PRIMARY KEY, + entry_id INTEGER, + item_type INTEGER, + item_id INTEGER, + quantity INTEGER +); + +DROP TABLE IF EXISTS public.stepup_state; + +CREATE TABLE IF NOT EXISTS public.gacha_stepup ( + gacha_id INTEGER, + step INTEGER, + character_id INTEGER +); + +DROP TABLE IF EXISTS public.lucky_box_state; + +CREATE TABLE IF NOT EXISTS public.gacha_box ( + gacha_id INTEGER, + entry_id INTEGER, + character_id INTEGER +); + +END; \ No newline at end of file diff --git a/patch-schema/guild-event-rp.sql b/patch-schema/guild-event-rp.sql new file mode 100644 index 000000000..1dca6aae1 --- /dev/null +++ b/patch-schema/guild-event-rp.sql @@ -0,0 +1,7 @@ +BEGIN; + +ALTER TABLE IF EXISTS public.guild_characters ADD rp_today INT DEFAULT 0; + +ALTER TABLE IF EXISTS public.guild_characters ADD rp_yesterday INT DEFAULT 0; + +END; \ No newline at end of file diff --git a/patch-schema/rasta-id.sql b/patch-schema/rasta-id.sql new file mode 100644 index 000000000..14541e378 --- /dev/null +++ b/patch-schema/rasta-id.sql @@ -0,0 +1,9 @@ +BEGIN; + +UPDATE characters SET savemercenary = NULL; + +ALTER TABLE characters ADD rasta_id INT; + +ALTER TABLE characters ADD pact_id INT; + +END; \ No newline at end of file diff --git a/patch-schema/stampcard.sql b/patch-schema/stampcard.sql new file mode 100644 index 000000000..f2c6b7d10 --- /dev/null +++ b/patch-schema/stampcard.sql @@ -0,0 +1,5 @@ +BEGIN; + +ALTER TABLE characters ADD stampcard INT NOT NULL DEFAULT 0; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index e9bb43a91..7c71cce27 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -258,6 +258,9 @@ func logoutPlayer(s *Session) { return } saveData.RP += uint16(rpGained) + if saveData.RP >= 50000 { + saveData.RP = 50000 + } saveData.Save(s) } @@ -525,8 +528,6 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { func handleMsgCaExchangeItem(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfPresentBox(s *Session, p mhfpacket.MHFPacket) {} - func handleMsgMhfServerCommand(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfAnnounce(s *Session, p mhfpacket.MHFPacket) { @@ -585,10 +586,11 @@ func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) { 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)) + s.logger.Error("Failed to get shared item box contents from db", zap.Error(err)) + bf.WriteBytes(make([]byte, 4)) } else { if len(boxContents) == 0 { - bf.WriteUint32(0x00) + bf.WriteBytes(make([]byte, 4)) } else { amount := len(boxContents) / 4 bf.WriteUint16(uint16(amount)) @@ -613,7 +615,9 @@ func handleMsgMhfUpdateUnionItem(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)) + s.logger.Error("Failed to get shared item box contents from db", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return } else { amount := len(boxContents) / 4 oldItems = make([]Item, amount) @@ -661,9 +665,9 @@ func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) { // 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)) + s.logger.Error("Failed to update shared item box contents in db", zap.Error(err)) } - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgMhfGetCogInfo(s *Session, p mhfpacket.MHFPacket) {} @@ -1499,25 +1503,74 @@ func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetEtcPoints(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetEtcPoints) - resp := byteframe.NewByteFrame() - resp.WriteUint8(0x3) // Maybe a count of uint32(s)? - resp.WriteUint32(0) // Point bonus quests - resp.WriteUint32(0) // Daily quests - resp.WriteUint32(0) // HS promotion points + var dailyTime time.Time + _ = s.server.db.QueryRow("SELECT COALESCE(daily_time, $2) FROM characters WHERE id = $1", s.charID, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)).Scan(&dailyTime) + if Time_Current_Adjusted().After(dailyTime) { + s.server.db.Exec("UPDATE characters SET bonus_quests = 0, daily_quests = 0 WHERE id=$1", s.charID) + } + var bonusQuests, dailyQuests, promoPoints uint32 + _ = s.server.db.QueryRow(`SELECT bonus_quests, daily_quests, promo_points FROM characters WHERE id = $1`, s.charID).Scan(&bonusQuests, &dailyQuests, &promoPoints) + resp := byteframe.NewByteFrame() + resp.WriteUint8(3) // Maybe a count of uint32(s)? + resp.WriteUint32(bonusQuests) + resp.WriteUint32(dailyQuests) + resp.WriteUint32(promoPoints) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } func handleMsgMhfUpdateEtcPoint(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUpdateEtcPoint) - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + + var column string + switch pkt.PointType { + case 0: + column = "bonus_quests" + case 1: + column = "daily_quests" + case 2: + column = "promo_points" + } + + var value int + err := s.server.db.QueryRow(fmt.Sprintf(`SELECT %s FROM characters WHERE id = $1`, column), s.charID).Scan(&value) + if err == nil { + if value-int(pkt.Delta) < 0 { + s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = 0 WHERE id = $1`, column), s.charID) + } else { + s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = %s + $1 WHERE id = $2`, column, column), pkt.Delta, s.charID) + } + } + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfStampcardStamp) - // TODO: Work out where it gets existing stamp count from, its format and then - // update the actual sent values to be correct - doAckBufSucceed(s, pkt.AckHandle, []byte{0x03, 0xe7, 0x03, 0xe7, 0x02, 0x99, 0x02, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x14, 0xf8, 0x69, 0x54}) + bf := byteframe.NewByteFrame() + bf.WriteUint16(pkt.HR) + bf.WriteUint16(pkt.GR) + var stamps uint16 + _ = s.server.db.QueryRow(`SELECT stampcard FROM characters WHERE id = $1`, s.charID).Scan(&stamps) + bf.WriteUint16(stamps) + stamps += pkt.Stamps + bf.WriteUint16(stamps) + s.server.db.Exec(`UPDATE characters SET stampcard = $1 WHERE id = $2`, stamps, s.charID) + if stamps%30 == 0 { + bf.WriteUint16(2) + bf.WriteUint16(pkt.Reward2) + bf.WriteUint16(pkt.Item2) + bf.WriteUint16(pkt.Quantity2) + addWarehouseGift(s, "item", mhfpacket.WarehouseStack{ItemID: pkt.Item2, Quantity: pkt.Quantity2}) + } else if stamps%15 == 0 { + bf.WriteUint16(1) + bf.WriteUint16(pkt.Reward1) + bf.WriteUint16(pkt.Item1) + bf.WriteUint16(pkt.Quantity1) + addWarehouseGift(s, "item", mhfpacket.WarehouseStack{ItemID: pkt.Item1, Quantity: pkt.Quantity1}) + } else { + bf.WriteBytes(make([]byte, 8)) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfStampcardPrize(s *Session, p mhfpacket.MHFPacket) {} @@ -1569,7 +1622,7 @@ func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) { s.QueueAck(pkt.AckHandle, resp.Data()) */ - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + doAckBufSucceed(s, pkt.AckHandle, []byte{}) } func handleMsgMhfRegistSpabiTime(s *Session, p mhfpacket.MHFPacket) {} @@ -1651,14 +1704,6 @@ func handleMsgMhfPostNotice(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfGetRandFromTable(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfGetTinyBin(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfGetTinyBin) - // requested after conquest quests - doAckBufSucceed(s, pkt.AckHandle, []byte{}) -} - -func handleMsgMhfPostTinyBin(s *Session, p mhfpacket.MHFPacket) {} - func handleMsgMhfGetSenyuDailyCount(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfGetSeibattle(s *Session, p mhfpacket.MHFPacket) { @@ -1668,45 +1713,6 @@ func handleMsgMhfGetSeibattle(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfPostSeibattle(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfGetRyoudama(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfGetRyoudama) - // likely guild related - // REQ: 00 04 13 53 8F 18 00 - // RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 01 00 01 FE 4E - // REQ: 00 06 13 53 8F 18 00 - // RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 - // REQ: 00 05 13 53 8F 18 00 - // RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 0E 2A 15 9E CC 00 00 00 01 82 79 83 4E 83 8A 81 5B 83 69 00 00 00 00 1E 55 B0 2F 00 00 00 01 8D F7 00 00 00 00 00 00 00 00 00 00 00 00 2A 15 9E CC 00 00 00 02 82 79 83 4E 83 8A 81 5B 83 69 00 00 00 00 03 D5 30 56 00 00 00 02 95 BD 91 F2 97 42 00 00 00 00 00 00 00 00 3F 57 76 9F 00 00 00 03 93 56 92 6E 96 B3 97 70 00 00 00 00 00 00 38 D9 0E C4 00 00 00 03 87 64 83 78 83 42 00 00 00 00 00 00 00 00 23 F3 B9 77 00 00 00 04 82 B3 82 CC 82 DC 82 E9 81 99 00 00 00 00 3F 1B 17 9C 00 00 00 04 82 B1 82 A4 82 BD 00 00 00 00 00 00 00 00 00 B9 F9 C0 00 00 00 05 82 CD 82 E9 82 A9 00 00 00 00 00 00 00 00 23 9F 9A EA 00 00 00 05 83 70 83 62 83 4C 83 83 83 49 00 00 00 00 38 D9 0E C4 00 00 00 06 87 64 83 78 83 42 00 00 00 00 00 00 00 00 1E 55 B0 2F 00 00 00 06 8D F7 00 00 00 00 00 00 00 00 00 00 00 00 03 D5 30 56 00 00 00 07 95 BD 91 F2 97 42 00 00 00 00 00 00 00 00 02 D3 B8 77 00 00 00 07 6F 77 6C 32 35 32 35 00 00 00 00 00 00 00 - data, _ := hex.DecodeString("0A218EAD0000000000000000000000010000000000000000") - doAckBufSucceed(s, pkt.AckHandle, data) -} - -func handleMsgMhfPostRyoudama(s *Session, p mhfpacket.MHFPacket) {} - -func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) { - // if the game gets bad responses for this it breaks the ability to save - pkt := p.(*mhfpacket.MsgMhfGetTenrouirai) - var data []byte - var err error - if pkt.Unk0 == 1 { - data, err = hex.DecodeString("0A218EAD000000000000000000000001010000000000060010") - } else if pkt.Unk2 == 4 { - data, err = hex.DecodeString("0A218EAD0000000000000000000000210101005000000202010102020104001000000202010102020106003200000202010002020104000C003202020101020201030032000002020101020202059C4000000202010002020105C35000320202010102020201003C00000202010102020203003200000201010001020203002800320201010101020204000C00000201010101020206002800000201010001020101003C00320201020101020105C35000000301020101020106003200000301020001020104001000320301020101020105C350000003010201010202030028000003010200010201030032003203010201010202059C4000000301020101010206002800000301020001010201003C00320301020101010206003200000301020101010204000C000003010200010101010050003203010201010101059C40000003010201010101030032000003010200010101040010003203010001010101060032000003010001010102030028000003010001010101010050003203010000010102059C4000000301000001010206002800000301000001010010") - } else { - data = []byte{0x00, 0x00, 0x00, 0x00} - s.logger.Info("GET_TENROUIRAI request for unknown type") - } - if err != nil { - panic(err) - } - doAckBufSucceed(s, pkt.AckHandle, data) -} - -func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfPostTenrouirai) - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) -} - func handleMsgMhfGetDailyMissionMaster(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfGetDailyMissionPersonal(s *Session, p mhfpacket.MHFPacket) {} @@ -1722,7 +1728,8 @@ func handleMsgMhfGetEquipSkinHist(s *Session, p mhfpacket.MHFPacket) { var data []byte err := s.server.db.QueryRow("SELECT COALESCE(skin_hist::bytea, $2::bytea) FROM characters WHERE id = $1", s.charID, make([]byte, 0xC80)).Scan(&data) if err != nil { - s.logger.Fatal("Failed to get skin_hist savedata from db", zap.Error(err)) + s.logger.Error("Failed to load skin_hist", zap.Error(err)) + data = make([]byte, 3200) } doAckBufSucceed(s, pkt.AckHandle, data) } @@ -1733,7 +1740,9 @@ func handleMsgMhfUpdateEquipSkinHist(s *Session, p mhfpacket.MHFPacket) { var data []byte err := s.server.db.QueryRow("SELECT COALESCE(skin_hist, $2) FROM characters WHERE id = $1", s.charID, make([]byte, 0xC80)).Scan(&data) if err != nil { - s.logger.Fatal("Failed to get skin_hist from db", zap.Error(err)) + s.logger.Error("Failed to save skin_hist", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + return } var bit int @@ -1787,8 +1796,8 @@ func handleMsgMhfGetEnhancedMinidata(s *Session, p mhfpacket.MHFPacket) { var data []byte err := s.server.db.QueryRow("SELECT minidata FROM characters WHERE id = $1", pkt.CharID).Scan(&data) if err != nil { - data = make([]byte, 0x400) // returning empty might avoid a client softlock - //s.logger.Fatal("Failed to get minidata from db", zap.Error(err)) + s.logger.Error("Failed to load minidata") + data = make([]byte, 1) } doAckBufSucceed(s, pkt.AckHandle, data) } @@ -1798,7 +1807,7 @@ func handleMsgMhfSetEnhancedMinidata(s *Session, p mhfpacket.MHFPacket) { 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)) + s.logger.Error("Failed to save minidata", zap.Error(err)) } doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go index 596bb5b33..b623efae6 100644 --- a/server/channelserver/handlers_cafe.go +++ b/server/channelserver/handlers_cafe.go @@ -15,7 +15,7 @@ func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) { var netcafePoints uint32 err := s.server.db.QueryRow("UPDATE characters SET netcafe_points = netcafe_points - $1 WHERE id = $2 RETURNING netcafe_points", pkt.PointCost, s.charID).Scan(&netcafePoints) if err != nil { - s.logger.Fatal("Failed to get netcafe points from db", zap.Error(err)) + s.logger.Error("Failed to get netcafe points from db", zap.Error(err)) } resp := byteframe.NewByteFrame() resp.WriteUint32(netcafePoints) @@ -27,7 +27,7 @@ func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) { var netcafePoints uint32 err := s.server.db.QueryRow("SELECT COALESCE(netcafe_points, 0) FROM characters WHERE id = $1", s.charID).Scan(&netcafePoints) if err != nil { - s.logger.Fatal("Failed to get netcate points from db", zap.Error(err)) + s.logger.Error("Failed to get netcate points from db", zap.Error(err)) } resp := byteframe.NewByteFrame() resp.WriteUint32(netcafePoints) @@ -37,10 +37,6 @@ func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfCheckDailyCafepoint) - // I am not sure exactly what this does, but all responses I have seen include this exact sequence of bytes - // 1 daily, 5 daily halk pots, 3 point boosted quests, also adds 5 netcafe points but not sent to client - // available once after midday every day - // get next midday var t = Time_static() year, month, day := t.Date() @@ -53,19 +49,25 @@ func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) { var dailyTime time.Time err := s.server.db.QueryRow("SELECT COALESCE(daily_time, $2) FROM characters WHERE id = $1", s.charID, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)).Scan(&dailyTime) if err != nil { - s.logger.Fatal("Failed to get daily_time savedata from db", zap.Error(err)) + s.logger.Error("Failed to get daily_time savedata from db", zap.Error(err)) } + var bondBonus, bonusQuests, dailyQuests uint32 + bf := byteframe.NewByteFrame() if t.After(dailyTime) { - // +5 netcafe points and setting next valid window - _, err := s.server.db.Exec("UPDATE characters SET daily_time=$1, netcafe_points=netcafe_points+5 WHERE id=$2", midday, s.charID) - if err != nil { - s.logger.Fatal("Failed to update daily_time and netcafe_points savedata in db", zap.Error(err)) - } - doAckBufSucceed(s, pkt.AckHandle, []byte{0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01}) + addPointNetcafe(s, 5) + bondBonus = 5 // Bond point bonus quests + bonusQuests = 3 // HRP bonus quests? + dailyQuests = 1 // Daily quests + s.server.db.Exec("UPDATE characters SET daily_time=$1, bonus_quests = $2, daily_quests = $3 WHERE id=$4", midday, bonusQuests, dailyQuests, s.charID) + bf.WriteBool(true) // Success? } else { - doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + bf.WriteBool(false) } + bf.WriteUint32(bondBonus) + bf.WriteUint32(bonusQuests) + bf.WriteUint32(dailyQuests) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) { @@ -194,7 +196,7 @@ func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket `, cbID).Scan(&cafeBonus.ID, &cafeBonus.ItemType, &cafeBonus.Quantity) if err == nil { if cafeBonus.ItemType == 17 { - s.server.db.Exec("UPDATE characters SET netcafe_points=netcafe_points+$1 WHERE id=$2", cafeBonus.Quantity, s.charID) + addPointNetcafe(s, int(cafeBonus.Quantity)) } } s.server.db.Exec("INSERT INTO public.cafe_accepted VALUES ($1, $2)", cbID, s.charID) @@ -202,6 +204,21 @@ func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } +func addPointNetcafe(s *Session, p int) error { + var points int + err := s.server.db.QueryRow("SELECT netcafe_points FROM characters WHERE id = $1", s.charID).Scan(&points) + if err != nil { + return err + } + if points+p > 100000 { + points = 100000 + } else { + points += p + } + s.server.db.Exec("UPDATE characters SET netcafe_points=$1 WHERE id=$2", points, s.charID) + return nil +} + func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfStartBoostTime) bf := byteframe.NewByteFrame() diff --git a/server/channelserver/handlers_caravan.go b/server/channelserver/handlers_caravan.go index f000d1623..86cf73249 100644 --- a/server/channelserver/handlers_caravan.go +++ b/server/channelserver/handlers_caravan.go @@ -1,9 +1,36 @@ package channelserver import ( + "encoding/hex" "erupe-ce/network/mhfpacket" ) +func handleMsgMhfGetRyoudama(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfGetRyoudama) + // likely guild related + // REQ: 00 04 13 53 8F 18 00 + // RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 01 00 01 FE 4E + // REQ: 00 06 13 53 8F 18 00 + // RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 + // REQ: 00 05 13 53 8F 18 00 + // RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 0E 2A 15 9E CC 00 00 00 01 82 79 83 4E 83 8A 81 5B 83 69 00 00 00 00 1E 55 B0 2F 00 00 00 01 8D F7 00 00 00 00 00 00 00 00 00 00 00 00 2A 15 9E CC 00 00 00 02 82 79 83 4E 83 8A 81 5B 83 69 00 00 00 00 03 D5 30 56 00 00 00 02 95 BD 91 F2 97 42 00 00 00 00 00 00 00 00 3F 57 76 9F 00 00 00 03 93 56 92 6E 96 B3 97 70 00 00 00 00 00 00 38 D9 0E C4 00 00 00 03 87 64 83 78 83 42 00 00 00 00 00 00 00 00 23 F3 B9 77 00 00 00 04 82 B3 82 CC 82 DC 82 E9 81 99 00 00 00 00 3F 1B 17 9C 00 00 00 04 82 B1 82 A4 82 BD 00 00 00 00 00 00 00 00 00 B9 F9 C0 00 00 00 05 82 CD 82 E9 82 A9 00 00 00 00 00 00 00 00 23 9F 9A EA 00 00 00 05 83 70 83 62 83 4C 83 83 83 49 00 00 00 00 38 D9 0E C4 00 00 00 06 87 64 83 78 83 42 00 00 00 00 00 00 00 00 1E 55 B0 2F 00 00 00 06 8D F7 00 00 00 00 00 00 00 00 00 00 00 00 03 D5 30 56 00 00 00 07 95 BD 91 F2 97 42 00 00 00 00 00 00 00 00 02 D3 B8 77 00 00 00 07 6F 77 6C 32 35 32 35 00 00 00 00 00 00 00 + data, _ := hex.DecodeString("0A218EAD0000000000000000000000010000000000000000") + doAckBufSucceed(s, pkt.AckHandle, data) +} + +func handleMsgMhfPostRyoudama(s *Session, p mhfpacket.MHFPacket) {} + +func handleMsgMhfGetTinyBin(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfGetTinyBin) + // requested after conquest quests + doAckBufSucceed(s, pkt.AckHandle, []byte{}) +} + +func handleMsgMhfPostTinyBin(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfPostTinyBin) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) +} + func handleMsgMhfCaravanMyScore(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfCaravanRanking(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 17a1d7858..a7a9856e2 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -53,7 +53,7 @@ func init() { } func sendDisabledCommandMessage(s *Session, cmd config.Command) { - sendServerChatMessage(s, fmt.Sprintf("%s command is disabled", cmd.Name)) + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDisabled"], cmd.Name)) } func sendServerChatMessage(s *Session, message string) { @@ -78,6 +78,233 @@ func sendServerChatMessage(s *Session, message string) { s.QueueSendMHF(castedBin) } +func parseChatCommand(s *Session, command string) { + if strings.HasPrefix(command, commands["Reload"].Prefix) { + // Flush all objects and users and reload + if commands["Reload"].Enabled { + sendServerChatMessage(s, s.server.dict["commandReload"]) + var temp mhfpacket.MHFPacket + deleteNotif := byteframe.NewByteFrame() + for _, object := range s.stage.objects { + if object.ownerCharID == s.charID { + continue + } + temp = &mhfpacket.MsgSysDeleteObject{ObjID: object.id} + deleteNotif.WriteUint16(uint16(temp.Opcode())) + temp.Build(deleteNotif, s.clientContext) + } + for _, session := range s.server.sessions { + if s == session { + continue + } + temp = &mhfpacket.MsgSysDeleteUser{CharID: session.charID} + deleteNotif.WriteUint16(uint16(temp.Opcode())) + temp.Build(deleteNotif, s.clientContext) + } + deleteNotif.WriteUint16(0x0010) + s.QueueSend(deleteNotif.Data()) + time.Sleep(500 * time.Millisecond) + reloadNotif := byteframe.NewByteFrame() + for _, session := range s.server.sessions { + if s == session { + continue + } + temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID} + reloadNotif.WriteUint16(uint16(temp.Opcode())) + temp.Build(reloadNotif, s.clientContext) + for i := 0; i < 3; i++ { + temp = &mhfpacket.MsgSysNotifyUserBinary{ + CharID: session.charID, + BinaryType: uint8(i + 1), + } + reloadNotif.WriteUint16(uint16(temp.Opcode())) + temp.Build(reloadNotif, s.clientContext) + } + } + for _, obj := range s.stage.objects { + if obj.ownerCharID == s.charID { + continue + } + temp = &mhfpacket.MsgSysDuplicateObject{ + ObjID: obj.id, + X: obj.x, + Y: obj.y, + Z: obj.z, + Unk0: 0, + OwnerCharID: obj.ownerCharID, + } + reloadNotif.WriteUint16(uint16(temp.Opcode())) + temp.Build(reloadNotif, s.clientContext) + } + reloadNotif.WriteUint16(0x0010) + s.QueueSend(reloadNotif.Data()) + } else { + sendDisabledCommandMessage(s, commands["Reload"]) + } + } + + if strings.HasPrefix(command, commands["KeyQuest"].Prefix) { + if commands["KeyQuest"].Enabled { + if strings.HasPrefix(command, fmt.Sprintf("%s get", commands["KeyQuest"].Prefix)) { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfGet"], s.kqf)) + } else if strings.HasPrefix(command, fmt.Sprintf("%s set", commands["KeyQuest"].Prefix)) { + var hexs string + n, numerr := fmt.Sscanf(command, fmt.Sprintf("%s set %%s", commands["KeyQuest"].Prefix), &hexs) + if numerr != nil || n != 1 || len(hexs) != 16 { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfSetError"], commands["KeyQuest"].Prefix)) + } else { + hexd, _ := hex.DecodeString(hexs) + s.kqf = hexd + s.kqfOverride = true + sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"]) + } + } + } else { + sendDisabledCommandMessage(s, commands["KeyQuest"]) + } + } + + if strings.HasPrefix(command, commands["Rights"].Prefix) { + // Set account rights + if commands["Rights"].Enabled { + var v uint32 + n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%d", commands["Rights"].Prefix), &v) + if err != nil || n != 1 { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix)) + } else { + _, err = s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID) + if err == nil { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsSuccess"], v)) + } + } + } else { + sendDisabledCommandMessage(s, commands["Rights"]) + } + } + + if strings.HasPrefix(command, commands["Course"].Prefix) { + if commands["Course"].Enabled { + var name string + n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%s", commands["Course"].Prefix), &name) + if err != nil || n != 1 { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix)) + } else { + name = strings.ToLower(name) + for _, course := range mhfpacket.Courses() { + for _, alias := range course.Aliases { + if strings.ToLower(name) == strings.ToLower(alias) { + if slices.Contains(s.server.erupeConfig.Courses, config.Course{Name: course.Aliases[0], Enabled: true}) { + if s.FindCourse(name).ID != 0 { + ei := slices.IndexFunc(s.courses, func(c mhfpacket.Course) bool { + for _, alias := range c.Aliases { + if strings.ToLower(name) == strings.ToLower(alias) { + return true + } + } + return false + }) + if ei != -1 { + s.courses = append(s.courses[:ei], s.courses[ei+1:]...) + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseDisabled"], course.Aliases[0])) + } + } else { + s.courses = append(s.courses, course) + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseEnabled"], course.Aliases[0])) + } + var newInt uint32 + for _, course := range s.courses { + newInt += uint32(math.Pow(2, float64(course.ID))) + } + s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", newInt, s.charID) + updateRights(s) + } else { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseLocked"], course.Aliases[0])) + } + } + } + } + } + } else { + sendDisabledCommandMessage(s, commands["Course"]) + } + } + + if strings.HasPrefix(command, commands["Raviente"].Prefix) { + if commands["Raviente"].Enabled { + if getRaviSemaphore(s.server) != nil { + s.server.raviente.Lock() + if !strings.HasPrefix(command, "!ravi ") { + sendServerChatMessage(s, s.server.dict["commandRaviNoCommand"]) + } else { + if strings.HasPrefix(command, "!ravi start") { + if s.server.raviente.register.startTime == 0 { + s.server.raviente.register.startTime = s.server.raviente.register.postTime + sendServerChatMessage(s, s.server.dict["commandRaviStartSuccess"]) + s.notifyRavi() + } else { + sendServerChatMessage(s, s.server.dict["commandRaviStartError"]) + } + } else if strings.HasPrefix(command, "!ravi cm") || strings.HasPrefix(command, "!ravi checkmultiplier") { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.raviente.GetRaviMultiplier(s.server))) + } else if strings.HasPrefix(command, "!ravi sr") || strings.HasPrefix(command, "!ravi sendres") { + if s.server.raviente.state.stateData[28] > 0 { + sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"]) + s.server.raviente.state.stateData[28] = 0 + } else { + sendServerChatMessage(s, s.server.dict["commandRaviResError"]) + } + } else if strings.HasPrefix(command, "!ravi ss") || strings.HasPrefix(command, "!ravi sendsed") { + sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"]) + // Total BerRavi HP + HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4] + s.server.raviente.support.supportData[1] = HP + } else if strings.HasPrefix(command, "!ravi rs") || strings.HasPrefix(command, "!ravi reqsed") { + sendServerChatMessage(s, s.server.dict["commandRaviRequest"]) + // Total BerRavi HP + HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4] + s.server.raviente.support.supportData[1] = HP + 12 + } else { + sendServerChatMessage(s, s.server.dict["commandRaviError"]) + } + } + s.server.raviente.Unlock() + } else { + sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"]) + } + } else { + sendDisabledCommandMessage(s, commands["Raviente"]) + } + } + + if strings.HasPrefix(command, commands["Teleport"].Prefix) { + if commands["Teleport"].Enabled { + var x, y int16 + n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%d %%d", commands["Teleport"].Prefix), &x, &y) + if err != nil || n != 2 { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportError"], commands["Teleport"].Prefix)) + } else { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y)) + + // Make the inside of the casted binary + payload := byteframe.NewByteFrame() + payload.SetLE() + payload.WriteUint8(2) // SetState type(position == 2) + payload.WriteInt16(x) // X + payload.WriteInt16(y) // Y + payloadBytes := payload.Data() + + s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{ + CharID: s.charID, + MessageType: BinaryMessageTypeState, + RawDataPayload: payloadBytes, + }) + } + } else { + sendDisabledCommandMessage(s, commands["Teleport"]) + } + } +} + func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysCastBinary) tmp := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) @@ -144,6 +371,18 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { roll.WriteNullTerminatedBytes([]byte(dice)) roll.WriteNullTerminatedBytes(tmp.ReadNullTerminatedBytes()) realPayload = roll.Data() + } else { + bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) + bf.SetLE() + chatMessage := &binpacket.MsgBinChat{} + chatMessage.Parse(bf) + if strings.HasPrefix(chatMessage.Message, "!") { + parseChatCommand(s, chatMessage.Message) + return + } + if (pkt.BroadcastType == BroadcastTypeStage && s.stage.id == "sl1Ns200p0a0u0") || pkt.BroadcastType == BroadcastTypeWorld { + s.server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message) + } } } @@ -167,8 +406,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { } case BroadcastTypeServer: if pkt.MessageType == 1 { - raviSema := getRaviSemaphore(s) - if raviSema != "" { + if getRaviSemaphore(s.server) != nil { s.server.BroadcastMHF(resp, s) } } else { @@ -190,266 +428,6 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { } s.Unlock() } - - // Handle chat - if pkt.MessageType == BinaryMessageTypeChat { - bf := byteframe.NewByteFrameFromBytes(realPayload) - - // IMPORTANT! Casted binary objects are sent _as they are in memory_, - // this means little endian for LE CPUs, might be different for PS3/PS4/PSP/XBOX. - bf.SetLE() - - chatMessage := &binpacket.MsgBinChat{} - chatMessage.Parse(bf) - - fmt.Printf("Got chat message: %+v\n", chatMessage) - - // Discord integration - if (pkt.BroadcastType == BroadcastTypeStage && s.stage.id == "sl1Ns200p0a0u0") || pkt.BroadcastType == BroadcastTypeWorld { - s.server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message) - } - - if strings.HasPrefix(chatMessage.Message, commands["Reload"].Prefix) { - // Flush all objects and users and reload - if commands["Reload"].Enabled { - sendServerChatMessage(s, "Reloading players...") - var temp mhfpacket.MHFPacket - deleteNotif := byteframe.NewByteFrame() - for _, object := range s.stage.objects { - if object.ownerCharID == s.charID { - continue - } - temp = &mhfpacket.MsgSysDeleteObject{ObjID: object.id} - deleteNotif.WriteUint16(uint16(temp.Opcode())) - temp.Build(deleteNotif, s.clientContext) - } - for _, session := range s.server.sessions { - if s == session { - continue - } - temp = &mhfpacket.MsgSysDeleteUser{CharID: session.charID} - deleteNotif.WriteUint16(uint16(temp.Opcode())) - temp.Build(deleteNotif, s.clientContext) - } - deleteNotif.WriteUint16(0x0010) - s.QueueSend(deleteNotif.Data()) - time.Sleep(500 * time.Millisecond) - reloadNotif := byteframe.NewByteFrame() - for _, session := range s.server.sessions { - if s == session { - continue - } - temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID} - reloadNotif.WriteUint16(uint16(temp.Opcode())) - temp.Build(reloadNotif, s.clientContext) - for i := 0; i < 3; i++ { - temp = &mhfpacket.MsgSysNotifyUserBinary{ - CharID: session.charID, - BinaryType: uint8(i + 1), - } - reloadNotif.WriteUint16(uint16(temp.Opcode())) - temp.Build(reloadNotif, s.clientContext) - } - } - for _, obj := range s.stage.objects { - if obj.ownerCharID == s.charID { - continue - } - temp = &mhfpacket.MsgSysDuplicateObject{ - ObjID: obj.id, - X: obj.x, - Y: obj.y, - Z: obj.z, - Unk0: 0, - OwnerCharID: obj.ownerCharID, - } - reloadNotif.WriteUint16(uint16(temp.Opcode())) - temp.Build(reloadNotif, s.clientContext) - } - reloadNotif.WriteUint16(0x0010) - s.QueueSend(reloadNotif.Data()) - } else { - sendDisabledCommandMessage(s, commands["Reload"]) - } - } - - 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 { - var v uint32 - n, err := fmt.Sscanf(chatMessage.Message, "!rights %d", &v) - if err != nil || n != 1 { - sendServerChatMessage(s, "Error in command. Format: !rights n") - } else { - _, err = s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID) - if err == nil { - sendServerChatMessage(s, fmt.Sprintf("Set rights integer: %d", v)) - } - } - } else { - sendDisabledCommandMessage(s, commands["Rights"]) - } - } - - if strings.HasPrefix(chatMessage.Message, commands["Course"].Prefix) { - if commands["Course"].Enabled { - var name string - n, err := fmt.Sscanf(chatMessage.Message, "!course %s", &name) - if err != nil || n != 1 { - sendServerChatMessage(s, "Error in command. Format: !course ") - } else { - name = strings.ToLower(name) - for _, course := range mhfpacket.Courses() { - for _, alias := range course.Aliases { - if strings.ToLower(name) == strings.ToLower(alias) { - if slices.Contains(s.server.erupeConfig.Courses, config.Course{Name: course.Aliases[0], Enabled: true}) { - if s.FindCourse(name).ID != 0 { - ei := slices.IndexFunc(s.courses, func(c mhfpacket.Course) bool { - for _, alias := range c.Aliases { - if strings.ToLower(name) == strings.ToLower(alias) { - return true - } - } - return false - }) - if ei != -1 { - s.courses = append(s.courses[:ei], s.courses[ei+1:]...) - sendServerChatMessage(s, fmt.Sprintf(`%s Course disabled`, course.Aliases[0])) - } - } else { - s.courses = append(s.courses, course) - sendServerChatMessage(s, fmt.Sprintf(`%s Course enabled`, course.Aliases[0])) - } - var newInt uint32 - for _, course := range s.courses { - newInt += uint32(math.Pow(2, float64(course.ID))) - } - s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", newInt, s.charID) - updateRights(s) - } else { - sendServerChatMessage(s, fmt.Sprintf(`%s Course is locked`, course.Aliases[0])) - } - } - } - } - } - } else { - sendDisabledCommandMessage(s, commands["Course"]) - } - } - - if strings.HasPrefix(chatMessage.Message, commands["Raviente"].Prefix) { - if commands["Raviente"].Enabled { - if getRaviSemaphore(s) != "" { - s.server.raviente.Lock() - if !strings.HasPrefix(chatMessage.Message, "!ravi ") { - sendServerChatMessage(s, "No Raviente command specified!") - } else { - if strings.HasPrefix(chatMessage.Message, "!ravi start") { - if s.server.raviente.register.startTime == 0 { - s.server.raviente.register.startTime = s.server.raviente.register.postTime - sendServerChatMessage(s, "The Great Slaying will begin in a moment") - s.notifyRavi() - } else { - sendServerChatMessage(s, "The Great Slaying has already begun!") - } - } else if strings.HasPrefix(chatMessage.Message, "!ravi sm") || strings.HasPrefix(chatMessage.Message, "!ravi setmultiplier") { - var num uint16 - n, numerr := fmt.Sscanf(chatMessage.Message, "!ravi sm %d", &num) - if numerr != nil || n != 1 { - sendServerChatMessage(s, "Error in command. Format: !ravi sm n") - } else if s.server.raviente.state.damageMultiplier == 1 { - if num > 32 { - sendServerChatMessage(s, "Raviente multiplier too high, defaulting to 32x") - s.server.raviente.state.damageMultiplier = 32 - } else { - sendServerChatMessage(s, fmt.Sprintf("Raviente multiplier set to %dx", num)) - s.server.raviente.state.damageMultiplier = uint32(num) - } - } else { - sendServerChatMessage(s, fmt.Sprintf("Raviente multiplier is already set to %dx!", s.server.raviente.state.damageMultiplier)) - } - } else if strings.HasPrefix(chatMessage.Message, "!ravi cm") || strings.HasPrefix(chatMessage.Message, "!ravi checkmultiplier") { - sendServerChatMessage(s, fmt.Sprintf("Raviente multiplier is currently %dx", s.server.raviente.state.damageMultiplier)) - } else if strings.HasPrefix(chatMessage.Message, "!ravi sr") || strings.HasPrefix(chatMessage.Message, "!ravi sendres") { - if s.server.raviente.state.stateData[28] > 0 { - sendServerChatMessage(s, "Sending resurrection support!") - s.server.raviente.state.stateData[28] = 0 - } else { - sendServerChatMessage(s, "Resurrection support has not been requested!") - } - } else if strings.HasPrefix(chatMessage.Message, "!ravi ss") || strings.HasPrefix(chatMessage.Message, "!ravi sendsed") { - sendServerChatMessage(s, "Sending sedation support if requested!") - // Total BerRavi HP - HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4] - s.server.raviente.support.supportData[1] = HP - } else if strings.HasPrefix(chatMessage.Message, "!ravi rs") || strings.HasPrefix(chatMessage.Message, "!ravi reqsed") { - sendServerChatMessage(s, "Requesting sedation support!") - // Total BerRavi HP - HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4] - s.server.raviente.support.supportData[1] = HP + 12 - } else { - sendServerChatMessage(s, "Raviente command not recognised!") - } - } - s.server.raviente.Unlock() - } else { - sendServerChatMessage(s, "No one has joined the Great Slaying!") - } - } else { - sendDisabledCommandMessage(s, commands["Raviente"]) - } - } - - if strings.HasPrefix(chatMessage.Message, commands["Teleport"].Prefix) { - if commands["Teleport"].Enabled { - var x, y int16 - n, err := fmt.Sscanf(chatMessage.Message, "!tele %d %d", &x, &y) - if err != nil || n != 2 { - sendServerChatMessage(s, "Invalid command. Usage:\"!tele 500 500\"") - } else { - sendServerChatMessage(s, fmt.Sprintf("Teleporting to %d %d", x, y)) - - // Make the inside of the casted binary - payload := byteframe.NewByteFrame() - payload.SetLE() - payload.WriteUint8(2) // SetState type(position == 2) - payload.WriteInt16(x) // X - payload.WriteInt16(y) // Y - payloadBytes := payload.Data() - - s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{ - CharID: s.charID, - MessageType: BinaryMessageTypeState, - RawDataPayload: payloadBytes, - }) - } - } else { - sendDisabledCommandMessage(s, commands["Teleport"]) - } - } - } } func handleMsgSysCastedBinary(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index 8499f9b17..7de800e04 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -2,6 +2,7 @@ package channelserver import ( "encoding/binary" + "errors" "erupe-ce/common/bfutil" "erupe-ce/common/stringsupport" @@ -58,6 +59,7 @@ func GetCharacterSaveData(s *Session, charID uint32) (*CharacterSaveData, error) } defer result.Close() if !result.Next() { + err = errors.New("no savedata found") s.logger.Error("No savedata found", zap.Uint32("charID", charID)) return nil, err } diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index ebab0c4c4..36dd2e596 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -29,7 +29,9 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) { // diffs themselves are also potentially compressed diff, err := nullcomp.Decompress(pkt.RawDataPayload) if err != nil { - s.logger.Fatal("Failed to decompress diff", zap.Error(err)) + s.logger.Error("Failed to decompress diff", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return } // Perform diff. s.logger.Info("Diffing...") @@ -39,12 +41,20 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) { // 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.Error("Failed to decompress savedata from packet", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return } s.logger.Info("Updating save with blob") characterSaveData.decompSave = saveData } characterSaveData.updateStructWithSaveData() + + // Bypass name-checker if new + if characterSaveData.IsNewCharacter == true { + s.Name = characterSaveData.Name + } + if characterSaveData.Name == s.Name { characterSaveData.Save(s) s.logger.Info("Wrote recompressed savedata back to DB.") @@ -58,9 +68,9 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) { } _, 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)) + s.logger.Error("Failed to update character name in db", zap.Error(err)) } - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func grpToGR(n uint32) uint16 { @@ -230,7 +240,7 @@ func dumpSaveData(s *Session, data []byte, suffix string) { _, err := os.Stat(dir) if err != nil { if os.IsNotExist(err) { - err = os.Mkdir(dir, os.ModeDir) + err = os.Mkdir(dir, os.ModePerm) if err != nil { s.logger.Warn("Error dumping savedata, could not create folder") return @@ -282,15 +292,9 @@ func handleMsgMhfSaveScenarioData(s *Session, p mhfpacket.MHFPacket) { 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)) + s.logger.Error("Failed to update scenario data in db", zap.Error(err)) } - // Do this ack manually because it uses a non-(0|1) error code - s.QueueSendMHF(&mhfpacket.MsgSysAck{ - AckHandle: pkt.AckHandle, - IsBufferResponse: false, - ErrorCode: 0x40, - AckData: []byte{0x00, 0x00, 0x00, 0x40}, - }) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgMhfLoadScenarioData(s *Session, p mhfpacket.MHFPacket) { @@ -299,13 +303,10 @@ func handleMsgMhfLoadScenarioData(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() 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)) + s.logger.Error("Failed to load scenariodata", zap.Error(err)) + bf.WriteBytes(make([]byte, 10)) } else { - if len(scenarioData) == 0 { - bf.WriteUint32(0x00) - } else { - bf.WriteBytes(scenarioData) - } + bf.WriteBytes(scenarioData) } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } diff --git a/server/channelserver/handlers_distitem.go b/server/channelserver/handlers_distitem.go index 91c980cff..0da9347fa 100644 --- a/server/channelserver/handlers_distitem.go +++ b/server/channelserver/handlers_distitem.go @@ -94,6 +94,35 @@ func handleMsgMhfApplyDistItem(s *Session, p mhfpacket.MHFPacket) { return } + if len(dist.Data) >= 2 { + distData := byteframe.NewByteFrameFromBytes(dist.Data) + distItems := int(distData.ReadUint16()) + for i := 0; i < distItems; i++ { + if len(dist.Data) >= 2+(i*13) { + itemType := distData.ReadUint8() + _ = distData.ReadBytes(6) + quantity := int(distData.ReadUint16()) + _ = distData.ReadBytes(4) + switch itemType { + case 17: + _ = addPointNetcafe(s, quantity) + case 19: + s.server.db.Exec("UPDATE users u SET gacha_premium=gacha_premium+$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", quantity, s.charID) + case 20: + s.server.db.Exec("UPDATE users u SET gacha_trial=gacha_trial+$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", quantity, s.charID) + case 21: + s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points+$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", quantity, s.charID) + case 23: + saveData, err := GetCharacterSaveData(s, s.charID) + if err == nil { + saveData.RP += uint16(quantity) + saveData.Save(s) + } + } + } + } + } + bf := byteframe.NewByteFrame() bf.WriteUint32(pkt.DistributionID) bf.WriteBytes(dist.Data) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 5605a458e..1f4771407 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -736,7 +736,10 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { // TODO: This doesn't implement blocking, if someone unlocked the same outfit at the same time s.server.db.Exec(`UPDATE guilds SET pugi_outfits=pugi_outfits+$1 WHERE id=$2`, int(math.Pow(float64(pkt.Data1.ReadUint32()), 2)), guild.ID) case mhfpacket.OPERATE_GUILD_DONATE_EVENT: - bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, true)) + quantity := uint16(pkt.Data1.ReadUint32()) + bf.WriteBytes(handleDonateRP(s, quantity, guild, true)) + // TODO: Move this value onto rp_yesterday and reset to 0... daily? + s.server.db.Exec(`UPDATE guild_characters SET rp_today=rp_today+$1 WHERE character_id=$2`, quantity, s.charID) case mhfpacket.OPERATE_GUILD_EVENT_EXCHANGE: rp := uint16(pkt.Data1.ReadUint32()) var balance uint32 @@ -1443,8 +1446,9 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(0) } - for range guildMembers { - bf.WriteUint32(0x00) // Unk + for _, member := range guildMembers { + bf.WriteUint16(member.RPToday) + bf.WriteUint16(member.RPYesterday) } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) @@ -1523,10 +1527,11 @@ func handleMsgMhfEnumerateGuildItem(s *Session, p mhfpacket.MHFPacket) { 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)) + s.logger.Error("Failed to get guild item box contents from db", zap.Error(err)) + bf.WriteBytes(make([]byte, 4)) } else { if len(boxContents) == 0 { - bf.WriteUint32(0x00) + bf.WriteBytes(make([]byte, 4)) } else { amount := len(boxContents) / 4 bf.WriteUint16(uint16(amount)) @@ -1556,7 +1561,9 @@ func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) { 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)) + s.logger.Error("Failed to get guild item box contents from db", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return } else { amount := len(boxContents) / 4 oldItems = make([]Item, amount) @@ -1604,7 +1611,7 @@ func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) { // 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)) + s.logger.Error("Failed to update guild item box contents in db", zap.Error(err)) } doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) @@ -1729,7 +1736,8 @@ func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) { guild, _ := GetGuildInfoByCharacterId(s, s.charID) data, err := s.server.db.Queryx("SELECT id, meal_id, level, expires FROM guild_meals WHERE guild_id = $1", guild.ID) if err != nil { - s.logger.Fatal("Failed to get guild meals from db", zap.Error(err)) + s.logger.Error("Failed to get guild meals from db", zap.Error(err)) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2)) } temp := byteframe.NewByteFrame() count := 0 @@ -1737,7 +1745,7 @@ func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) { mealData := &GuildMeal{} err = data.StructScan(&mealData) if err != nil { - s.logger.Fatal("Failed to scan meal data", zap.Error(err)) + continue } if mealData.Expires > uint32(Time_Current_Adjusted().Add(-60*time.Minute).Unix()) { count++ @@ -1759,12 +1767,12 @@ func handleMsgMhfRegistGuildCooking(s *Session, p mhfpacket.MHFPacket) { if pkt.OverwriteID != 0 { _, err := s.server.db.Exec("DELETE FROM guild_meals WHERE id = $1", pkt.OverwriteID) if err != nil { - s.logger.Fatal("Failed to delete meal in db", zap.Error(err)) + s.logger.Error("Failed to delete meal in db", zap.Error(err)) } } _, err := s.server.db.Exec("INSERT INTO guild_meals (guild_id, meal_id, level, expires) VALUES ($1, $2, $3, $4)", guild.ID, pkt.MealID, pkt.Success, Time_Current_Adjusted().Add(30*time.Minute).Unix()) if err != nil { - s.logger.Fatal("Failed to register meal in db", zap.Error(err)) + s.logger.Error("Failed to register meal in db", zap.Error(err)) } doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x01, 0x00}) } @@ -1803,58 +1811,51 @@ func handleMsgMhfGuildHuntdata(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"` + ID uint32 `db:"id"` + StampID uint32 `db:"stamp_id"` + Title string `db:"title"` + Body string `db:"body"` + AuthorID uint32 `db:"author_id"` + Timestamp time.Time `db:"created_at"` + LikedBy string `db:"liked_by"` } 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)) + if pkt.BoardType == 1 { + pkt.MaxPosts = 4 } - + msgs, err := s.server.db.Queryx("SELECT id, stamp_id, title, body, author_id, 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.Error("Failed to get guild messages from db", zap.Error(err)) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } + s.server.db.Exec("UPDATE characters SET guild_post_checked = $1 WHERE id = $2", time.Now(), s.charID) bf := byteframe.NewByteFrame() - noMsgs := true - postCount := 0 + var postCount uint32 for msgs.Next() { - noMsgs = false - postCount++ postData := &MessageBoardPost{} err = msgs.StructScan(&postData) if err != nil { - s.logger.Fatal("Failed to get guild messages from db", zap.Error(err)) + continue } - - bf.WriteUint32(postData.Type) + postCount++ + bf.WriteUint32(postData.ID) bf.WriteUint32(postData.AuthorID) - bf.WriteUint64(postData.Timestamp) - likedBySlice := strings.Split(postData.LikedBy, ",") - if likedBySlice[0] == "" { - bf.WriteUint32(0) - } else { - bf.WriteUint32(uint32(len(likedBySlice))) - } + bf.WriteUint32(0) + bf.WriteUint32(uint32(postData.Timestamp.Unix())) + bf.WriteUint32(uint32(stringsupport.CSVLength(postData.LikedBy))) bf.WriteBool(stringsupport.CSVContains(postData.LikedBy, int(s.charID))) bf.WriteUint32(postData.StampID) ps.Uint32(bf, postData.Title, true) ps.Uint32(bf, postData.Body, true) } - 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()) - } + data := byteframe.NewByteFrame() + data.WriteUint32(postCount) + data.WriteBytes(bf.Data()) + doAckBufSucceed(s, pkt.AckHandle, data.Data()) } func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) { @@ -1869,101 +1870,62 @@ func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) return } - var titleConv, bodyConv string switch pkt.MessageOp { case 0: // Create message postType := bf.ReadUint32() // 0 = message, 1 = news - stampId := bf.ReadUint32() + stampID := bf.ReadUint32() titleLength := bf.ReadUint32() bodyLength := bf.ReadUint32() - title := bf.ReadBytes(uint(titleLength)) - body := bf.ReadBytes(uint(bodyLength)) - titleConv = stringsupport.SJISToUTF8(title) - bodyConv = stringsupport.SJISToUTF8(body) - _, 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), titleConv, bodyConv) - if err != nil { - s.logger.Fatal("Failed to add new guild message to db", zap.Error(err)) - } + title := stringsupport.SJISToUTF8(bf.ReadBytes(uint(titleLength))) + body := stringsupport.SJISToUTF8(bf.ReadBytes(uint(bodyLength))) + 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, stampID, postType, title, body) // 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)) - } + postID := bf.ReadUint32() + s.server.db.Exec("DELETE FROM guild_posts WHERE id = $1", postID) case 2: // Update message - postType := bf.ReadUint32() - timestamp := bf.ReadUint64() + postID := bf.ReadUint32() + bf.ReadBytes(8) titleLength := bf.ReadUint32() bodyLength := bf.ReadUint32() - title := bf.ReadBytes(uint(titleLength)) - body := bf.ReadBytes(uint(bodyLength)) - titleConv = stringsupport.SJISToUTF8(title) - bodyConv = stringsupport.SJISToUTF8(body) - _, 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", titleConv, bodyConv, int(postType), int(timestamp), guild.ID) - if err != nil { - s.logger.Fatal("Failed to update guild message in db", zap.Error(err)) - } + title := stringsupport.SJISToUTF8(bf.ReadBytes(uint(titleLength))) + body := stringsupport.SJISToUTF8(bf.ReadBytes(uint(bodyLength))) + s.server.db.Exec("UPDATE guild_posts SET title = $1, body = $2 WHERE id = $3", title, body, postID) 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)) - } + postID := bf.ReadUint32() + bf.ReadBytes(8) + stampID := bf.ReadUint32() + s.server.db.Exec("UPDATE guild_posts SET stamp_id = $1 WHERE id = $2", stampID, postID) case 4: // Like message - postType := bf.ReadUint32() - timestamp := bf.ReadUint64() + postID := bf.ReadUint32() + bf.ReadBytes(8) 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) + err := s.server.db.QueryRow("SELECT liked_by FROM guild_posts WHERE id = $1", postID).Scan(&likedBy) if err != nil { - s.logger.Fatal("Failed to get guild message like data from db", zap.Error(err)) + s.logger.Error("Failed to get guild message like data from db", zap.Error(err)) } else { if likeState { likedBy = stringsupport.CSVAdd(likedBy, 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)) - } + s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, postID) } else { likedBy = stringsupport.CSVRemove(likedBy, 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 unlike guild message in db", zap.Error(err)) - } + s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, postID) } } case 5: // Check for new messages - var timeChecked int + var timeChecked time.Time 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 AND author_id != $3", guild.ID, timeChecked, s.charID).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 - } - } + err := s.server.db.QueryRow("SELECT guild_post_checked FROM characters WHERE id = $1", s.charID).Scan(&timeChecked) + if err == nil { + s.server.db.QueryRow("SELECT COUNT(*) FROM guild_posts WHERE guild_id = $1 AND (EXTRACT(epoch FROM created_at)::int) > $2", guild.ID, timeChecked.Unix()).Scan(&newPosts) + if newPosts > 0 { + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01}) + return } } } - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgMhfEntryRookieGuild(s *Session, p mhfpacket.MHFPacket) { diff --git a/server/channelserver/handlers_guild_adventure.go b/server/channelserver/handlers_guild_adventure.go index 45f836127..304b5721f 100644 --- a/server/channelserver/handlers_guild_adventure.go +++ b/server/channelserver/handlers_guild_adventure.go @@ -23,7 +23,9 @@ func handleMsgMhfLoadGuildAdventure(s *Session, p mhfpacket.MHFPacket) { guild, _ := GetGuildInfoByCharacterId(s, s.charID) data, err := s.server.db.Queryx("SELECT id, destination, charge, depart, return, collected_by FROM guild_adventures WHERE guild_id = $1", guild.ID) if err != nil { - s.logger.Fatal("Failed to get guild adventures from db", zap.Error(err)) + s.logger.Error("Failed to get guild adventures from db", zap.Error(err)) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return } temp := byteframe.NewByteFrame() count := 0 @@ -32,7 +34,7 @@ func handleMsgMhfLoadGuildAdventure(s *Session, p mhfpacket.MHFPacket) { adventureData := &GuildAdventure{} err = data.StructScan(&adventureData) if err != nil { - s.logger.Fatal("Failed to scan adventure data", zap.Error(err)) + continue } temp.WriteUint32(adventureData.ID) temp.WriteUint32(adventureData.Destination) @@ -52,7 +54,7 @@ func handleMsgMhfRegistGuildAdventure(s *Session, p mhfpacket.MHFPacket) { guild, _ := GetGuildInfoByCharacterId(s, s.charID) _, err := s.server.db.Exec("INSERT INTO guild_adventures (guild_id, destination, depart, return) VALUES ($1, $2, $3, $4)", guild.ID, pkt.Destination, Time_Current_Adjusted().Unix(), Time_Current_Adjusted().Add(6*time.Hour).Unix()) if err != nil { - s.logger.Fatal("Failed to register guild adventure", zap.Error(err)) + s.logger.Error("Failed to register guild adventure", zap.Error(err)) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } @@ -62,12 +64,12 @@ func handleMsgMhfAcquireGuildAdventure(s *Session, p mhfpacket.MHFPacket) { var collectedBy string err := s.server.db.QueryRow("SELECT collected_by FROM guild_adventures WHERE id = $1", pkt.ID).Scan(&collectedBy) if err != nil { - s.logger.Fatal("Error parsing adventure collected by", zap.Error(err)) + s.logger.Error("Error parsing adventure collected by", zap.Error(err)) } else { collectedBy = stringsupport.CSVAdd(collectedBy, int(s.charID)) _, err := s.server.db.Exec("UPDATE guild_adventures SET collected_by = $1 WHERE id = $2", collectedBy, pkt.ID) if err != nil { - s.logger.Fatal("Failed to collect adventure in db", zap.Error(err)) + s.logger.Error("Failed to collect adventure in db", zap.Error(err)) } } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) @@ -77,7 +79,7 @@ func handleMsgMhfChargeGuildAdventure(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfChargeGuildAdventure) _, err := s.server.db.Exec("UPDATE guild_adventures SET charge = charge + $1 WHERE id = $2", pkt.Amount, pkt.ID) if err != nil { - s.logger.Fatal("Failed to charge guild adventure", zap.Error(err)) + s.logger.Error("Failed to charge guild adventure", zap.Error(err)) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } @@ -87,7 +89,7 @@ func handleMsgMhfRegistGuildAdventureDiva(s *Session, p mhfpacket.MHFPacket) { guild, _ := GetGuildInfoByCharacterId(s, s.charID) _, err := s.server.db.Exec("INSERT INTO guild_adventures (guild_id, destination, charge, depart, return) VALUES ($1, $2, $3, $4, $5)", guild.ID, pkt.Destination, pkt.Charge, Time_Current_Adjusted().Unix(), Time_Current_Adjusted().Add(1*time.Hour).Unix()) if err != nil { - s.logger.Fatal("Failed to register guild adventure", zap.Error(err)) + s.logger.Error("Failed to register guild adventure", zap.Error(err)) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } diff --git a/server/channelserver/handlers_guild_alliance.go b/server/channelserver/handlers_guild_alliance.go index 7e2a9a2ac..fbb285e0a 100644 --- a/server/channelserver/handlers_guild_alliance.go +++ b/server/channelserver/handlers_guild_alliance.go @@ -73,7 +73,8 @@ func buildAllianceObjectFromDbResult(result *sqlx.Rows, err error, s *Session) ( parentGuild, err := GetGuildInfoByID(s, alliance.ParentGuildID) if err != nil { - s.logger.Fatal("Failed to get parent guild info", zap.Error(err)) + s.logger.Error("Failed to get parent guild info", zap.Error(err)) + return nil, err } else { alliance.ParentGuild = *parentGuild alliance.TotalMembers += parentGuild.MemberCount @@ -82,7 +83,8 @@ func buildAllianceObjectFromDbResult(result *sqlx.Rows, err error, s *Session) ( if alliance.SubGuild1ID > 0 { subGuild1, err := GetGuildInfoByID(s, alliance.SubGuild1ID) if err != nil { - s.logger.Fatal("Failed to get sub guild 1 info", zap.Error(err)) + s.logger.Error("Failed to get sub guild 1 info", zap.Error(err)) + return nil, err } else { alliance.SubGuild1 = *subGuild1 alliance.TotalMembers += subGuild1.MemberCount @@ -92,7 +94,8 @@ func buildAllianceObjectFromDbResult(result *sqlx.Rows, err error, s *Session) ( if alliance.SubGuild2ID > 0 { subGuild2, err := GetGuildInfoByID(s, alliance.SubGuild2ID) if err != nil { - s.logger.Fatal("Failed to get sub guild 2 info", zap.Error(err)) + s.logger.Error("Failed to get sub guild 2 info", zap.Error(err)) + return nil, err } else { alliance.SubGuild2 = *subGuild2 alliance.TotalMembers += subGuild2.MemberCount @@ -106,7 +109,7 @@ func handleMsgMhfCreateJoint(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfCreateJoint) _, err := s.server.db.Exec("INSERT INTO guild_alliances (name, parent_id) VALUES ($1, $2)", pkt.Name, pkt.GuildID) if err != nil { - s.logger.Fatal("Failed to create guild alliance in db", zap.Error(err)) + s.logger.Error("Failed to create guild alliance in db", zap.Error(err)) } doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x01, 0x01, 0x01, 0x01}) } @@ -116,11 +119,11 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) { guild, err := GetGuildInfoByID(s, pkt.GuildID) if err != nil { - s.logger.Fatal("Failed to get guild info", zap.Error(err)) + s.logger.Error("Failed to get guild info", zap.Error(err)) } alliance, err := GetAllianceData(s, pkt.AllianceID) if err != nil { - s.logger.Fatal("Failed to get alliance info", zap.Error(err)) + s.logger.Error("Failed to get alliance info", zap.Error(err)) } switch pkt.Action { @@ -128,7 +131,7 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) { if guild.LeaderCharID == s.charID && alliance.ParentGuildID == guild.ID { _, err = s.server.db.Exec("DELETE FROM guild_alliances WHERE id=$1", alliance.ID) if err != nil { - s.logger.Fatal("Failed to disband alliance", zap.Error(err)) + s.logger.Error("Failed to disband alliance", zap.Error(err)) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } else { diff --git a/server/channelserver/handlers_guild_member.go b/server/channelserver/handlers_guild_member.go index 2c3f0cd1a..3f19d4b2e 100644 --- a/server/channelserver/handlers_guild_member.go +++ b/server/channelserver/handlers_guild_member.go @@ -13,6 +13,8 @@ type GuildMember struct { CharID uint32 `db:"character_id"` JoinedAt *time.Time `db:"joined_at"` Souls uint32 `db:"souls"` + RPToday uint16 `db:"rp_today"` + RPYesterday uint16 `db:"rp_yesterday"` Name string `db:"name"` IsApplicant bool `db:"is_applicant"` OrderIndex uint8 `db:"order_index"` @@ -63,6 +65,8 @@ SELECT g.id as guild_id, joined_at, coalesce(souls, 0) as souls, + rp_today, + rp_yesterday, c.name, character.character_id, coalesce(gc.order_index, 0) as order_index, diff --git a/server/channelserver/handlers_guild_tresure.go b/server/channelserver/handlers_guild_tresure.go index c5e2fa0dc..c04007151 100644 --- a/server/channelserver/handlers_guild_tresure.go +++ b/server/channelserver/handlers_guild_tresure.go @@ -124,7 +124,10 @@ func treasureHuntUnregister(s *Session) { } var huntID int var hunters string - rows, _ := s.server.db.Queryx("SELECT id, hunters FROM guild_hunts WHERE guild_id=$1", guild.ID) + rows, err := s.server.db.Queryx("SELECT id, hunters FROM guild_hunts WHERE guild_id=$1", guild.ID) + if err != nil { + return + } for rows.Next() { rows.Scan(&huntID, &hunters) hunters = stringsupport.CSVRemove(hunters, int(s.charID)) diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index b3aadf372..ab150f180 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -246,18 +246,12 @@ func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) { var data []byte err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&data) if err != nil { - s.logger.Fatal("Failed to get preset decorations savedata from db", zap.Error(err)) + s.logger.Error("Failed to load decomyset", zap.Error(err)) } - - if len(data) > 0 { - doAckBufSucceed(s, pkt.AckHandle, data) - //doAckBufSucceed(s, pkt.AckHandle, data) - } else { - // set first byte to 1 to avoid pop up every time without save - body := make([]byte, 0x226) - body[0] = 1 - doAckBufSucceed(s, pkt.AckHandle, body) + if len(data) == 0 { + data = []byte{0x01, 0x00} } + doAckBufSucceed(s, pkt.AckHandle, data) } func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) { @@ -267,7 +261,7 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[1:]) // skip first unk byte err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&loadData) if err != nil { - s.logger.Fatal("Failed to get preset decorations savedata from db", zap.Error(err)) + s.logger.Error("Failed to load decomyset", zap.Error(err)) } else { numSets := bf.ReadUint8() // sets being written // empty save @@ -313,7 +307,7 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) { 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)) + s.logger.Error("Failed to save decomyset", zap.Error(err)) } } doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) diff --git a/server/channelserver/handlers_kouryou.go b/server/channelserver/handlers_kouryou.go index 57f26ea40..bff9292a6 100644 --- a/server/channelserver/handlers_kouryou.go +++ b/server/channelserver/handlers_kouryou.go @@ -12,7 +12,7 @@ func handleMsgMhfAddKouryouPoint(s *Session, p mhfpacket.MHFPacket) { var points int err := s.server.db.QueryRow("UPDATE characters SET kouryou_point=COALESCE(kouryou_point + $1, $1) WHERE id=$2 RETURNING kouryou_point", pkt.KouryouPoints, s.charID).Scan(&points) if err != nil { - s.logger.Fatal("Failed to update KouryouPoint in db", zap.Error(err)) + s.logger.Error("Failed to update KouryouPoint in db", zap.Error(err)) } resp := byteframe.NewByteFrame() resp.WriteUint32(uint32(points)) @@ -24,7 +24,7 @@ func handleMsgMhfGetKouryouPoint(s *Session, p mhfpacket.MHFPacket) { var points int err := s.server.db.QueryRow("SELECT COALESCE(kouryou_point, 0) FROM characters WHERE id = $1", s.charID).Scan(&points) if err != nil { - s.logger.Fatal("Failed to get kouryou_point savedata from db", zap.Error(err)) + s.logger.Error("Failed to get kouryou_point savedata from db", zap.Error(err)) } resp := byteframe.NewByteFrame() resp.WriteUint32(uint32(points)) @@ -37,7 +37,7 @@ func handleMsgMhfExchangeKouryouPoint(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfExchangeKouryouPoint) err := s.server.db.QueryRow("UPDATE characters SET kouryou_point=kouryou_point - $1 WHERE id=$2 RETURNING kouryou_point", pkt.KouryouPoints, s.charID).Scan(&points) if err != nil { - s.logger.Fatal("Failed to update platemyset savedata in db", zap.Error(err)) + s.logger.Error("Failed to update platemyset savedata in db", zap.Error(err)) } resp := byteframe.NewByteFrame() resp.WriteUint32(uint32(points)) diff --git a/server/channelserver/handlers_mail.go b/server/channelserver/handlers_mail.go index e31e90eed..1df4711c6 100644 --- a/server/channelserver/handlers_mail.go +++ b/server/channelserver/handlers_mail.go @@ -392,24 +392,29 @@ func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) { 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") + s.logger.Error("Failed to get guild info for mail") + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return } gm, err := GetGuildMembers(s, g.ID, false) if err != nil { - s.logger.Fatal("Failed to get guild members for mail") + s.logger.Error("Failed to get guild members for mail") + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return } 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") + s.logger.Error("Failed to send mail") + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return } } } 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") + s.logger.Error("Failed to send mail") } } - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index bdeb924de..c52802c73 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -11,115 +11,96 @@ import ( "io/ioutil" "os" "path/filepath" + "time" ) -// THERE ARE [PARTENER] [MERCENARY] [OTOMO AIRU] - -/////////////////////////////////////////// -/// PARTENER // -/////////////////////////////////////////// - func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadPartner) - // load partner from database var data []byte err := s.server.db.QueryRow("SELECT partner FROM characters WHERE id = $1", s.charID).Scan(&data) - if err != nil { - s.logger.Fatal("Failed to get partner savedata from db", zap.Error(err)) + if len(data) == 0 { + s.logger.Error("Failed to load partner", zap.Error(err)) + data = make([]byte, 9) } - if len(data) > 0 { - doAckBufSucceed(s, pkt.AckHandle, data) - } else { - doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) - } - // TODO(Andoryuuta): Figure out unusual double ack. One sized, one not. + doAckBufSucceed(s, pkt.AckHandle, data) doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSavePartner) - dumpSaveData(s, pkt.RawDataPayload, "partner") - _, err := s.server.db.Exec("UPDATE characters SET partner=$1 WHERE id=$2", pkt.RawDataPayload, s.charID) if err != nil { - s.logger.Fatal("Failed to update partner savedata in db", zap.Error(err)) + s.logger.Error("Failed to save partner", zap.Error(err)) } doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } func handleMsgMhfLoadLegendDispatch(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadLegendDispatch) - data := []byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x01, 0x8d, 0x40, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x02, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x04, 0x30, 0x40} - doAckBufSucceed(s, pkt.AckHandle, data) + bf := byteframe.NewByteFrame() + legendDispatch := []struct { + Unk uint32 + Timestamp uint32 + }{ + {0, uint32(Time_Current_Midnight().Add(-12 * time.Hour).Unix())}, + {0, uint32(Time_Current_Midnight().Add(12 * time.Hour).Unix())}, + {0, uint32(Time_Current_Midnight().Add(36 * time.Hour).Unix())}, + } + bf.WriteUint8(uint8(len(legendDispatch))) + for _, dispatch := range legendDispatch { + bf.WriteUint32(dispatch.Unk) + bf.WriteUint32(dispatch.Timestamp) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadHunterNavi) var data []byte err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data) - if err != nil { - s.logger.Fatal("Failed to get hunter navigation savedata from db", zap.Error(err)) - } - - if len(data) > 0 { - doAckBufSucceed(s, pkt.AckHandle, data) - } else { - // set first byte to 1 to avoid pop up every time without save - body := make([]byte, 0x226) - body[0] = 1 - doAckBufSucceed(s, pkt.AckHandle, body) + if len(data) == 0 { + s.logger.Error("Failed to load hunternavi", zap.Error(err)) + data = make([]byte, 0x226) } + doAckBufSucceed(s, pkt.AckHandle, data) } func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi) - - dumpSaveData(s, pkt.RawDataPayload, "hunternavi") - if pkt.IsDataDiff { var data []byte - // Load existing save err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data) if err != nil { - s.logger.Fatal("Failed to get hunternavi savedata from db", zap.Error(err)) + s.logger.Error("Failed to load hunternavi", zap.Error(err)) } // Check if we actually had any hunternavi data, using a blank buffer if not. // This is requried as the client will try to send a diff after character creation without a prior MsgMhfSaveHunterNavi packet. if len(data) == 0 { data = make([]byte, 0x226) - data[0] = 1 // set first byte to 1 to avoid pop up every time without save } // Perform diff and compress it to write back to db s.logger.Info("Diffing...") saveOutput := deltacomp.ApplyDataDiff(pkt.RawDataPayload, data) - _, err = s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", saveOutput, s.charID) if err != nil { - s.logger.Fatal("Failed to update hunternavi savedata in db", zap.Error(err)) + s.logger.Error("Failed to save hunternavi", zap.Error(err)) } - - s.logger.Info("Wrote recompressed hunternavi back to DB.") + 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 { - s.logger.Fatal("Failed to update hunternavi savedata in db", zap.Error(err)) + s.logger.Error("Failed to save hunternavi", zap.Error(err)) } } doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } -/////////////////////////////////////////// - -/////////////////////////////////////////// -/// MERCENARY // -/////////////////////////////////////////// - func handleMsgMhfMercenaryHuntdata(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfMercenaryHuntdata) if pkt.Unk0 == 1 { @@ -149,15 +130,11 @@ func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfCreateMercenary) - bf := byteframe.NewByteFrame() - var nextID uint32 - s.server.db.QueryRow("SELECT nextval('rasta_id_seq')").Scan(&nextID) - - bf.WriteUint32(nextID) // New MercID - bf.WriteUint32(0xDEADBEEF) // Unk - + _ = s.server.db.QueryRow("SELECT nextval('rasta_id_seq')").Scan(&nextID) + s.server.db.Exec("UPDATE characters SET rasta_id=$1 WHERE id=$2", nextID, s.charID) + bf.WriteUint32(nextID) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) } @@ -165,29 +142,66 @@ 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) + temp := byteframe.NewByteFrameFromBytes(pkt.MercData) + s.server.db.Exec("UPDATE characters SET savemercenary=$1, rasta_id=$2 WHERE id=$3", pkt.MercData, temp.ReadUint32(), s.charID) } - s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", pkt.GCP, s.charID) + s.server.db.Exec("UPDATE characters SET gcp=$1, pact_id=$2 WHERE id=$3", pkt.GCP, pkt.PactMercID, s.charID) doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfReadMercenaryW) - if pkt.Unk0 { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2)) + if pkt.Op > 0 { + bf := byteframe.NewByteFrame() + var pactID uint32 + var name string + var cid uint32 + + s.server.db.QueryRow("SELECT pact_id FROM characters WHERE id=$1", s.charID).Scan(&pactID) + if pactID > 0 { + s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid) + bf.WriteUint8(1) // numLends + bf.WriteUint32(pactID) + bf.WriteUint32(cid) + bf.WriteBool(false) // ? + bf.WriteUint32(uint32(Time_Current_Adjusted().Add(time.Hour * 24 * -8).Unix())) + bf.WriteUint32(uint32(Time_Current_Adjusted().Add(time.Hour * 24 * -1).Unix())) + bf.WriteBytes(stringsupport.PaddedString(name, 18, true)) + } else { + bf.WriteUint8(0) + } + + if pkt.Op < 2 { + var loans uint8 + temp := byteframe.NewByteFrame() + rows, _ := s.server.db.Query("SELECT name, id, pact_id FROM characters WHERE pact_id=(SELECT rasta_id FROM characters WHERE id=$1)", s.charID) + for rows.Next() { + loans++ + rows.Scan(&name, &cid, &pactID) + temp.WriteUint32(pactID) + temp.WriteUint32(cid) + temp.WriteUint32(uint32(Time_Current_Adjusted().Add(time.Hour * 24 * -8).Unix())) + temp.WriteUint32(uint32(Time_Current_Adjusted().Add(time.Hour * 24 * -1).Unix())) + temp.WriteBytes(stringsupport.PaddedString(name, 18, true)) + } + bf.WriteUint8(loans) + bf.WriteBytes(temp.Data()) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) return } var data []byte var gcp uint32 - s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", s.charID).Scan(&data) - s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id = $1", s.charID).Scan(&gcp) + s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data) + s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id=$1", s.charID).Scan(&gcp) resp := byteframe.NewByteFrame() + resp.WriteUint16(0) if len(data) == 0 { - resp.WriteBytes(make([]byte, 3)) + resp.WriteBool(false) } else { - resp.WriteBytes(data[1:]) - resp.WriteUint32(0) // Unk + resp.WriteBool(true) + resp.WriteBytes(data) } resp.WriteUint32(gcp) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) @@ -201,31 +215,33 @@ func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) { if len(data) == 0 { resp.WriteBool(false) } else { - resp.WriteBytes(data[4:]) + resp.WriteBytes(data) } doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } -func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {} - -/////////////////////////////////////////// - -/////////////////////////////////////////// -/// OTOMO AIRU // -/////////////////////////////////////////// +func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfContractMercenary) + switch pkt.Op { + case 0: + s.server.db.Exec("UPDATE characters SET pact_id=$1 WHERE id=$2", pkt.PactMercID, s.charID) + case 1: // Cancel lend + s.server.db.Exec("UPDATE characters SET pact_id=0 WHERE id=$1", s.charID) + case 2: // Cancel loan + s.server.db.Exec("UPDATE characters SET pact_id=0 WHERE id=$1", pkt.CID) + } + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) +} func handleMsgMhfLoadOtomoAirou(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadOtomoAirou) var data []byte err := s.server.db.QueryRow("SELECT otomoairou FROM characters WHERE id = $1", s.charID).Scan(&data) - if err != nil { - s.logger.Fatal("Failed to get partnyaa savedata from db", zap.Error(err)) - } - if len(data) > 0 { - doAckBufSucceed(s, pkt.AckHandle, data) - } else { - doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + if len(data) == 0 { + s.logger.Error("Failed to load otomoairou", zap.Error(err)) + data = make([]byte, 10) } + doAckBufSucceed(s, pkt.AckHandle, data) } func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) { @@ -411,5 +427,3 @@ func GetCatDetails(bf *byteframe.ByteFrame) []CatDefinition { } return cats } - -/////////////////////////////////////////// diff --git a/server/channelserver/handlers_plate.go b/server/channelserver/handlers_plate.go index 172a5f134..3f5688184 100644 --- a/server/channelserver/handlers_plate.go +++ b/server/channelserver/handlers_plate.go @@ -12,28 +12,23 @@ func handleMsgMhfLoadPlateData(s *Session, p mhfpacket.MHFPacket) { var data []byte err := s.server.db.QueryRow("SELECT platedata FROM characters WHERE id = $1", s.charID).Scan(&data) if err != nil { - s.logger.Error("Failed to get plate data savedata from db", zap.Error(err)) - } - - if len(data) > 0 { - doAckBufSucceed(s, pkt.AckHandle, data) - } else { - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + s.logger.Error("Failed to load platedata", zap.Error(err)) } + doAckBufSucceed(s, pkt.AckHandle, data) } func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSavePlateData) - dumpSaveData(s, pkt.RawDataPayload, "platedata") - if pkt.IsDataDiff { var data []byte // Load existing save err := s.server.db.QueryRow("SELECT platedata FROM characters WHERE id = $1", s.charID).Scan(&data) if err != nil { - s.logger.Fatal("Failed to get platedata savedata from db", zap.Error(err)) + s.logger.Error("Failed to load platedata", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + return } if len(data) > 0 { @@ -41,7 +36,9 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) { s.logger.Info("Decompressing...") data, err = nullcomp.Decompress(data) if err != nil { - s.logger.Fatal("Failed to decompress savedata from db", zap.Error(err)) + s.logger.Error("Failed to decompress platedata", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + return } } else { // create empty save if absent @@ -52,20 +49,25 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) { s.logger.Info("Diffing...") saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data)) if err != nil { - s.logger.Fatal("Failed to diff and compress platedata savedata", zap.Error(err)) + s.logger.Error("Failed to diff and compress platedata", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + return } _, err = s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", saveOutput, s.charID) if err != nil { - s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err)) + s.logger.Error("Failed to save platedata", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + return } - s.logger.Info("Wrote recompressed platedata back to DB.") + s.logger.Info("Wrote recompressed platedata back to DB") } else { + dumpSaveData(s, pkt.RawDataPayload, "platedata") // simply update database, no extra processing _, err := s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID) if err != nil { - s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err)) + s.logger.Error("Failed to save platedata", zap.Error(err)) } } @@ -77,28 +79,23 @@ func handleMsgMhfLoadPlateBox(s *Session, p mhfpacket.MHFPacket) { var data []byte err := s.server.db.QueryRow("SELECT platebox FROM characters WHERE id = $1", s.charID).Scan(&data) if err != nil { - s.logger.Error("Failed to get sigil box savedata from db", zap.Error(err)) - } - - if len(data) > 0 { - doAckBufSucceed(s, pkt.AckHandle, data) - } else { - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + s.logger.Error("Failed to load platebox", zap.Error(err)) } + doAckBufSucceed(s, pkt.AckHandle, data) } func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSavePlateBox) - dumpSaveData(s, pkt.RawDataPayload, "platebox") - if pkt.IsDataDiff { var data []byte // Load existing save err := s.server.db.QueryRow("SELECT platebox FROM characters WHERE id = $1", s.charID).Scan(&data) if err != nil { - s.logger.Fatal("Failed to get sigil box savedata from db", zap.Error(err)) + s.logger.Error("Failed to load platebox", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + return } // Decompress @@ -107,7 +104,9 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) { s.logger.Info("Decompressing...") data, err = nullcomp.Decompress(data) if err != nil { - s.logger.Fatal("Failed to decompress savedata from db", zap.Error(err)) + s.logger.Error("Failed to decompress platebox", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + return } } else { // create empty save if absent @@ -118,20 +117,25 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) { s.logger.Info("Diffing...") saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data)) if err != nil { - s.logger.Fatal("Failed to diff and compress savedata", zap.Error(err)) + s.logger.Error("Failed to diff and compress platebox", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + return } _, err = s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", saveOutput, s.charID) if err != nil { - s.logger.Fatal("Failed to update platebox savedata in db", zap.Error(err)) + s.logger.Error("Failed to save platebox", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + return } - s.logger.Info("Wrote recompressed platebox back to DB.") + s.logger.Info("Wrote recompressed platebox back to DB") } else { + dumpSaveData(s, pkt.RawDataPayload, "platebox") // simply update database, no extra processing _, err := s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", pkt.RawDataPayload, s.charID) if err != nil { - s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err)) + s.logger.Error("Failed to save platebox", zap.Error(err)) } } doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) @@ -141,16 +145,11 @@ func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadPlateMyset) var data []byte err := s.server.db.QueryRow("SELECT platemyset FROM characters WHERE id = $1", s.charID).Scan(&data) - if err != nil { - s.logger.Fatal("Failed to get presets sigil savedata from db", zap.Error(err)) - } - - if len(data) > 0 { - doAckBufSucceed(s, pkt.AckHandle, data) - } else { - blankData := make([]byte, 0x780) - doAckBufSucceed(s, pkt.AckHandle, blankData) + if len(data) == 0 { + s.logger.Error("Failed to load platemyset", zap.Error(err)) + data = make([]byte, 0x780) } + doAckBufSucceed(s, pkt.AckHandle, data) } func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) { @@ -159,7 +158,7 @@ func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) { 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)) + s.logger.Error("Failed to save platemyset", zap.Error(err)) } doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 76e2db778..9caef6dbf 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -1,6 +1,7 @@ package channelserver import ( + "encoding/hex" "fmt" "io" "os" @@ -113,16 +114,32 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { } vsQuestItems := []uint16{1580, 1581, 1582, 1583, 1584, 1585, 1587, 1588, 1589, 1595, 1596, 1597, 1598, 1599, 1600, 1601, 1602, 1603, 1604} + vsQuestBets := []struct { + IsTicket bool + Quantity uint32 + }{ + {true, 5}, + {false, 1000}, + {false, 5000}, + {false, 10000}, + } + + data, _ := hex.DecodeString("06E601CF051406E6D4D4D40007CA02F006E6D4D4D4000685051506E6D4D4D40007CA051206E6D4D4D40007CA051306E6D4D4D40007CA02DC06E6D4D4D40006E202D806E6D4D4D40006E202A406E6D4D4D40006E5026806E6D4D4D40006E3027406E6D4D4D40006E3027006E6D4D4D40006E4026506E6D4D4D40006E602E006E6D4D4D40006EE02E706E6D4D4D40006E70B4006E6D4D4D40006E602E206E6D4D4D400068202E506E6D4D4D400068206C706E6D4D4D40006E706B606E6D4D4D40006E706D706E6D4D4D40006E706F206E6D4D4D40006E706DD06E6D4D4D40006E706D306E6D4D4D40006E706FC06E6D4D4D40006E706B806E6D4D4D40006E706CE06E6D4D4D40006E706FD06E6D4D4D40006E706A506E6D4D4D40006E7051906E6D4D4D40006EE02E606E6D4D4D40006700B4106E6D4D4D40006E60B4E06E6D4D4D40006E60B4F06E6D4D4D40006E60B4C06E6D4D4D40006E60B4D06E6D4D4D40006E60B4A06E6D4D4D40006E60B4B06E6D4D4D40006E60B4806E6D4D4D40006E60B4906E6D4D4D40006E60B5606E6D4D4D40006E60B5706E6D4D4D40006E60B5406E6D4D4D40006E60B2606E6D4D4D40006E60B2706E6D4D4D40006E602DE06E6D4D4D40006E702DF06E6D4D4D40006E70B2406E6D4D4D40006E60B2506E6D4D4D40006E60A3006E6D4D4D400062E0A3106E6D4D4D400062E0A3E06E6D4D4D400062E0B2206E6D4D4D40006E60B2306E6D4D4D40006E60B2006E6D4D4D40006E60B2106E6D4D4D40006E60B2E06E6D4D4D40006E60B2F06E6D4D4D40006E6051A06E6D4D4D4000682051B06E6D4D4D400077602CD06E6D4D4D40006BC0B2C06E6D4D4D40006E60A3F06E6D4D4D400062E0A3C06E6D4D4D400062E0A3D06E6D4D4D400062E0A3A06E6D4D4D400062E0A3B06E6D4D4D400062E0A3806E6D4D4D400062E0A3906E6D4D4D400062E0A0606E6D4D4D400062E0A0706E6D4D4D400062E0A0406E6D4D4D400062E0A0506E6D4D4D400062E0A0206E6D4D4D400062E0A0306E6D4D4D400062E0A0006E6D4D4D400062E0A0106E6D4D4D400062E0A0E06E6D4D4D400062E0A0F06E6D4D4D400062E0A0C06E6D4D4D400062E0A0D06E6D4D4D400062E0A0A06E6D4D4D400062E0A0B06E6D4D4D400062E0A0806E6D4D4D400062E0A0906E6D4D4D400062E0A1606E6D4D4D40007CA0A1706E6D4D4D40007CA0A1406E6D4D4D40007CA0A1506E6D4D4D40007CA0A1206E6D4D4D40007CA0A1306E6D4D4D40007CA0A1006E6D4D4D40007CA0A1106E6D4D4D40007CA0A1E06E6D4D4D40007CA0A1F06E6D4D4D40007CA0A1C06E6D4D4D40007CA0A1D06E6D4D4D40007CA0A1A06E6D4D4D40007CA0A1B06E6D4D4D40007CA0A1806E6D4D4D40007CA0A1906E6D4D4D40007CA0BE606E6D4D4D40007CA0BE706E6D4D4D40007CA0BE406E6D4D4D40007CA0BE506E6D4D4D40007CA0BE206E6D4D4D40007CA0B2D06E6D4D4D40006E60B2A06E6D4D4D40006E60BE306E6D4D4D40007CA02D606E6D4D4D40007CA029F06E6D4D4D400062E0B9406E6D4D4D40006820BE006E6D4D4D40007CA0BE106E6D4D4D40007CA0BEE06E6D4D4D40007CA0BEF06E6D4D4D40007CA02C106E6D4D4D400C5B602CE06E6D4D4D400C5B602CF06E6D4D4D400674E02CC06E6D4D4D400674E051006E6D4D4D400062E02FA06E6D4D4D400062E02CA06E6D4D4D40006B602CB06E6D4D4D40006A0051106E6D4D4D400062E02FD06E6D4D4D400062E0B9506E6D4D4D40006820B9206E6D4D4D40006820B9306E6D4D4D40006820B9006E6D4D4D40006820B9106E6D4D4D40006820B9E06E6D4D4D40006820B9F06E6D4D4D40006820BEC06E6D4D4D40006820BED06E6D4D4D40006820BEA06E6D4D4D40006820BEB06E6D4D4D40006820BE806E6D4D4D40006820BE906E6D4D4D40006820BF606E6D4D4D40006820BF706E6D4D4D40006820BF406E6D4D4D40006820BF506E6D4D4D40006820BF206E6D4D4D40006820BF306E6D4D4D40006820BF006E6D4D4D40006820BF106E6D4D4D400068202DB06E6D4D4D40006E70B9C06E6D4D4D40006820BFE06E6D4D4D40006820BFF06E6D4D4D400068202A706E6D4D4D40006E70B9D06E6D4D4D40006820BFC06E6D4D4D400068202D106E6D4D4D40006E702DD06E6D4D4D40006E402DA06E6D4D4D40006EC02D906E6D4D4D40006E402A606E6D4D4D40006EC02A506E6D4D4D40006E402A206E6D4D4D40006EC02A106E6D4D4D40006E402AE06E6D4D4D40006EC02AD06E6D4D4D40006E402AA06E6D4D4D40006EC02A906E6D4D4D40006E402B606E6D4D4D40006EC0BFD06E6D4D4D40006820BFA06E6D4D4D40006820BFB06E6D4D4D40006820BF806E6D4D4D40006820BF906E6D4D4D40006820BC606E6D4D4D40006820BC706E6D4D4D40006820BC406E6D4D4D40006820BC506E6D4D4D40006820BC206E6D4D4D40006820BC306E6D4D4D40006820BC006E6D4D4D40006820BC106E6D4D4D40006820BCE06E6D4D4D40006820BCF06E6D4D4D40006820BCC06E6D4D4D40006820BCD06E6D4D4D40006820BCA06E6D4D4D40006820BCB06E6D4D4D40006820BC806E6D4D4D40006820BC906E6D4D4D40006820BD606E6D4D4D40006820BD706E6D4D4D40006820BD406E6D4D4D40006820BD506E6D4D4D40006820BD206E6D4D4D40006820BD306E6D4D4D40006820BD006E6D4D4D40006820BD106E6D4D4D40006820BDE06E6D4D4D40006820BDF06E6D4D4D40006820BDC06E6D4D4D40006820BDD06E6D4D4D40006820BDA06E6D4D4D40006820BDB06E6D4D4D40006820B9A06E6D4D4D40006820BD806E6D4D4D40006820BD906E6D4D4D40006820BA606E6D4D4D40006820BA706E6D4D4D40006820BA406E6D4D4D40006820BA506E6D4D4D40006820BA206E6D4D4D40006820BA306E6D4D4D40006820BA006E6D4D4D40006820BA106E6D4D4D40006820BAE06E6D4D4D40006820BAF06E6D4D4D40006820BAC06E6D4D4D40006820BBE06E6D4D4D40006820BBF06E6D4D4D40006820BBC06E6D4D4D40006820BBD06E6D4D4D40006820BBA06E6D4D4D40006820BBB06E6D4D4D40006820BB806E6D4D4D40006820BB906E6D4D4D40006820B8606E6D4D4D40006820B8706E6D4D4D40006820B8406E6D4D4D40006820B8506E6D4D4D40006820B8206E6D4D4D400068202D706E6D4D4D40007CA02D406E6D4D4D40007CA026E06E6D4D4D40007CA0B9B06E6D4D4D40006820B9806E6D4D4D40006820B6A06E6D4D4D40006820B6B06E6D4D4D4000682029C06E6D4D4D40006E60B6806E6D4D4D4000682029D06E6D4D4D40006E60B6906E6D4D4D40006820A6E06E6D4D4D40006E60A6F06E6D4D4D40006E60A6C06E6D4D4D40006E60A6D06E6D4D4D40006E60A6A06E6D4D4D40006E60A6B06E6D4D4D40006E60A6806E6D4D4D40006E60A6906E6D4D4D40006E60A7606E6D4D4D40006E60A7706E6D4D4D40006E602E406E6D4D4D40005010A7406E6D4D4D40006E60A7506E6D4D4D40006E602D006E6D4D4D40006E60A7206E6D4D4D40006E6026606E6D4D4D400028C0A4406E6D4D4D40006E60A4506E6D4D4D40006E6026C06E6D4D4D40006E7026D06E6D4D4D40006E5026A06E6D4D4D40006E3026B06E6D4D4D40006E70A4206E6D4D4D40006E6026906E6D4D4D40006E7027606E6D4D4D40006E5027706E6D4D4D40006E50A4306E6D4D4D40006E6027506E6D4D4D40006E7027206E6D4D4D40006E7027306E6D4D4D40006E70A4006E6D4D4D40006E60A4106E6D4D4D40006E60A4E06E6D4D4D40006E60A4F06E6D4D4D40006E60A4C06E6D4D4D40006E60A4D06E6D4D4D40006E60A4A06E6D4D4D40006E60A4B06E6D4D4D40006E60A4806E6D4D4D40006E6029E06E6D4D4D40006E602A306E6D4D4D40006E402A006E6D4D4D40006E302AF06E6D4D4D40006E402AC06E6D4D4D40006E302AB06E6D4D4D40006E402A806E6D4D4D40006E3027106E6D4D4D40006E2027E06E6D4D4D40006EC027F06E6D4D4D40006E2027C06E6D4D4D40006EC027D06E6D4D4D40006E40B7606E6D4D4D40006820B7706E6D4D4D40006820B7406E6D4D4D40006820B7506E6D4D4D40006820B7206E6D4D4D40006820B7306E6D4D4D40006820B7006E6D4D4D40006820B7106E6D4D4D40006820B7E06E6D4D4D40006820B3C06E6D4D4D40006E60D3406E6D4D4D40006820D3506E6D4D4D40006820B3D06E6D4D4D40006E6026706E6D4D4D40006E60D5E06E6D4D4D40006820D5F06E6D4D4D40006820D5C06E6D4D4D40006820D5D06E6D4D4D40006820D5A06E6D4D4D40006820D5B06E6D4D4D40006820D5806E6D4D4D40006820D5906E6D4D4D40006820D2606E6D4D4D40006820D2706E6D4D4D40006820D2406E6D4D4D40006820D2506E6D4D4D40006820D2206E6D4D4D40006820D3206E6D4D4D40006820D3306E6D4D4D40006820D3006E6D4D4D40006820D3106E6D4D4D40006820D3E06E6D4D4D40006820D2306E6D4D4D40006820D2006E6D4D4D40006820D2106E6D4D4D40006820D2E06E6D4D4D40006820D2F06E6D4D4D40006820D2C06E6D4D4D40006820D2D06E6D4D4D40006820D2A06E6D4D4D40006820D2B06E6D4D4D40006820D2806E6D4D4D40006820D2906E6D4D4D40006820D3606E6D4D4D40006820D3706E6D4D4D400068202E306E6D4D4D40006F802E106E6D4D4D40006820D3F06E6D4D4D40006820D0A06E6D4D4D40006820D0B06E6D4D4D40006820D0806E6D4D4D40006820D0906E6D4D4D40006820D1606E6D4D4D40006820D1706E6D4D4D40006820D1406E6D4D4D40006820D1506E6D4D4D40006820D1206E6D4D4D40006820D3C06E6D4D4D400068202B406E6D4D4D40006E60D1306E6D4D4D40006820D1006E6D4D4D40006820D1106E6D4D4D40006820D1E06E6D4D4D40006820AE006E6D4D4D40006820AE106E6D4D4D40006820AEE06E6D4D4D40006820AEF06E6D4D4D40006820AEC06E6D4D4D40006820AED06E6D4D4D40006820AEA06E6D4D4D40006820AEB06E6D4D4D40006820AE806E6D4D4D40006820AE906E6D4D4D40006820AF606E6D4D4D4000682026406E6D4D4D40006E60AF706E6D4D4D40006820B3A06E6D4D4D40006E60AB206E6D4D4D40006E60B3B06E6D4D4D40006E60D3D06E6D4D4D40006820D3A06E6D4D4D40006820D3B06E6D4D4D40006820AB306E6D4D4D40006E60AB006E6D4D4D40006E60AB106E6D4D4D40006E60ABE06E6D4D4D40006E60ABF06E6D4D4D40006E60ABC06E6D4D4D40006E60ABD06E6D4D4D40006E60ABA06E6D4D4D40006E60ABB06E6D4D4D40006E60AB806E6D4D4D40006E60AB906E6D4D4D40006E60A8606E6D4D4D40006E60A8806E6D4D4D40006E60A8906E6D4D4D40006E60A9606E6D4D4D40006E60A9706E6D4D4D40006E60A9406E6D4D4D40006E60A9506E6D4D4D40006E60A9206E6D4D4D40006E60A9306E6D4D4D40006E60A9006E6D4D4D40006E60A9106E6D4D4D40006E60A9E06E6D4D4D40006E60A9F06E6D4D4D40006E60A9C06E6D4D4D40006E60D3806E6D4D4D40006820D3906E6D4D4D40006820D0606E6D4D4D40006820D0706E6D4D4D40006820D0406E6D4D4D40006820D0506E6D4D4D40006820D0206E6D4D4D40006820D0306E6D4D4D40006820D0006E6D4D4D40006820D0106E6D4D4D40006820D0E06E6D4D4D40006820D0F06E6D4D4D40006820D0C06E6D4D4D40006820D0D06E6D4D4D40006820B3806E6D4D4D40006E60B3906E6D4D4D40006E60B0606E6D4D4D40006E60B0706E6D4D4D40006E60B0406E6D4D4D40006E60B0506E6D4D4D40006E60B0206E6D4D4D40006E60B0306E6D4D4D40006E60B0006E6D4D4D40006E60B1206E6D4D4D40006E60B1306E6D4D4D40006E60B1006E6D4D4D40006E60B1106E6D4D4D40006E60B1E06E6D4D4D40006E60B1F06E6D4D4D40006E60B1C06E6D4D4D40006E60B1D06E6D4D4D40006E60B1A06E6D4D4D40006E60B1B06E6D4D4D40006E60B1806E6D4D4D40006E60B1906E6D4D4D40006E608E606E6D4D4D40006E6029B06E6D4D4D40006F20AF406E6D4D4D4000682026006E6D4D4D40006E70AC606E6D4D4D40006820AC706E6D4D4D40006820AC406E6D4D4D40006820AC506E6D4D4D40006820AC206E6D4D4D40006820AC306E6D4D4D40006820AC006E6D4D4D40006820AC106E6D4D4D40006820ACE06E6D4D4D40006820ACF06E6D4D4D40006820ACC06E6D4D4D40006820ACD06E6D4D4D40006820ACA06E6D4D4D4000682027A06E6D4D4D40006E30ADC06E6D4D4D40006820ADD06E6D4D4D40006820ADA06E6D4D4D40006820ADB06E6D4D4D40006820AD806E6D4D4D40006820AD906E6D4D4D40006820AA606E6D4D4D40006820AA706E6D4D4D40006820AA406E6D4D4D40006820AA506E6D4D4D40006820AA206E6D4D4D40006820AA306E6D4D4D40006820AA006E6D4D4D4000682") + bf.WriteBytes(data) - bf.WriteUint16(0) // Unk - bf.WriteUint16(0) // Unk bf.WriteUint16(uint16(len(vsQuestItems))) - bf.WriteUint32(0) // Unk + bf.WriteUint32(uint32(len(vsQuestBets))) bf.WriteUint16(0) // Unk for i := range vsQuestItems { bf.WriteUint16(vsQuestItems[i]) } + for i := range vsQuestBets { + bf.WriteBool(vsQuestBets[i].IsTicket) + bf.WriteUint8(9) + bf.WriteUint16(7) + bf.WriteUint32(vsQuestBets[i].Quantity) + } bf.WriteUint16(totalCount) bf.WriteUint16(pkt.Offset) diff --git a/server/channelserver/handlers_register.go b/server/channelserver/handlers_register.go index 2c44cc6db..301c192b0 100644 --- a/server/channelserver/handlers_register.go +++ b/server/channelserver/handlers_register.go @@ -21,7 +21,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint8(1) resp.WriteUint8(dest) ref := &s.server.raviente.state.stateData[dest] - damageMultiplier := s.server.raviente.state.damageMultiplier + damageMultiplier := s.server.raviente.GetRaviMultiplier(s.server) switch op { case 2: resp.WriteUint32(*ref) @@ -252,21 +252,21 @@ func (s *Session) notifyRavi() { raviNotif.WriteUint16(uint16(temp.Opcode())) temp.Build(raviNotif, s.clientContext) raviNotif.WriteUint16(0x0010) // End it. - sema := getRaviSemaphore(s) - if sema != "" { - for session := range s.server.semaphore[sema].clients { + sema := getRaviSemaphore(s.server) + if sema != nil { + for session := range sema.clients { session.QueueSend(raviNotif.Data()) } } } -func getRaviSemaphore(s *Session) string { - for _, semaphore := range s.server.semaphore { - if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "4") { - return semaphore.id_semaphore +func getRaviSemaphore(s *Server) *Semaphore { + for _, semaphore := range s.semaphore { + if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "3") { + return semaphore } } - return "" + return nil } func resetRavi(s *Session) { @@ -278,7 +278,6 @@ func resetRavi(s *Session) { s.server.raviente.register.ravienteType = 0 s.server.raviente.register.maxPlayers = 0 s.server.raviente.register.carveQuest = 0 - s.server.raviente.state.damageMultiplier = 1 s.server.raviente.register.register = []uint32{0, 0, 0, 0, 0} s.server.raviente.state.stateData = []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} s.server.raviente.support.supportData = []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} diff --git a/server/channelserver/handlers_rengoku.go b/server/channelserver/handlers_rengoku.go index c1e8d2dbd..732cb77ef 100644 --- a/server/channelserver/handlers_rengoku.go +++ b/server/channelserver/handlers_rengoku.go @@ -19,7 +19,9 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) { 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)) + s.logger.Error("Failed to save rengokudata", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return } bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) bf.Seek(71, 0) @@ -34,7 +36,7 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) { s.server.db.Exec("INSERT INTO rengoku_score (character_id) VALUES ($1)", s.charID) } s.server.db.Exec("UPDATE rengoku_score SET max_stages_mp=$1, max_points_mp=$2, max_stages_sp=$3, max_points_sp=$4 WHERE character_id=$5", maxStageMp, maxScoreMp, maxStageSp, maxScoreSp, s.charID) - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgMhfLoadRengokuData(s *Session, p mhfpacket.MHFPacket) { @@ -42,7 +44,7 @@ func handleMsgMhfLoadRengokuData(s *Session, p mhfpacket.MHFPacket) { var data []byte err := s.server.db.QueryRow("SELECT rengokudata FROM characters WHERE id = $1", s.charID).Scan(&data) if err != nil { - s.logger.Fatal("Failed to get rengokudata savedata from db", zap.Error(err)) + s.logger.Error("Failed to load rengokudata", zap.Error(err)) } if len(data) > 0 { doAckBufSucceed(s, pkt.AckHandle, data) diff --git a/server/channelserver/handlers_reward.go b/server/channelserver/handlers_reward.go index 6552f2c61..de2877572 100644 --- a/server/channelserver/handlers_reward.go +++ b/server/channelserver/handlers_reward.go @@ -49,7 +49,3 @@ func handleMsgMhfAcquireMonthlyReward(s *Session, p mhfpacket.MHFPacket) { } func handleMsgMhfAcceptReadReward(s *Session, p mhfpacket.MHFPacket) {} - -func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {} - -func handleMsgMhfGetWeeklySeibatuRankingReward(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 350e31acd..29c3f4c64 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -2,14 +2,10 @@ package channelserver import ( "encoding/hex" - ps "erupe-ce/common/pascalstring" - "time" - "erupe-ce/common/byteframe" + ps "erupe-ce/common/pascalstring" "erupe-ce/network/mhfpacket" - "github.com/lib/pq" - "github.com/sachaos/lottery" - "go.uber.org/zap" + "math/rand" ) type ShopItem struct { @@ -28,16 +24,36 @@ type ShopItem struct { } type Gacha struct { - ID uint32 `db:"id"` - MinGR uint32 `db:"min_gr"` - MinHR uint32 `db:"min_hr"` - Name string `db:"name"` - Link1 string `db:"link1"` - Link2 string `db:"link2"` - Link3 string `db:"link3"` - Icon uint16 `db:"icon"` - Type uint16 `db:"type"` - Hide bool `db:"hide"` + ID uint32 `db:"id"` + MinGR uint32 `db:"min_gr"` + MinHR uint32 `db:"min_hr"` + Name string `db:"name"` + URLBanner string `db:"url_banner"` + URLFeature string `db:"url_feature"` + URLThumbnail string `db:"url_thumbnail"` + Wide bool `db:"wide"` + Recommended bool `db:"recommended"` + GachaType uint8 `db:"gacha_type"` + Hidden bool `db:"hidden"` +} + +type GachaEntry struct { + EntryType uint8 `db:"entry_type"` + ID uint32 `db:"id"` + ItemType uint8 `db:"item_type"` + ItemNumber uint16 `db:"item_number"` + ItemQuantity uint16 `db:"item_quantity"` + Weight float64 `db:"weight"` + Rarity uint8 `db:"rarity"` + Rolls uint8 `db:"rolls"` + FrontierPoints uint16 `db:"frontier_points"` + DailyLimit uint8 `db:"daily_limit"` +} + +type GachaItem struct { + ItemType uint8 `db:"item_type"` + ItemID uint16 `db:"item_id"` + Quantity uint16 `db:"quantity"` } func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { @@ -55,7 +71,7 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { switch pkt.ShopType { case 1: // Running gachas var count uint16 - shopEntries, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, link1, link2, link3, icon, type, hide FROM gacha_shop") + shopEntries, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden FROM gacha_shop") if err != nil { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) return @@ -74,12 +90,18 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint32(gacha.MinHR) resp.WriteUint32(0) // only 0 in known packet ps.Uint8(resp, gacha.Name, true) - ps.Uint8(resp, gacha.Link1, false) - ps.Uint8(resp, gacha.Link2, false) - resp.WriteBool(gacha.Hide) - ps.Uint8(resp, gacha.Link3, false) - resp.WriteUint16(gacha.Icon) - resp.WriteUint16(gacha.Type) + ps.Uint8(resp, gacha.URLBanner, false) + ps.Uint8(resp, gacha.URLFeature, false) + resp.WriteBool(gacha.Wide) + ps.Uint8(resp, gacha.URLThumbnail, false) + resp.WriteUint8(0) // Unk + if gacha.Recommended { + resp.WriteUint8(2) + } else { + resp.WriteUint8(0) + } + resp.WriteUint8(gacha.GachaType) + resp.WriteBool(gacha.Hidden) count++ } resp.Seek(0, 0) @@ -87,47 +109,62 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint16(count) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) case 2: // Actual gacha - shopEntries, err := s.server.db.Query("SELECT entryType, itemhash, currType, currNumber, currQuant, percentage, rarityIcon, rollsCount, itemCount, dailyLimit, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1", pkt.ShopID) + bf := byteframe.NewByteFrame() + bf.WriteUint32(pkt.ShopID) + var gachaType int + s.server.db.QueryRow(`SELECT gacha_type FROM gacha_shop WHERE id = $1`, pkt.ShopID).Scan(&gachaType) + entries, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID) if err != nil { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) return } - var entryType, currType, rarityIcon, rollsCount, itemCount, dailyLimit uint8 - var currQuant, currNumber, percentage uint16 - var itemhash uint32 - var itemType, itemId, quantity pq.Int64Array - var count uint16 - resp := byteframe.NewByteFrame() - resp.WriteUint32(pkt.ShopID) - resp.WriteUint16(0) // total defs - for shopEntries.Next() { - err = shopEntries.Scan(&entryType, &itemhash, &currType, &currNumber, &currQuant, &percentage, &rarityIcon, &rollsCount, &itemCount, &dailyLimit, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity)) + var divisor float64 + s.server.db.QueryRow(`SELECT COALESCE(SUM(weight) / 100000.0, 0) AS chance FROM gacha_entries WHERE gacha_id = $1`, pkt.ShopID).Scan(&divisor) + var entryCount uint16 + bf.WriteUint16(0) + gachaEntry := GachaEntry{} + gachaItem := GachaItem{} + for entries.Next() { + entryCount++ + entries.StructScan(&gachaEntry) + bf.WriteUint8(gachaEntry.EntryType) + bf.WriteUint32(gachaEntry.ID) + bf.WriteUint8(gachaEntry.ItemType) + bf.WriteUint16(0) + bf.WriteUint16(gachaEntry.ItemNumber) + bf.WriteUint16(gachaEntry.ItemQuantity) + if gachaType >= 4 { // If box + bf.WriteUint16(1) + } else { + bf.WriteUint16(uint16(gachaEntry.Weight / divisor)) + } + bf.WriteUint8(gachaEntry.Rarity) + bf.WriteUint8(gachaEntry.Rolls) + + var itemCount uint8 + temp := byteframe.NewByteFrame() + items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id=$1`, gachaEntry.ID) if err != nil { - panic(err) + bf.WriteUint8(0) + } else { + for items.Next() { + itemCount++ + items.StructScan(&gachaItem) + temp.WriteUint16(uint16(gachaItem.ItemType)) + temp.WriteUint16(gachaItem.ItemID) + temp.WriteUint16(gachaItem.Quantity) + } + bf.WriteUint8(itemCount) } - resp.WriteUint8(entryType) - resp.WriteUint32(itemhash) - resp.WriteUint8(currType) - resp.WriteUint16(0) // unk, always 0 in existing packets - resp.WriteUint16(currNumber) // it's either item ID or quantity for gacha coins - resp.WriteUint16(currQuant) // only for item ID - resp.WriteUint16(percentage) - resp.WriteUint8(rarityIcon) - resp.WriteUint8(rollsCount) - resp.WriteUint8(itemCount) - resp.WriteUint16(0) // unk, always 0 in existing packets - resp.WriteUint8(dailyLimit) - resp.WriteUint8(0) // unk, always 0 in existing packets - for i := 0; i < int(itemCount); i++ { - resp.WriteUint16(uint16(itemType[i])) // unk, always 0 in existing packets - resp.WriteUint16(uint16(itemId[i])) // unk, always 0 in existing packets - resp.WriteUint16(uint16(quantity[i])) // unk, always 0 in existing packets - } - count++ + + bf.WriteUint16(gachaEntry.FrontierPoints) + bf.WriteUint8(gachaEntry.DailyLimit) + bf.WriteUint8(0) + bf.WriteBytes(temp.Data()) } - resp.Seek(4, 0) - resp.WriteUint16(count) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + bf.Seek(4, 0) + bf.WriteUint16(entryCount) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) case 4: // N Points, 0-6 doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) case 5: // GCP->Item, 0-6 @@ -206,124 +243,363 @@ func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory) bf := byteframe.NewByteFrame() - bf.WriteUint8(0) + bf.WriteUint8(1) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetGachaPoint) var fp, gp, gt uint32 - s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_prem, 0), COALESCE(gacha_trial,0) FROM characters WHERE id=$1", s.charID).Scan(&fp, &gp, >) + s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_premium, 0), COALESCE(gacha_trial, 0) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)", s.charID).Scan(&fp, &gp, >) resp := byteframe.NewByteFrame() - resp.WriteUint32(gp) // Real Gacha Points? - resp.WriteUint32(gt) // Trial Gacha Point? - resp.WriteUint32(fp) // Frontier Points? + resp.WriteUint32(gp) + resp.WriteUint32(gt) + resp.WriteUint32(fp) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } -type gachaItem struct { - itemhash uint32 - percentage uint16 - rarityIcon byte - itemCount byte - itemType pq.Int64Array - itemId pq.Int64Array - quantity pq.Int64Array +func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfUseGachaPoint) + if pkt.TrialCoins > 0 { + s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.TrialCoins, s.charID) + } + if pkt.PremiumCoins > 0 { + s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.PremiumCoins, s.charID) + } + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } -func (i gachaItem) Weight() int { - return int(i.percentage) +func spendGachaCoin(s *Session, quantity uint16) { + var gt uint16 + s.server.db.QueryRow(`SELECT COALESCE(gacha_trial, 0) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(>) + if quantity <= gt { + s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, quantity, s.charID) + } else { + s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, quantity, s.charID) + } +} + +func transactGacha(s *Session, gachaID uint32, rollID uint8) (error, int) { + var itemType uint8 + var itemNumber uint16 + var rolls int + err := s.server.db.QueryRowx(`SELECT item_type, item_number, rolls FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, gachaID, rollID).Scan(&itemType, &itemNumber, &rolls) + if err != nil { + return err, 0 + } + switch itemType { + /* + valid types that need manual savedata manipulation: + - Ryoudan Points + - Bond Points + - Image Change Points + valid types that work (no additional code needed): + - Tore Points + - Festa Points + */ + case 17: + _ = addPointNetcafe(s, int(itemNumber)*-1) + case 19: + fallthrough + case 20: + spendGachaCoin(s, itemNumber) + case 21: + s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", itemNumber, s.charID) + } + return nil, rolls +} + +func getGuaranteedItems(s *Session, gachaID uint32, rollID uint8) []GachaItem { + var rewards []GachaItem + var reward GachaItem + items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = (SELECT id FROM gacha_entries WHERE entry_type = $1 AND gacha_id = $2)`, rollID, gachaID) + if err == nil { + for items.Next() { + items.StructScan(&reward) + rewards = append(rewards, reward) + } + } + return rewards +} + +func addGachaItem(s *Session, items []GachaItem) { + var data []byte + s.server.db.QueryRow(`SELECT gacha_items FROM characters WHERE id = $1`, s.charID).Scan(&data) + if len(data) > 0 { + numItems := int(data[0]) + data = data[1:] + oldItem := byteframe.NewByteFrameFromBytes(data) + for i := 0; i < numItems; i++ { + items = append(items, GachaItem{ + ItemType: oldItem.ReadUint8(), + ItemID: oldItem.ReadUint16(), + Quantity: oldItem.ReadUint16(), + }) + } + } + newItem := byteframe.NewByteFrame() + newItem.WriteUint8(uint8(len(items))) + for i := range items { + newItem.WriteUint8(items[i].ItemType) + newItem.WriteUint16(items[i].ItemID) + newItem.WriteUint16(items[i].Quantity) + } + s.server.db.Exec(`UPDATE characters SET gacha_items = $1 WHERE id = $2`, newItem.Data(), s.charID) +} + +func getRandomEntries(entries []GachaEntry, rolls int, isBox bool) ([]GachaEntry, error) { + var chosen []GachaEntry + var totalWeight float64 + for i := range entries { + totalWeight += entries[i].Weight + } + for { + if !isBox { + result := rand.Float64() * totalWeight + for _, entry := range entries { + result -= entry.Weight + if result < 0 { + chosen = append(chosen, entry) + break + } + } + } else { + result := rand.Intn(len(entries)) + chosen = append(chosen, entries[result]) + entries[result] = entries[len(entries)-1] + entries = entries[:len(entries)-1] + } + if rolls == len(chosen) { + break + } + } + return chosen, nil +} + +func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem) + var data []byte + err := s.server.db.QueryRow("SELECT COALESCE(gacha_items, $2) FROM characters WHERE id = $1", s.charID, []byte{0x00}).Scan(&data) + if err != nil { + data = []byte{0x00} + } + + // I think there are still some edge cases where rewards can be nulled via overflow + if data[0] > 36 || len(data) > 181 { + resp := byteframe.NewByteFrame() + resp.WriteUint8(36) + resp.WriteBytes(data[1:181]) + doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + } else { + doAckBufSucceed(s, pkt.AckHandle, data) + } + + if !pkt.Freeze { + if data[0] > 36 || len(data) > 181 { + update := byteframe.NewByteFrame() + update.WriteUint8(uint8(len(data[181:]) / 5)) + update.WriteBytes(data[181:]) + s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", update.Data(), s.charID) + } else { + s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID) + } + } } func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha) - // needs to query db for input gacha and return a result or number of results - // uint8 number of results - // uint8 item type - // uint16 item id - // uint16 quantity - - var currType, rarityIcon, rollsCount, itemCount byte - var currQuant, currNumber, percentage uint16 - var itemhash uint32 - var itemType, itemId, quantity pq.Int64Array - var items []lottery.Weighter - // get info for updating data and calculating costs - err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount) + bf := byteframe.NewByteFrame() + var gachaEntries []GachaEntry + var entry GachaEntry + var rewards []GachaItem + var reward GachaItem + err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType) if err != nil { - panic(err) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return } - // get existing items in storage if any - var data []byte - _ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data) - if len(data) == 0 { - data = []byte{0x00} - } - // get gacha items and iterate through them for gacha roll - shopEntries, err := s.server.db.Query("SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=100", pkt.GachaHash) + temp := byteframe.NewByteFrame() + entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) if err != nil { - panic(err) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return } - for shopEntries.Next() { - err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity)) + for entries.Next() { + entries.StructScan(&entry) + gachaEntries = append(gachaEntries, entry) + } + rewardEntries, err := getRandomEntries(gachaEntries, rolls, false) + for i := range rewardEntries { + items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID) if err != nil { - panic(err) + continue } - items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity}) - } - // execute rolls, build response and update database - results := byte(0) - resp := byteframe.NewByteFrame() - dbUpdate := byteframe.NewByteFrame() - resp.WriteUint8(0) // results go here later - l := lottery.NewDefaultLottery() - for x := 0; x < int(rollsCount); x++ { - ind := l.Draw(items) - results += items[ind].(*gachaItem).itemCount - for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ { - // items in storage don't get rarity - dbUpdate.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y])) - dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y])) - dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y])) - data = append(data, dbUpdate.Data()...) - dbUpdate.Seek(0, 0) - // response needs all item info and the rarity - resp.WriteBytes(dbUpdate.Data()) - resp.WriteUint8(items[ind].(*gachaItem).rarityIcon) + for items.Next() { + items.StructScan(&reward) + rewards = append(rewards, reward) + temp.WriteUint8(reward.ItemType) + temp.WriteUint16(reward.ItemID) + temp.WriteUint16(reward.Quantity) + temp.WriteUint8(entry.Rarity) } } - resp.Seek(0, 0) - resp.WriteUint8(results) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - - // add claimables to DB - data[0] = data[0] + results - _, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID) - if err != nil { - s.logger.Fatal("Failed to update minidata in db", zap.Error(err)) - } - // deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards - if currType == 19 { - _, err = s.server.db.Exec("UPDATE characters SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end WHERE id=$2", currNumber, s.charID) - } - if err != nil { - s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) - } + bf.WriteUint8(uint8(len(rewards))) + bf.WriteBytes(temp.Data()) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + addGachaItem(s, rewards) } -func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) { - // should write to database when that's set up - pkt := p.(*mhfpacket.MsgMhfUseGachaPoint) - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) +func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha) + bf := byteframe.NewByteFrame() + var gachaEntries []GachaEntry + var entry GachaEntry + var rewards []GachaItem + var reward GachaItem + err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points+(SELECT frontier_points FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.GachaID, pkt.RollType, s.charID) + s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID) + s.server.db.Exec(`INSERT INTO gacha_stepup (gacha_id, step, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, pkt.RollType+1, s.charID) + temp := byteframe.NewByteFrame() + guaranteedItems := getGuaranteedItems(s, pkt.GachaID, pkt.RollType) + for _, item := range guaranteedItems { + temp.WriteUint8(item.ItemType) + temp.WriteUint16(item.ItemID) + temp.WriteUint16(item.Quantity) + temp.WriteUint8(0) + } + entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + for entries.Next() { + entries.StructScan(&entry) + gachaEntries = append(gachaEntries, entry) + } + rewardEntries, err := getRandomEntries(gachaEntries, rolls, false) + for i := range rewardEntries { + items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID) + if err != nil { + continue + } + for items.Next() { + items.StructScan(&reward) + rewards = append(rewards, reward) + temp.WriteUint8(reward.ItemType) + temp.WriteUint16(reward.ItemID) + temp.WriteUint16(reward.Quantity) + temp.WriteUint8(entry.Rarity) + } + } + bf.WriteUint8(uint8(len(rewards) + len(guaranteedItems))) + bf.WriteUint8(uint8(len(rewards))) + bf.WriteBytes(temp.Data()) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + addGachaItem(s, rewards) + addGachaItem(s, guaranteedItems) +} + +func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfGetStepupStatus) + // TODO: Reset daily (noon) + var step uint8 + s.server.db.QueryRow(`SELECT step FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID).Scan(&step) + var stepCheck int + s.server.db.QueryRow(`SELECT COUNT(1) FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, pkt.GachaID, step).Scan(&stepCheck) + if stepCheck == 0 { + s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID) + step = 0 + } + bf := byteframe.NewByteFrame() + bf.WriteUint8(step) + bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) +} + +func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfGetBoxGachaInfo) + entries, err := s.server.db.Queryx(`SELECT entry_id FROM gacha_box WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + var entryIDs []uint32 + for entries.Next() { + var entryID uint32 + entries.Scan(&entryID) + entryIDs = append(entryIDs, entryID) + } + bf := byteframe.NewByteFrame() + bf.WriteUint8(uint8(len(entryIDs))) + for i := range entryIDs { + bf.WriteUint32(entryIDs[i]) + bf.WriteBool(true) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) +} + +func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha) + bf := byteframe.NewByteFrame() + var gachaEntries []GachaEntry + var entry GachaEntry + var rewards []GachaItem + var reward GachaItem + err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + temp := byteframe.NewByteFrame() + entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + for entries.Next() { + entries.StructScan(&entry) + gachaEntries = append(gachaEntries, entry) + } + rewardEntries, err := getRandomEntries(gachaEntries, rolls, true) + for i := range rewardEntries { + items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID) + if err != nil { + continue + } + s.server.db.Exec(`INSERT INTO gacha_box (gacha_id, entry_id, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, rewardEntries[i].ID, s.charID) + for items.Next() { + items.StructScan(&reward) + rewards = append(rewards, reward) + temp.WriteUint8(reward.ItemType) + temp.WriteUint16(reward.ItemID) + temp.WriteUint16(reward.Quantity) + temp.WriteUint8(0) + } + } + bf.WriteUint8(uint8(len(rewards))) + bf.WriteBytes(temp.Data()) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + addGachaItem(s, rewards) +} + +func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfResetBoxGachaInfo) + s.server.db.Exec("DELETE FROM gacha_box WHERE gacha_id = $1 AND character_id = $2", pkt.GachaID, s.charID) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item) var balance uint32 var itemValue, quantity int - _ = s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue) + s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue) cost := (int(pkt.Quantity) * quantity) * itemValue - s.server.db.QueryRow("UPDATE characters SET frontier_points=frontier_points::int - $1 WHERE id=$2 RETURNING frontier_points", cost, s.charID).Scan(&balance) + s.server.db.QueryRow("UPDATE users u SET frontier_points=frontier_points::int - $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance) bf := byteframe.NewByteFrame() bf.WriteUint32(balance) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) @@ -335,7 +611,7 @@ func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) { var itemValue, quantity int s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue) cost := (int(pkt.Quantity) / quantity) * itemValue - s.server.db.QueryRow("UPDATE characters SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE id=$2 RETURNING frontier_points", cost, s.charID).Scan(&balance) + s.server.db.QueryRow("UPDATE users u SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance) bf := byteframe.NewByteFrame() bf.WriteUint32(balance) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) @@ -394,291 +670,6 @@ func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } -func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha) - results := byte(0) - stepResults := byte(0) - resp := byteframe.NewByteFrame() - rollFrame := byteframe.NewByteFrame() - stepFrame := byteframe.NewByteFrame() - stepData := []byte{} - var currType, rarityIcon, rollsCount, itemCount byte - var currQuant, currNumber, percentage uint16 - var itemhash uint32 - var itemType, itemId, quantity pq.Int64Array - var items []lottery.Weighter - // get info for updating data and calculating costs - err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity)) - if err != nil { - panic(err) - } - // get existing items in storage if any - var data []byte - _ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data) - if len(data) == 0 { - data = []byte{0x00} - } - // roll definition includes items with step up gachas that are appended last - for x := 0; x < int(itemCount); x++ { - stepFrame.WriteUint8(uint8(itemType[x])) - stepFrame.WriteUint16(uint16(itemId[x])) - stepFrame.WriteUint16(uint16(quantity[x])) - stepData = append(stepData, stepFrame.Data()...) - stepFrame.WriteUint8(0) // rarity still defined - stepResults++ - } - // get gacha items and iterate through them for gacha roll - shopEntries, err := s.server.db.Query("SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=100", pkt.GachaHash) - if err != nil { - panic(err) - } - for shopEntries.Next() { - err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity)) - if err != nil { - panic(err) - } - items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity}) - } - // execute rolls, build response and update database - resp.WriteUint16(0) // results count goes here later - l := lottery.NewDefaultLottery() - for x := 0; x < int(rollsCount); x++ { - ind := l.Draw(items) - results += items[ind].(*gachaItem).itemCount - for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ { - // items in storage don't get rarity - rollFrame.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y])) - rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y])) - rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y])) - data = append(data, rollFrame.Data()...) - rollFrame.Seek(0, 0) - // response needs all item info and the rarity - resp.WriteBytes(rollFrame.Data()) - resp.WriteUint8(items[ind].(*gachaItem).rarityIcon) - } - } - resp.WriteBytes(stepFrame.Data()) - resp.Seek(0, 0) - resp.WriteUint8(results + stepResults) - resp.WriteUint8(results) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - - // add claimables to DB - data = append(data, stepData...) - data[0] = data[0] + results + stepResults - _, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID) - if err != nil { - s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) - } - // deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards - // reduce real if trial don't cover cost - if currType == 19 { - _, err = s.server.db.Exec(`UPDATE characters - SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, - gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end - WHERE id=$2`, currNumber, s.charID) - } - if err != nil { - s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) - } - // update step progression - _, err = s.server.db.Exec("UPDATE stepup_state SET step_progression = $1 WHERE char_id = $2", pkt.RollType+1, s.charID) - if err != nil { - s.logger.Fatal("Failed to update step_progression in db", zap.Error(err)) - } - -} - -func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem) - // persistent for claimable items on cat - var data []byte - err := s.server.db.QueryRow("SELECT COALESCE(gacha_items, $2) FROM characters WHERE id = $1", s.charID, []byte{0x00}).Scan(&data) - if err != nil { - panic("Failed to get gacha_items") - } - // limit of 36 items are returned - if data[0] > 36 { - outData := make([]byte, 181) - copy(outData, data[0:181]) - outData[0] = byte(36) - saveData := append(data[:1], data[181:]...) - saveData[0] = saveData[0] - 36 - doAckBufSucceed(s, pkt.AckHandle, outData) - if pkt.Unk0 != 0x2401 { - _, err := s.server.db.Exec("UPDATE characters SET gacha_items = $2 WHERE id = $1", s.charID, saveData) - if err != nil { - s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) - } - } - } else { - doAckBufSucceed(s, pkt.AckHandle, data) - if pkt.Unk0 != 0x2401 { - _, err := s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID) - if err != nil { - s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) - } - } - } -} - -func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfGetStepupStatus) - // get the reset time from db - var step_progression int - var step_time time.Time - err := s.server.db.QueryRow(`SELECT COALESCE(step_progression, 0), COALESCE(step_time, $1) FROM stepup_state WHERE char_id = $2 AND shophash = $3`, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), s.charID, pkt.GachaHash).Scan(&step_progression, &step_time) - if err != nil { - s.logger.Fatal("Failed to Select coalesce in db", zap.Error(err)) - } - - // calculate next midday - var t = time.Now().In(time.FixedZone("UTC+9", 9*60*60)) - year, month, day := t.Date() - midday := time.Date(year, month, day, 12, 0, 0, 0, t.Location()) - if t.After(midday) { - midday = midday.Add(24 * time.Hour) - } - // after midday or not set - if t.After(step_time) { - step_progression = 0 - } - _, err = s.server.db.Exec(`INSERT INTO stepup_state (shophash, step_progression, step_time, char_id) - VALUES ($1,$2,$3,$4) ON CONFLICT (shophash, char_id) - DO UPDATE SET step_progression=$2, step_time=$3 - WHERE EXCLUDED.char_id=$4 AND EXCLUDED.shophash=$1`, pkt.GachaHash, step_progression, midday, s.charID) - if err != nil { - s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err)) - } - resp := byteframe.NewByteFrame() - resp.WriteUint8(uint8(step_progression)) - resp.WriteUint32(uint32(time.Now().In(time.FixedZone("UTC+9", 9*60*60)).Unix())) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) -} - func handleMsgMhfPlayFreeGacha(s *Session, p mhfpacket.MHFPacket) { // not sure this is used anywhere, free gachas use the MSG_MHF_PLAY_NORMAL_GACHA method in captures } - -func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfGetBoxGachaInfo) - count := 0 - var used_itemhash pq.Int64Array - // pull array of used values - // single sized respone with 0x00 is a valid with no items present - _ = s.server.db.QueryRow("SELECT used_itemhash FROM lucky_box_state WHERE shophash=$1 AND char_id=$2", pkt.GachaHash, s.charID).Scan((*pq.Int64Array)(&used_itemhash)) - resp := byteframe.NewByteFrame() - resp.WriteUint8(0) - for ind := range used_itemhash { - resp.WriteUint32(uint32(used_itemhash[ind])) - resp.WriteUint8(1) - count++ - } - resp.Seek(0, 0) - resp.WriteUint8(uint8(count)) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) -} - -func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha) - // needs to query db for input gacha and return a result or number of results - // uint8 number of results - // uint8 item type - // uint16 item id - // uint16 quantity - - var currType, rarityIcon, rollsCount, itemCount byte - var currQuant, currNumber, percentage uint16 - var itemhash uint32 - var itemType, itemId, quantity, usedItemHash pq.Int64Array - var items []lottery.Weighter - // get info for updating data and calculating costs - err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount) - if err != nil { - panic(err) - } - // get existing items in storage if any - var data []byte - _ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data) - if len(data) == 0 { - data = []byte{0x00} - } - // get gacha items and iterate through them for gacha roll - shopEntries, err := s.server.db.Query(`SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity - FROM gacha_shop_items - WHERE shophash=$1 AND entryType=100 - EXCEPT ALL SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity - FROM gacha_shop_items gsi JOIN lucky_box_state lbs ON gsi.itemhash = ANY(lbs.used_itemhash) - WHERE lbs.char_id=$2`, pkt.GachaHash, s.charID) - if err != nil { - panic(err) - } - for shopEntries.Next() { - err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity)) - if err != nil { - panic(err) - } - items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity}) - } - // execute rolls, build response and update database - results := byte(0) - resp := byteframe.NewByteFrame() - dbUpdate := byteframe.NewByteFrame() - resp.WriteUint8(0) // results go here later - l := lottery.NewDefaultLottery() - for x := 0; x < int(rollsCount); x++ { - ind := l.Draw(items) - results += items[ind].(*gachaItem).itemCount - for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ { - // items in storage don't get rarity - dbUpdate.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y])) - dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y])) - dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y])) - data = append(data, dbUpdate.Data()...) - dbUpdate.Seek(0, 0) - // response needs all item info and the rarity - resp.WriteBytes(dbUpdate.Data()) - resp.WriteUint8(items[ind].(*gachaItem).rarityIcon) - - usedItemHash = append(usedItemHash, int64(items[ind].(*gachaItem).itemhash)) - } - // remove rolled - items = append(items[:ind], items[ind+1:]...) - } - resp.Seek(0, 0) - resp.WriteUint8(results) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - - // add claimables to DB - data[0] = data[0] + results - _, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID) - if err != nil { - s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) - } - // update lucky_box_state - _, err = s.server.db.Exec(`INSERT INTO lucky_box_state (char_id, shophash, used_itemhash) - VALUES ($1,$2,$3) ON CONFLICT (char_id, shophash) - DO UPDATE SET used_itemhash = COALESCE(lucky_box_state.used_itemhash::int[] || $3::int[], $3::int[]) - WHERE EXCLUDED.char_id=$1 AND EXCLUDED.shophash=$2`, s.charID, pkt.GachaHash, usedItemHash) - if err != nil { - s.logger.Fatal("Failed to update lucky box state in db", zap.Error(err)) - } - // deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards - if currType == 19 { - _, err = s.server.db.Exec(`UPDATE characters - SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end - WHERE id=$2`, currNumber, s.charID) - } - if err != nil { - s.logger.Fatal("Failed to update gacha_trial in db", zap.Error(err)) - } -} - -func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfResetBoxGachaInfo) - _, err := s.server.db.Exec("DELETE FROM lucky_box_state WHERE shophash=$1 AND char_id=$2", pkt.GachaHash, s.charID) - if err != nil { - panic(err) - } - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) -} diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 90ecdec99..bb5f41ff7 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -158,7 +158,9 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { if s.stageID == "" { s.stageMoveStack.Set(pkt.StageID) } else { + s.stage.Lock() s.stage.reservedClientSlots[s.charID] = false + s.stage.Unlock() s.stageMoveStack.Push(s.stageID) s.stageMoveStack.Lock() } diff --git a/server/channelserver/handlers_tournament.go b/server/channelserver/handlers_tournament.go index 4511cfedc..11b8645c3 100644 --- a/server/channelserver/handlers_tournament.go +++ b/server/channelserver/handlers_tournament.go @@ -1,8 +1,59 @@ package channelserver -import "erupe-ce/network/mhfpacket" +import ( + "erupe-ce/common/byteframe" + ps "erupe-ce/common/pascalstring" + "erupe-ce/network/mhfpacket" + "time" +) -func handleMsgMhfInfoTournament(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgMhfInfoTournament(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfInfoTournament) + bf := byteframe.NewByteFrame() + + switch pkt.Unk0 { + case 0: + bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) + bf.WriteUint32(0) // Tied to schedule ID? + case 1: + + bf.WriteBytes(make([]byte, 21)) + ps.Uint8(bf, "", false) + break + + bf.WriteUint32(0xACEDCAFE) + + bf.WriteUint32(5) // Active schedule? + + bf.WriteUint32(1) // Schedule ID? + + bf.WriteUint32(32) // Max players + bf.WriteUint32(0) // Registered players + + bf.WriteUint16(0) + bf.WriteUint16(2) // Color code for schedule item + bf.WriteUint32(0) + + bf.WriteUint32(uint32(time.Now().Add(time.Hour * -10).Unix())) + bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) + bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) + bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) + bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) + bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) + + bf.WriteBool(true) // Unk + bf.WriteBool(false) // Cafe-only + + bf.WriteUint32(0) // Min HR + bf.WriteUint32(0) // Max HR + + ps.Uint8(bf, "Test", false) + + // ... + } + + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) +} func handleMsgMhfEntryTournament(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/handlers_tower.go b/server/channelserver/handlers_tower.go index 0bafffaa2..0a2675812 100644 --- a/server/channelserver/handlers_tower.go +++ b/server/channelserver/handlers_tower.go @@ -58,6 +58,42 @@ func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } +func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) { + // if the game gets bad responses for this it breaks the ability to save + pkt := p.(*mhfpacket.MsgMhfGetTenrouirai) + var data []byte + var err error + if pkt.Unk0 == 1 { + data, err = hex.DecodeString("0A218EAD000000000000000000000001010000000000060010") + } else if pkt.Unk2 == 4 { + data, err = hex.DecodeString("0A218EAD0000000000000000000000210101005000000202010102020104001000000202010102020106003200000202010002020104000C003202020101020201030032000002020101020202059C4000000202010002020105C35000320202010102020201003C00000202010102020203003200000201010001020203002800320201010101020204000C00000201010101020206002800000201010001020101003C00320201020101020105C35000000301020101020106003200000301020001020104001000320301020101020105C350000003010201010202030028000003010200010201030032003203010201010202059C4000000301020101010206002800000301020001010201003C00320301020101010206003200000301020101010204000C000003010200010101010050003203010201010101059C40000003010201010101030032000003010200010101040010003203010001010101060032000003010001010102030028000003010001010101010050003203010000010102059C4000000301000001010206002800000301000001010010") + } else { + data = []byte{0x00, 0x00, 0x00, 0x00} + s.logger.Info("GET_TENROUIRAI request for unknown type") + } + if err != nil { + panic(err) + } + doAckBufSucceed(s, pkt.AckHandle, data) +} + +func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfPostTenrouirai) + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) +} + +func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {} + +func handleMsgMhfGetWeeklySeibatuRankingReward(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfGetWeeklySeibatuRankingReward) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) +} + +func handleMsgMhfPresentBox(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfPresentBox) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) +} + func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetGemInfo) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 7c7447dfa..e388149eb 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -92,8 +92,7 @@ type RavienteRegister struct { } type RavienteState struct { - damageMultiplier uint32 - stateData []uint32 + stateData []uint32 } type RavienteSupport struct { @@ -111,9 +110,7 @@ func NewRaviente() *Raviente { maxPlayers: 0, carveQuest: 0, } - ravienteState := &RavienteState{ - damageMultiplier: 1, - } + ravienteState := &RavienteState{} ravienteSupport := &RavienteSupport{} ravienteRegister.register = []uint32{0, 0, 0, 0, 0} ravienteState.stateData = []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -127,6 +124,23 @@ func NewRaviente() *Raviente { return raviente } +func (r *Raviente) GetRaviMultiplier(s *Server) uint32 { + raviSema := getRaviSemaphore(s) + if raviSema != nil { + var minPlayers uint32 + if r.register.maxPlayers > 8 { + minPlayers = 24 + } else { + minPlayers = 4 + } + if uint32(len(raviSema.clients)) > minPlayers { + return 1 + } + return minPlayers / uint32(len(raviSema.clients)) + } + return 0 +} + // NewServer creates a new Server type. func NewServer(config *Config) *Server { s := &Server{ diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index 1dc917559..b7bf57b4f 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -6,6 +6,32 @@ func getLangStrings(s *Server) map[string]string { case "jp": strings["language"] = "日本語" strings["cafeReset"] = "%d/%dにリセット" + + strings["commandDisabled"] = "%sのコマンドは無効です" + strings["commandReload"] = "リロードします" + strings["commandKqfGet"] = "現在のキークエストフラグ:%x" + strings["commandKqfSetError"] = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx" + strings["commandKqfSetSuccess"] = "キークエストのフラグが更新されました。ワールド/ランドを移動してください" + strings["commandRightsError"] = "コース更新コマンドエラー 例:%s x" + strings["commandRightsSuccess"] = "コース情報を更新しました:%d" + strings["commandCourseError"] = "コース確認コマンドエラー 例:%s " + strings["commandCourseDisabled"] = "%sコースは無効です" + strings["commandCourseEnabled"] = "%sコースは有効です" + strings["commandCourseLocked"] = "%sコースはロックされています" + strings["commandTeleportError"] = "テレポートコマンドエラー 構文:%s x y" + strings["commandTeleportSuccess"] = "%d %dにテレポート" + + strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません" + strings["commandRaviStartSuccess"] = "大討伐を開始します" + strings["commandRaviStartError"] = "大討伐は既に開催されています" + strings["commandRaviMultiplier"] = "ラヴィダメージ倍率:x%d" + strings["commandRaviResSuccess"] = "復活支援を実行します" + strings["commandRaviResError"] = "復活支援は実行されませんでした" + strings["commandRaviSedSuccess"] = "鎮静支援を実行します" + strings["commandRaviRequest"] = "鎮静支援を要請します" + strings["commandRaviError"] = "ラヴィコマンドが認識されません" + strings["commandRaviNoPlayers"] = "誰も大討伐に参加していません" + strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!" strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!" strings["ravienteExtremeLimited"] = "<大討伐:猛狂期【極】(制限付)>が開催されました!" @@ -28,6 +54,32 @@ func getLangStrings(s *Server) map[string]string { default: strings["language"] = "English" strings["cafeReset"] = "Resets on %d/%d" + + strings["commandDisabled"] = "%s command is disabled" + strings["commandReload"] = "Reloading players..." + strings["commandKqfGet"] = "KQF: %x" + strings["commandKqfSetError"] = "Error in command. Format: %s set xxxxxxxxxxxxxxxx" + strings["commandKqfSetSuccess"] = "KQF set, please switch Land/World" + strings["commandRightsError"] = "Error in command. Format: %s x" + strings["commandRightsSuccess"] = "Set rights integer: %d" + strings["commandCourseError"] = "Error in command. Format: %s " + strings["commandCourseDisabled"] = "%s Course disabled" + strings["commandCourseEnabled"] = "%s Course enabled" + strings["commandCourseLocked"] = "%s Course is locked" + strings["commandTeleportError"] = "Error in command. Format: %s x y" + strings["commandTeleportSuccess"] = "Teleporting to %d %d" + + strings["commandRaviNoCommand"] = "No Raviente command specified!" + strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment" + strings["commandRaviStartError"] = "The Great Slaying has already begun!" + strings["commandRaviMultiplier"] = "Raviente multiplier is currently %dx" + strings["commandRaviResSuccess"] = "Sending resurrection support!" + strings["commandRaviResError"] = "Resurrection support has not been requested!" + strings["commandRaviSedSuccess"] = "Sending sedation support if requested!" + strings["commandRaviRequest"] = "Requesting sedation support!" + strings["commandRaviError"] = "Raviente command not recognised!" + strings["commandRaviNoPlayers"] = "No one has joined the Great Slaying!" + strings["ravienteBerserk"] = " is being held!" strings["ravienteExtreme"] = " is being held!" strings["ravienteExtremeLimited"] = " is being held!" diff --git a/server/launcherserver/handler.go b/server/launcherserver/handler.go deleted file mode 100644 index d1cad4f3c..000000000 --- a/server/launcherserver/handler.go +++ /dev/null @@ -1,20 +0,0 @@ -package launcherserver - -import ( - "net/http" -) - -// ServerHandler is a handler function akin to http.Handler's ServeHTTP, -// but has an additional *Server argument. -type ServerHandler func(*Server, http.ResponseWriter, *http.Request) - -// ServerHandlerFunc is a small type that implements http.Handler and -// wraps a calling ServerHandler with a *Server argument. -type ServerHandlerFunc struct { - server *Server - f ServerHandler -} - -func (shf ServerHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { - shf.f(shf.server, w, r) -} diff --git a/server/launcherserver/launcher_server.go b/server/launcherserver/launcher_server.go deleted file mode 100644 index 100ca43c7..000000000 --- a/server/launcherserver/launcher_server.go +++ /dev/null @@ -1,98 +0,0 @@ -package launcherserver - -import ( - "context" - "fmt" - "net/http" - "os" - "sync" - "time" - - "erupe-ce/config" - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/jmoiron/sqlx" - "go.uber.org/zap" -) - -// Config struct allows configuring the server. -type Config struct { - Logger *zap.Logger - DB *sqlx.DB - ErupeConfig *config.Config - UseOriginalLauncherFiles bool -} - -// Server is the MHF launcher HTTP server. -type Server struct { - sync.Mutex - logger *zap.Logger - erupeConfig *config.Config - db *sqlx.DB - httpServer *http.Server - useOriginalLauncherFiles bool - isShuttingDown bool -} - -// NewServer creates a new Server type. -func NewServer(config *Config) *Server { - s := &Server{ - logger: config.Logger, - erupeConfig: config.ErupeConfig, - db: config.DB, - useOriginalLauncherFiles: config.UseOriginalLauncherFiles, - httpServer: &http.Server{}, - } - return s -} - -// Start starts the server in a new goroutine. -func (s *Server) Start() error { - // Set up the routes responsible for serving the launcher HTML, serverlist, unique name check, and JP auth. - r := mux.NewRouter() - - // Universal serverlist.xml route - s.setupServerlistRoutes(r) - - // Change the launcher HTML routes if we are using the custom launcher instead of the original. - if s.useOriginalLauncherFiles { - s.setupOriginalLauncherRotues(r) - } else { - s.setupCustomLauncherRotues(r) - } - - s.httpServer.Addr = fmt.Sprintf(":%d", s.erupeConfig.Launcher.Port) - s.httpServer.Handler = handlers.LoggingHandler(os.Stdout, r) - - serveError := make(chan error, 1) - go func() { - if err := s.httpServer.ListenAndServe(); err != nil { - // Send error if any. - serveError <- err - } - }() - - // Get the error from calling ListenAndServe, otherwise assume it's good after 250 milliseconds. - select { - case err := <-serveError: - return err - case <-time.After(250 * time.Millisecond): - return nil - } -} - -// Shutdown exits the server gracefully. -func (s *Server) Shutdown() { - s.logger.Debug("Shutting down") - - s.Lock() - s.isShuttingDown = true - s.Unlock() - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := s.httpServer.Shutdown(ctx); err != nil { - // Just warn because we are shutting down the server anyway. - s.logger.Warn("Got error on httpServer shutdown", zap.Error(err)) - } -} diff --git a/server/launcherserver/routes.go b/server/launcherserver/routes.go deleted file mode 100644 index 59c47bba7..000000000 --- a/server/launcherserver/routes.go +++ /dev/null @@ -1,79 +0,0 @@ -package launcherserver - -import ( - "fmt" - "html" - "net/http" - - "github.com/gorilla/mux" - //"github.com/julienschmidt/httprouter" -) - -func serverList(s *Server, w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, - ``, - s.erupeConfig.Host, - s.erupeConfig.Sign.Port, - ) -} - -func serverUniqueName(w http.ResponseWriter, r *http.Request) { - // TODO(Andoryuuta): Implement checking for unique character name. - fmt.Fprintf(w, `OK`) -} - -func jpLogin(w http.ResponseWriter, r *http.Request) { - // HACK(Andoryuuta): Return the given password back as the `skey` to defer the login logic to the sign server. - resultJSON := fmt.Sprintf(`{"result": "Ok", "skey": "%s", "code": "000", "msg": ""}`, r.FormValue("pw")) - - fmt.Fprintf(w, - ` - - - - - - `, html.EscapeString(resultJSON)) - -} - -func (s *Server) setupServerlistRoutes(r *mux.Router) { - // TW - twServerList := r.Host("mhf-n.capcom.com.tw").Subrouter() - twServerList.HandleFunc("/server/unique.php", serverUniqueName) // Name checking is also done on this host. - twServerList.Handle("/server/serverlist.xml", ServerHandlerFunc{s, serverList}) - - // JP - jpServerList := r.Host("srv-mhf.capcom-networks.jp").Subrouter() - jpServerList.Handle("/serverlist.xml", ServerHandlerFunc{s, serverList}) -} - -func (s *Server) setupOriginalLauncherRotues(r *mux.Router) { - // TW - twMain := r.Host("mhfg.capcom.com.tw").Subrouter() - twMain.PathPrefix("/").Handler(http.FileServer(http.Dir("./www/tw/"))) - - // JP - jpMain := r.Host("cog-members.mhf-z.jp").Subrouter() - jpMain.PathPrefix("/").Handler(http.FileServer(http.Dir("./www/jp/"))) - - // JP Launcher does additional auth over HTTP that the TW launcher doesn't. - jpAuth := r.Host("www.capcom-onlinegames.jp").Subrouter() - jpAuth.HandleFunc("/auth/launcher/login", jpLogin) //.Methods("POST") - jpAuth.PathPrefix("/auth/").Handler(http.StripPrefix("/auth/", http.FileServer(http.Dir("./www/jp/auth/")))) - -} - -func (s *Server) setupCustomLauncherRotues(r *mux.Router) { - // TW - twMain := r.Host("mhfg.capcom.com.tw").Subrouter() - twMain.PathPrefix("/g6_launcher/").Handler(http.StripPrefix("/g6_launcher/", http.FileServer(http.Dir("./www/erupe/")))) - - // JP - jpMain := r.Host("cog-members.mhf-z.jp").Subrouter() - jpMain.PathPrefix("/launcher/").Handler(http.StripPrefix("/launcher/", http.FileServer(http.Dir("./www/erupe")))) -} diff --git a/www/erupe/css/main.css b/www/erupe/css/main.css deleted file mode 100644 index d7bd661dd..000000000 --- a/www/erupe/css/main.css +++ /dev/null @@ -1,198 +0,0 @@ -*{margin:0; padding:0; font-family:sans-serif!important;} -*[unselectable="on"]{cursor:default; user-select: none; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none;} -a{border:none; outline:none;} -ul{list-style:none;} -img{border-style:none;} -html,body{width:100%; height:100%; overflow:hidden;} -body{background:#092314; font-family:sans-serif; color:#d1c0a5; font-size:14px; line-height:16px;} -.grabbable{position:absolute; left:0; top:0; width:100%; height:100%;} -#main{width:1124px; height:600px; overflow:hidden; position:absolute; left:0; top:0; background-image:url(../img/background.png); background-repeat:no-repeat;} - -#game_starting{display:none; position:absolute; left:0; top:0; width:100%; height:100%; background-color:rgba(0,0,0,0.5); background-image:url(../img/start_now.png); background-repeat:no-repeat; background-position:center; z-index:2000;} - -#window_controls{width:106px; height:20px; position:absolute; right:10px; top:0; z-index:1000;} -#window_controls div{width:50px; height:20px; position:absolute; top:0; background-color:rgba(0,0,0,0.5); border-radius:0px 0px 5px 5px; cursor:pointer; text-align:center;} -#window_controls div:hover{background-color:rgba(150,150,150,0.3)} -#window_controls .button_min{left:0;} -#window_controls .button_close{right:0;} - -#header{width:525px; height:114px; position:absolute; left:0; top:11px; background:no-repeat 0px 27px;} -#header .background{position:absolute; left:0px; top:24px; height:70px; background-color:rgba(0,0,0,0.6); width:550px; border-radius:1px 5px 5px 1px} -#header img{margin-left:17px; position:relative;} -#header p{color:#dcdcdc; position:absolute; left:330px; top:72px;} -#header p.title1 {font-weight:bold; font-size:15px; position: absolute; top:30px} -#header p.title2 {font-weight:bold; font-size:15px; position: absolute; top:48px} - -#button_config{width:284px; height:33px; position:absolute; left:137px; bottom:48px; cursor:pointer; background:no-repeat 0px 0px; z-index:1000; background-image:url(../img/button_config.png);} -#button_config:hover{background-image:url(../img/button_config_hover.png);} - -#log_outer{width:410px; height:86px; padding:6px 8px; position:absolute; left:65px; bottom:90px; background-color:rgba(0,0,0,0.8); border-radius:3px 3px 3px 3px; border: 1px solid #a6a08a;} -#log_outer .log_inner{display:block; width:410px; height:86px; font-size:12px; line-height:14px; color:#dcdcdc; overflow:hidden; position:absolute;} -#log_outer .log_inner p{width:400px;} -#log_outer .log_inner span.winsock{color:#d9ff07;} -#log_outer .log_inner span.white{color:#ffffff;} -#log_outer .log_inner span.green{color:#28a745;} -#log_outer .log_inner span.red{color:#bd2130;} - -#login{width:300px; height:265px; position:absolute; left:129px; top:140px;} -#login .login_button{position:absolute; left:-3px; top:167px; width:300px; height:64px; background-image:url(../img/button_launch.png); cursor:pointer; display:flex; justify-content:center; align-items:center;} -#login .login_button:hover{background-image:url(../img/button_launch_hover.png);} -#login .login_button span{font-size:24px; font-weight:bold; left:113px; top:23px; color:white; cursor:pointer;} - -#login .login_save{width:135px; height:12px; position:absolute; left:81px; top:236px; cursor:pointer; color:white;} - -#login .login_form{width:254px; height:146px; position:absolute; left:19px; top:0;} -#login .login_form *{color:#fff; font-size:12px; outline:none; border:none; background:none;} -#login .login_form input{position:absolute; width:244px; background-color:rgba(0,0,0,0.6); padding:6px; border:1px solid #a6a08a; border-radius:2px 2px 2px 2px;} -#login .login_form input#username{top:24px;} -#login .login_form input#password{top:74px;} -#login .login_form input#server{top:124px;} -#login .login_form input[type=submit]{width:0; height:0; padding:0; border:0;} -#login .login_form label{position:absolute; font-size:16px;} -#login .login_form label.label_username{top:8px;} -#login .login_form label.label_password{top:58px;} -#login .login_form label.label_server{top:108px;} - -#login #processing{display:none; position:absolute; left:-7px; top:-3px; width:307px; height:271px; background-color:rgba(0,0,0,0.8); background-image:url(../img/load.gif); background-position:center; background-size:50px 50px; background-repeat:no-repeat; border-radius:2px 2px 2px 2px; z-index:1;} -#login #processing p{color:#fff; text-align:center; width:100%; position:absolute; left:0; top:160px; z-index:2;} - -#char_select{width:490px; height:290px; position:absolute; left:35px; top:115px; color:#313131; display:none;} -#char_select #units{width:398px; height:100px; position:absolute; left:45px; top:50px; background-image:url(../img/button_charsel_char.jpg);} - -#char_select #units .unit{display:none; width:398px; height:100px; position:absolute; left:0; cursor:default;} -#char_select #units .unit.active{display:block;} - -#char_select .scroll{width:91px; height:25px; position:absolute; left:198px; background-image:url(../img/button_charsel.png); cursor:pointer; z-index:1000;} -#char_select .scroll.up{top:14px;} -#char_select .scroll.down{top:159px; transform:scaleY(-1);} -#char_select .scroll:hover{background-image:url(../img/button_charsel_hover.png);} - -#char_select #units .unit *{color:white; font-size:16px; position:absolute;} -#char_select #units .unit img{top:42px; left:18px;} -#char_select #units .unit #char_name{top:15px; font-size:30px; width:100%; text-align:center;} -#char_select #units .unit #char_weapon{top:70px; left:72px; font-weight:bold; width:120px; text-align:center;} -#char_select #units .unit #char_hr{top:43px; left:210px;} -#char_select #units .unit #char_gr{top:43px; left:280px;} -#char_select #units .unit #char_sex{top:43px; left:350px;} -#char_select #units .unit #char_uid{top:58px; left:210px;} -#char_select #units .unit #char_login{top:73px; left:210px;} - -#char_select .weapon_text{position:absolute; font-weight:bold; width:120px; top:98px; left:117px; color:white; text-align:center;} - -#char_select .auto_login{position:absolute; top:155px; left:50px; color:white; cursor:pointer;} - -#char_select .button_add, -#char_select .button_del, -#char_select .button_log{width:130px; height:33px; position:absolute; top:195px; background-image:url(../img/button_option.png); cursor:pointer; z-index:1000; display:flex; justify-content:center; align-items:center;} - -#char_select .button_add{left:40px;} -#char_select .button_del{left:179px;} -#char_select .button_log{left:318px;} - -#char_select .button_add span, -#char_select .button_del span, -#char_select .button_log span{font-size: 14px; color:white; cursor:pointer;} - -#char_select .button_add:hover, -#char_select .button_del:hover, -#char_select .button_log:hover{background-image:url(../img/button_option_hover.png);} - -#char_select .button_start{width:300px; height:64px; position:absolute; left:90px; top:229px; background-image:url(../img/button_launch.png); cursor:pointer; z-index:1000; display:flex; justify-content:center; align-items:center;} -#char_select .button_start:hover{background-image:url(../img/button_launch_hover.png);} -#char_select .button_start span{font-size:24px; font-weight:bold; color:white; cursor:pointer;} - -#dev{display:none; width:100%; z-index: 10000; position:absolute; bottom:0px;} -#dev input{width:100%; height:20px; font-size: 16px;} - -#footer{width:100%; height:39px; position:absolute; left:0; bottom:0; background-color:rgba(0,0,0,0.6); overflow:hidden;} -#footer .link{padding:4px; display:block; z-index:1000; cursor:pointer;float:left;} - -#launcher_info_list{width:535px; height:240px; position:absolute; right:38px; padding:5px; top:135px; overflow:hidden; overflow-y:auto; background-color:rgba(0,0,0,0.5); border-radius:3px 3px 3px 3px; border: 1px solid #a6a08a;} -#launcher_info_list .important_info, #launcher_info_list .normal_info{margin-bottom:5px;} -#launcher_info_list .head{width:513px; height:23px; position:relative;} -#launcher_info_list .head .lbl{position:absolute; left:0; top:0; width:513px; height:23px;} -#launcher_info_list .head a{position:absolute; right:10px; top:6px; display:block; width:35px; height:13px; background:no-repeat 0px 0px;} -#launcher_info_list .head a:hover{background-position:0px -13px;} -#launcher_info_list ul.article{margin-left:18px; width:517px; position:relative;} -#launcher_info_list ul.article li{width:517px; position:relative; overflow:hidden; padding:5px 0;} -#launcher_info_list ul.article li div{position:relative; float:left;} -#launcher_info_list ul.article li .date{width:110px;} -#launcher_info_list ul.article li .body{width:340px;} -#launcher_info_list ul.article li .body a{color:#d1c0a5; text-decoration:none;} -#launcher_info_list ul.article li .body a:hover{color:#EFDDC2;} -#launcher_info_list .important_info ul.article li .body a, #launcher_info_list .important_info ul.article li .date{color:#f4c833;} -#launcher_info_list .important_info ul.article li .body a:hover{color:#F8DD81;} -#launcher_info_list ul.article li .icon{width:30px; height:14px; background:no-repeat center center;} -#launcher_info_list a{cursor:pointer;} - -#launcher_modal{width:100%; height:100%; position:fixed; left:0; top:0;z-index:1000; display:none; } -#launcher_modal .modal{width:100%; height:100%; position:absolute; left:0; top:0;} -#launcher_modal .dialog{position:absolute; left:282px; top:140px; width:560px; height:320px; background:url(../img/modelbg.jpeg) round;} -#launcher_modal .dialog p{color:#d1c0a5; font-size:18px; line-height:36px; width:100%; position:relative; text-align:center; padding-top:60px;} -#launcher_modal .dialog p.alert{line-height:26px; padding-top:90px; font-size:20px;} -#launcher_modal .dialog p span.uid{font-size:12px;} -#launcher_modal .dialog p span.attention{color:#f4de22;} -#launcher_modal .dialog p span.notes{color:#eb3535; font-size:16px; line-height:20px;} -#launcher_modal .dialog p div.sp{width:100%; height:10px;} -#launcher_modal .dialog .btn_box{width:100%; height:44px; position:absolute; left:0; top:252px;} -#launcher_modal .dialog .btns{text-align:center; margin:0 auto; position:relative; overflow:hidden;} -#launcher_modal .dialog .btns ul{position:relative; left:50%; float:left; list-style:none} -#launcher_modal .dialog .btns ul li{position:relative; left:-50%; float:left; margin-left:33px;} -#launcher_modal .dialog .btns ul li:first-child{margin-left:5px;} -#launcher_modal .dialog .btns ul li div{display:block; width:130px; height:35px; padding-top:9px; position:relative; background:url(../img/button_option.png) no-repeat 0px 0px; text-decoration:none; color:white; font-weight:bold; font-size:18px; line-height:18px; text-align:center; cursor:pointer;} -#launcher_modal .dialog .btns ul li div:hover, -#launcher_modal .dialog .btns ul li div.hover{background:url(../img/button_option_hover.png) no-repeat 0px 0px;} - -#launcher_menu{height:86px; position:absolute; left:535px; top:416px; overflow:hidden;} -#launcher_menu .btn{width:82px; height:86px; background:no-repeat 0px 0px; float:left; margin:0 2px; cursor:pointer;} -#launcher_menu .btn:hover{background-position:0px -86px;} -#launcher_menu .btn.manual{background-image:url(../img/manual.png);} -#launcher_menu .btn.pastebin{background-image:url(../img/pastebin.png);} - -/* UNUSED -#launcher_bnr{width:533px; height:129px; position:absolute; right:38px; top:30px;} -#launcher_bnr .bnr{width:523px; height:129px; position:absolute; left:0; top:0; background:no-repeat 0px 0px; overflow:hidden;} -#launcher_bnr .bnr img{margin-left:1px;} -#launcher_bnr .bnr .frame{width:523px; height:129px; position:absolute; left:0; top:0; background:no-repeat 0px 0px; cursor:pointer;} -#launcher_bnr .dots{width:10px; position:absolute; right:0; top:0; max-height:129px;} -#launcher_bnr .dots ul{position:relative; width:10px; padding-top:6px;} -#launcher_bnr .dots li{display:block; width:10px; height:10px; padding:6px 0; position:relative;} -#launcher_bnr .dots li .dot{width:10px; height:10px; background:no-repeat 0px 0px; cursor:pointer;} -#launcher_bnr .dots li .dot.crr, -#launcher_bnr .dots li .dot:hover{background-position:0px -10px;} -#launcher_bnr .dots li .dot img{display:none;} - -#launcher_info_detail{width:532px; height:423px; position:absolute; right:38px; top:30px; background:no-repeat 0px 0px;} -#launcher_info_detail .article_frame{width:532px; height:423px; position:absolute; left:0; top:0; overflow:hidden; overflow-y:auto;} -#launcher_info_detail .article_frame .article{width:498px; min-height:394px; position:relative; margin:10px 0 10px 13px; padding-top:9px; background-color:#fef5e6; font-family:"MS Pゴシック", "MS PGothic", sans-serif; font-size:14px; line-height:18px; color:#333333;} -#launcher_info_detail .article_frame .article .article_category{width:498px; height:24px; background:no-repeat 0px 0px; margin-bottom:9px;} -#launcher_info_detail .article_frame .article .newstitle_area{width:468px; background-color:#e8d9b9; padding:4px 0 2px; margin:0 auto 10px;} -#launcher_info_detail .article_frame .article .newstext_area{width:468px; margin:0 auto;} -#launcher_info_detail .btn_back{cursor:pointer; width:160px; height:18px; position:absolute; left:184px; bottom:6px; background:no-repeat 0px 0px;} -#launcher_info_detail .btn_back:hover{background-position:0px -18px;} -#launcher_info_detail a{cursor:pointer;} - -#launcher_footer{width:100%; height:39px; position:absolute; left:0; bottom:0; overflow:hidden;} -#launcher_footer .btn{display:block; position:absolute; z-index:1000; cursor:pointer;} -#launcher_footer .btn.capcom{width:101px; height:26px; background:no-repeat 0px 0px; left:31px; top:6px;} -#launcher_footer .btn.cog{width:76px; height:21px; background:no-repeat 0px 0px; left:142px; top:9px;} -#launcher_footer .btn.hangame{display:none;} -#launcher_footer p{color:#a0a0a0; font-size:12px; line-height:12px; position:absolute; left:142px; top:14px;} - -#launcher_footer .share{position:absolute; right:36px; top:6px; overflow:hidden; width:136px; height:27px;} -#launcher_footer .share .btn{display:block; background:no-repeat; margin-left:10px; position:relative; float:left; cursor:pointer;} -#launcher_footer .share .btn.tw{width:28px; height:27px; background-position:0px 0px;} -#launcher_footer .share .btn.fb{width:28px; height:27px; background-position:-28px 0px;} -#launcher_footer .share .btn.yt{width:50px; height:21px; margin-top:3px; background-position:-56px -3px;} - -.scroll_bar_box{width:10px; position:absolute;} -.scroll_bar_box_base{width:10px; height:100%; position:absolute; right:0; top:0; border-radius:5px; background:#000; opacity:0.4; -moz-opacity:0.4; filter: alpha(opacity=40); -ms-filter:"alpha(opacity=40)";} -.scroll_bar_box_body{width:6px; position:absolute; right:2px; border-radius:3px; background:#535353; transition:background-color 0.2s linear 0; cursor:pointer; opacity:0.8; -moz-opacity:0.8; filter: alpha(opacity=80); -ms-filter:"alpha(opacity=80)";} -.scroll_bar_box_body:hover, .scroll_bar_box_body.active{background:#FFF; transition:background-color 0.4s linear 0;} -.xboxOnly,.ps3Only,.psvOnly,.wiiuOnly{display:none} -body#cog .cogHide,body#cog .nhnOnly{display:none} -body#hangame .nhnHide,body#hangame .cogOnly{display:none} -.spOnly,.iosOnly,.droidOnly{display:none} -.consoleOnly,.nfXboxOnly,.nfPs3Only,.nfPsVOnly{display:none} -.nintendoOnly,.playstation3Only,.playstation4Only,.playstationVitaOnly{display:none} -*/ \ No newline at end of file diff --git a/www/erupe/img/background.png b/www/erupe/img/background.png deleted file mode 100644 index e080c7d74..000000000 Binary files a/www/erupe/img/background.png and /dev/null differ diff --git a/www/erupe/img/button_charsel.png b/www/erupe/img/button_charsel.png deleted file mode 100644 index 0ee9c5682..000000000 Binary files a/www/erupe/img/button_charsel.png and /dev/null differ diff --git a/www/erupe/img/button_charsel_char.jpg b/www/erupe/img/button_charsel_char.jpg deleted file mode 100644 index aa7f6aeb2..000000000 Binary files a/www/erupe/img/button_charsel_char.jpg and /dev/null differ diff --git a/www/erupe/img/button_charsel_hover.png b/www/erupe/img/button_charsel_hover.png deleted file mode 100644 index bb9414b98..000000000 Binary files a/www/erupe/img/button_charsel_hover.png and /dev/null differ diff --git a/www/erupe/img/button_config.png b/www/erupe/img/button_config.png deleted file mode 100644 index aeba7431c..000000000 Binary files a/www/erupe/img/button_config.png and /dev/null differ diff --git a/www/erupe/img/button_config_hover.png b/www/erupe/img/button_config_hover.png deleted file mode 100644 index fa122297a..000000000 Binary files a/www/erupe/img/button_config_hover.png and /dev/null differ diff --git a/www/erupe/img/button_launch.png b/www/erupe/img/button_launch.png deleted file mode 100644 index 62f65edfd..000000000 Binary files a/www/erupe/img/button_launch.png and /dev/null differ diff --git a/www/erupe/img/button_launch_hover.png b/www/erupe/img/button_launch_hover.png deleted file mode 100644 index e5f897c85..000000000 Binary files a/www/erupe/img/button_launch_hover.png and /dev/null differ diff --git a/www/erupe/img/button_option.png b/www/erupe/img/button_option.png deleted file mode 100644 index a34bfb83a..000000000 Binary files a/www/erupe/img/button_option.png and /dev/null differ diff --git a/www/erupe/img/button_option_hover.png b/www/erupe/img/button_option_hover.png deleted file mode 100644 index 53ab543f6..000000000 Binary files a/www/erupe/img/button_option_hover.png and /dev/null differ diff --git a/www/erupe/img/icons/bow.png b/www/erupe/img/icons/bow.png deleted file mode 100644 index 832b58686..000000000 Binary files a/www/erupe/img/icons/bow.png and /dev/null differ diff --git a/www/erupe/img/icons/db.png b/www/erupe/img/icons/db.png deleted file mode 100644 index 70c0f8ace..000000000 Binary files a/www/erupe/img/icons/db.png and /dev/null differ diff --git a/www/erupe/img/icons/discord.png b/www/erupe/img/icons/discord.png deleted file mode 100644 index e31cb9459..000000000 Binary files a/www/erupe/img/icons/discord.png and /dev/null differ diff --git a/www/erupe/img/icons/github.png b/www/erupe/img/icons/github.png deleted file mode 100644 index 628da97c7..000000000 Binary files a/www/erupe/img/icons/github.png and /dev/null differ diff --git a/www/erupe/img/icons/gl.png b/www/erupe/img/icons/gl.png deleted file mode 100644 index 73274e46d..000000000 Binary files a/www/erupe/img/icons/gl.png and /dev/null differ diff --git a/www/erupe/img/icons/gs.png b/www/erupe/img/icons/gs.png deleted file mode 100644 index 978b5e56a..000000000 Binary files a/www/erupe/img/icons/gs.png and /dev/null differ diff --git a/www/erupe/img/icons/hbg.png b/www/erupe/img/icons/hbg.png deleted file mode 100644 index 2213219a4..000000000 Binary files a/www/erupe/img/icons/hbg.png and /dev/null differ diff --git a/www/erupe/img/icons/hh.png b/www/erupe/img/icons/hh.png deleted file mode 100644 index 8c2b81723..000000000 Binary files a/www/erupe/img/icons/hh.png and /dev/null differ diff --git a/www/erupe/img/icons/hm.png b/www/erupe/img/icons/hm.png deleted file mode 100644 index ed4b39d4e..000000000 Binary files a/www/erupe/img/icons/hm.png and /dev/null differ diff --git a/www/erupe/img/icons/ico_title.png b/www/erupe/img/icons/ico_title.png deleted file mode 100644 index dac853f83..000000000 Binary files a/www/erupe/img/icons/ico_title.png and /dev/null differ diff --git a/www/erupe/img/icons/lbg.png b/www/erupe/img/icons/lbg.png deleted file mode 100644 index 62e710ee1..000000000 Binary files a/www/erupe/img/icons/lbg.png and /dev/null differ diff --git a/www/erupe/img/icons/ln.png b/www/erupe/img/icons/ln.png deleted file mode 100644 index 5648c77e4..000000000 Binary files a/www/erupe/img/icons/ln.png and /dev/null differ diff --git a/www/erupe/img/icons/ls.png b/www/erupe/img/icons/ls.png deleted file mode 100644 index bd83c7bdb..000000000 Binary files a/www/erupe/img/icons/ls.png and /dev/null differ diff --git a/www/erupe/img/icons/ms.png b/www/erupe/img/icons/ms.png deleted file mode 100644 index f50c2f4ec..000000000 Binary files a/www/erupe/img/icons/ms.png and /dev/null differ diff --git a/www/erupe/img/icons/sa.png b/www/erupe/img/icons/sa.png deleted file mode 100644 index f11df9bfa..000000000 Binary files a/www/erupe/img/icons/sa.png and /dev/null differ diff --git a/www/erupe/img/icons/ss.png b/www/erupe/img/icons/ss.png deleted file mode 100644 index 57e6d9b3e..000000000 Binary files a/www/erupe/img/icons/ss.png and /dev/null differ diff --git a/www/erupe/img/icons/tf.png b/www/erupe/img/icons/tf.png deleted file mode 100644 index f626eb292..000000000 Binary files a/www/erupe/img/icons/tf.png and /dev/null differ diff --git a/www/erupe/img/icons/uk.png b/www/erupe/img/icons/uk.png deleted file mode 100644 index 75146b8a2..000000000 Binary files a/www/erupe/img/icons/uk.png and /dev/null differ diff --git a/www/erupe/img/load.gif b/www/erupe/img/load.gif deleted file mode 100644 index 7a22d54c7..000000000 Binary files a/www/erupe/img/load.gif and /dev/null differ diff --git a/www/erupe/img/logo.png b/www/erupe/img/logo.png deleted file mode 100644 index 46b572145..000000000 Binary files a/www/erupe/img/logo.png and /dev/null differ diff --git a/www/erupe/img/manual.png b/www/erupe/img/manual.png deleted file mode 100644 index b1d864c8b..000000000 Binary files a/www/erupe/img/manual.png and /dev/null differ diff --git a/www/erupe/img/modelbg.jpeg b/www/erupe/img/modelbg.jpeg deleted file mode 100644 index e8787206c..000000000 Binary files a/www/erupe/img/modelbg.jpeg and /dev/null differ diff --git a/www/erupe/img/moreinfo.png b/www/erupe/img/moreinfo.png deleted file mode 100644 index 8fe7d8d34..000000000 Binary files a/www/erupe/img/moreinfo.png and /dev/null differ diff --git a/www/erupe/img/pastebin.png b/www/erupe/img/pastebin.png deleted file mode 100644 index 0c7169211..000000000 Binary files a/www/erupe/img/pastebin.png and /dev/null differ diff --git a/www/erupe/img/start_now.png b/www/erupe/img/start_now.png deleted file mode 100644 index 97b5bcb04..000000000 Binary files a/www/erupe/img/start_now.png and /dev/null differ diff --git a/www/erupe/index.html b/www/erupe/index.html deleted file mode 100644 index 0b130c684..000000000 --- a/www/erupe/index.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - Monster Hunter Frontier Z Launcher - - - - -
-
-
-
-
-
__
-
-
- -
-
-

Authenticating...

-
- - - -
-
-
-
Current Weapon
-
-
- -
Add Character
-
Delete Character
-
Log Out
-
Launch
-
-
-
-

-
-
-
-
-
-
-

Important Updates

-
- -
-
-
-
Normal Updates
-
- -
-
-
-
-
-
- -
- -
-

-
-
-
-
-
- - -
-
-
- - - \ No newline at end of file diff --git a/www/erupe/js/script.js b/www/erupe/js/script.js deleted file mode 100644 index 5e75c7aad..000000000 --- a/www/erupe/js/script.js +++ /dev/null @@ -1,540 +0,0 @@ -var __mhf_launcher = {}; -var loginScreen = true; -var loggingIn = false; -var doingAuto = false; -var uids; -var selectedUid; -var firstChar; -var modalState = false; - - -function soundSel() { - window.external.playSound('IDR_WAV_SEL'); -} - -function soundOk() { - window.external.playSound('IDR_WAV_OK'); -} - -function soundPreLogin() { - window.external.playSound('IDR_WAV_PRE_LOGIN'); -} - -function soundLogin() { - window.external.playSound('IDR_WAV_LOGIN'); -} - -function soundNiku() { - window.external.playSound('IDR_NIKU'); -} - -function addLog(text, mode) { - switch (mode) { - case 'winsock': - text = ''+text+'
'; - break; - case 'normal': - text = ''+text+'
'; - break; - case 'good': - text = ''+text+'
'; - break; - case 'error': - text = ''+text+'
'; - break; - } - let logText = document.getElementById('log_p'); - logText.innerHTML = logText.innerHTML + text; - let logBox = document.getElementsByClassName('log_inner')[0]; - logBox.scrollTop = logBox.scrollHeight; -} - -function loadAccount() { - let allowed = localStorage.getItem('saving'); - if (allowed != 'null' && allowed == 'true') { - document.getElementById('username').value = localStorage.getItem('username'); - document.getElementById('password').value = localStorage.getItem('password'); - document.getElementById('login_save').checked = true; - let autoEnabled = localStorage.getItem('autologin'); - if (autoEnabled != 'null' && autoEnabled == 'true') { - doingAuto = true; - doLogin(); - } - } -} - -function saveAccount() { - let checkbox = document.getElementById('login_save'); - if (checkbox.checked == true) { - let username = document.getElementById('username').value; - let password = document.getElementById('password').value; - if (username[username.length - 1] == '+') { - username = username.slice(0, username.length - 1) - } - localStorage.setItem('username', username); - localStorage.setItem('password', password); - localStorage.setItem('saving', 'true'); - } else { - localStorage.removeItem('username'); - localStorage.removeItem('password'); - localStorage.removeItem('saving'); - localStorage.removeItem('uid'); - } -} - -function createCharItem(name, uid, weapon, hr, gr, date, sex) { - var icon; - const dateObject = new Date(date * 1000); - date = dateObject.toLocaleDateString('en-US'); - let dateString = ''; - for (var i = 0; i < date.length; i++) { - if (date[i] != '‎') { // invisible LTR char - dateString += date[i]; - } - } - if (sex == 'M') { - sex = "♂"; - } else { - sex = "♀"; - } - if (hr > 999) { - hr = 999; - } - if (gr > 999) { - gr = 999; - } - switch (weapon) { - case '片手剣': - weapon = 'Sword & Shield'; - icon = 'img/icons/ss.png'; - break; - case '双剣': - weapon = 'Dual Swords'; - icon = 'img/icons/db.png'; - break; - case '大剣': - weapon = 'Greatsword'; - icon = 'img/icons/gs.png'; - break; - case '太刀': - weapon = 'Longsword'; - icon = 'img/icons/ls.png'; - break; - case 'ハンマー': - weapon = 'Hammer'; - icon = 'img/icons/hm.png'; - break; - case '狩猟笛': - weapon = 'Hunting Horn'; - icon = 'img/icons/hh.png'; - break; - case 'ランス': - weapon = 'Lance'; - icon = 'img/icons/ln.png'; - break; - case 'ガンランス': - weapon = 'Gunlance'; - icon = 'img/icons/gl.png'; - break; - case '穿龍棍': - weapon = 'Tonfa'; - icon = 'img/icons/tf.png'; - break; - case 'スラッシュアックスF': - weapon = 'Switch Axe F'; - icon = 'img/icons/sa.png'; - break; - case 'マグネットスパイク': - weapon = 'Magnet Spike'; - icon = 'img/icons/ms.png'; - break; - case 'ヘビィボウガン': - weapon = 'Heavy Bowgun'; - icon = 'img/icons/hbg.png'; - break; - case 'ライトボウガン': - weapon = 'Light Bowgun'; - icon = 'img/icons/lbg.png'; - break; - case '弓': - weapon = 'Bow'; - icon = 'img/icons/bow.png'; - break; - default: - weapon = 'Unknown'; - icon = 'img/icons/uk.png'; - } - let charElem = document.createElement('DIV'); - charElem.setAttribute('href', '#'); - charElem.id = uid; - charElem.classList.add('unit'); - if (firstChar) { - firstChar = false; - selectedUid = uid; - charElem.classList.add('active'); - } - - let elemName = document.createElement('DIV'); - elemName.id = 'char_name'; - elemName.innerHTML = name; - charElem.appendChild(elemName); - let elemWeapon = document.createElement('DIV'); - elemWeapon.id = 'char_weapon'; - elemWeapon.innerHTML = weapon; - charElem.appendChild(elemWeapon); - let elemHr = document.createElement('DIV'); - elemHr.id = 'char_hr'; - elemHr.innerHTML = 'HR'+hr; - charElem.appendChild(elemHr); - let elemGr = document.createElement('DIV'); - elemGr.id = 'char_gr'; - elemGr.innerHTML = 'GR'+gr; - charElem.appendChild(elemGr); - let elemSex = document.createElement('DIV'); - elemSex.id = 'char_sex'; - elemSex.innerHTML = sex; - charElem.appendChild(elemSex); - let elemUid = document.createElement('DIV'); - elemUid.id = 'char_uid'; - elemUid.innerHTML = 'ID: '+uid; - charElem.appendChild(elemUid); - let elemLastLogin = document.createElement('DIV'); - elemLastLogin.id = 'char_login'; - elemLastLogin.innerHTML = 'Last Login: '+dateString; - charElem.appendChild(elemLastLogin); - - let iconElem = document.createElement('IMG'); - iconElem.src = icon; - charElem.appendChild(iconElem); - let unitsElem = document.getElementById('units'); - unitsElem.appendChild(charElem); -} - -function switchPrompt() { - loginScreen = !loginScreen; - if (loginScreen) { - document.getElementById('units').innerHTML = ''; - document.getElementById('char_select').style.display = 'none'; - document.getElementById('login').style.display = 'block'; - } else { // Character selector - document.getElementById('login').style.display = 'none'; - document.getElementById('char_select').style.display = 'block'; - try { - // Example data for browser testing - //var charInfo = ""; - var charInfo = window.external.getCharacterInfo(); - charInfo = charInfo.split("'").join('"'); - charInfo = charInfo.split(''').join("'"); - } catch (e) { - addLog('Error getting character info: '+e, 'error'); - } - try { - firstChar = true; - uids = new Array(); - parser = new DOMParser(); - let xml = parser.parseFromString(charInfo, 'text/xml'); - let numChars = xml.getElementsByTagName('Character').length; - for (var i = 0; i < numChars; i++) { - let char = xml.getElementsByTagName('Character')[i].attributes; - createCharItem( - char.name.value, - char.uid.value, - char.weapon.value, - char.HR.value, - char.GR.value, - char.lastLogin.value, - char.sex.value - ); - uids.push(char.uid.value); - } - } catch (e) { - addLog('Error parsing character info XML: '+e, 'error'); - switchPrompt(); - return; - } - let uid = localStorage.getItem('uid'); - if (uid != 'null' && uids.indexOf(uid) >= 0) { - setUidIndex(uids.indexOf(uid)); - } - } -} - -function doLogin(option) { - if (loggingIn) { - return; - } else { - loggingIn = true; - } - let username = document.getElementById('username').value; - let password = document.getElementById('password').value; - if (username == '') { - addLog('Please enter Erupe ID!', 'error'); - } else if (password == '') { - addLog('Please enter Password!', 'error'); - } else { - document.getElementById('processing').style.display = 'block'; - soundPreLogin(); - addLog('Authenticating...', 'normal'); - try { - if (option) { - addLog('Creating new character...', 'normal'); - window.external.loginCog(username+'+', password, password); - } else { - window.external.loginCog(username, password, 'test'); - } - } catch (e) { - addLog('Error on loginCog: '+e, 'error'); - } - checkAuth(); - } -} - -function checkAuth() { - let loginResult = window.external.getLastAuthResult(); - if (loginResult == 'AUTH_PROGRESS') { - setTimeout(checkAuth, 10); - return; - } else if (loginResult == 'AUTH_SUCCESS') { - loggingIn = false; - saveAccount(); - addLog('Connected.', 'good'); - if (doingAuto) { - let uid = localStorage.getItem('uid'); - window.external.selectCharacter(uid, uid); - window.external.exitLauncher(); - } else { - addLog('After selecting a character, press [Launch]', 'normal'); - switchPrompt(); - } - } else { - loggingIn = false; - addLog('Error logging in: '+loginResult+':'+window.external.getSignResult(), 'error'); - } - document.getElementById('processing').style.display = 'none'; -} - -function checkDelete() { - let deleteResult = window.external.getLastAuthResult(); - if (deleteResult == 'DEL_PROGRESS') { - setTimeout(checkDelete, 10); - return; - } else if (deleteResult == 'DEL_SUCCESS') { - doLogin(0); - switchPrompt(); - toggleModal(0); - } -} - -function launch() { - document.getElementById('game_starting').style.display = 'block'; - try { - window.external.selectCharacter(selectedUid, selectedUid); - } catch (e) { - addLog('Error selecting character: '+e, 'error'); - document.getElementById('game_starting').style.display = 'none'; - } - let allowed = localStorage.getItem('saving'); - if (allowed != 'null' && allowed == 'true') { - localStorage.setItem('uid', selectedUid); - let autoBox = document.getElementById('auto_box'); - if (autoBox.checked) { - localStorage.setItem('autologin', true); - } - } - setTimeout(function () { - window.external.exitLauncher(); - }, 3000); -} - -function deleteCharacter(id) { - window.external.deleteCharacter(id); - checkDelete(); -} - -function autoWarning() { - let autoBox = document.getElementById('auto_box'); - if (autoBox.checked) { - addLog('Auto-Login is for advanced users, to disable it you will need to clear your IE cache. Uncheck the box now if you are not an advanced user.', 'error'); - } -} - -function charselScrollUp() { - let index = uids.indexOf(selectedUid) - 1; - if (index < 0) { - index = uids.length - 1; - } - setUidIndex(index); -} - -function charselScrollDown() { - let index = uids.indexOf(selectedUid) + 1; - if (index == uids.length) { - index = 0; - } - setUidIndex(index); -} - -function setUidIndex(index) { - let units = document.getElementsByClassName('unit'); - let numUnits = units.length; - for (var i = 0; i < numUnits; i++) { - units[i].classList.remove('active'); - } - selectedUid = uids[index]; - document.getElementById(selectedUid).classList.add('active'); -} - -function toggleModal(preset, url) { - let modal = document.getElementById('launcher_modal'); - modalState = !modalState; - if (modalState) { - setModalContent(preset, url); - modal.style.display = 'block'; - } else { - modal.style.display = 'none'; - } -} - -function setModalContent(preset, url) { - let modal = document.getElementById('launcher_modal'); - switch (preset) { - case 'openLink': - modal.querySelector('.dialog p').innerHTML = ' \ - Are you sure you want to open this URL? \ -
\ - '+url+' \ -
\ -
\ - This will open in a browser \ - '; - modal.querySelector('.dialog .btns').innerHTML = ' \ -
    \ -
  • \ -
    Open
    \ -
  • \ -
  • \ -
    Cancel
    \ -
  • \ -
\ - '; - break; - case 'confirmCharDelete': - modal.querySelector('.dialog p').innerHTML = ' \ - Are you sure you want to delete your character? \ -
NAME \ - (ID: 000000) \ -
\ -
\ - You will not be able to recover this character, \ -
it will be gone forever. \ -
\ - '; - modal.querySelector(".dialog .btns").innerHTML = ' \ -
    \ -
  • \ -
    Yes
    \ -
  • \ -
  • \ -
    Cancel
    \ -
  • \ -
\ - '; - break; - case 'addCharNew': - modal.querySelector('.dialog p').innerHTML = ' \ - Are you sure you want to add a new character? \ -
\ -
\ - Press [Add Character] to add a new slot. \ - '; - modal.querySelector('.dialog .btns').innerHTML = ' \ -
    \ -
  • \ -
    Add Character
    \ -
  • \ -
  • \ -
    Cancel
    \ -
  • \ -
\ - '; - break; - default: - return; - } -} - -function charselAdd() { - toggleModal('addCharNew'); -} - -function charselDel() { - toggleModal('confirmCharDelete'); -} - -function charselLog() { - addLog('Disconnected.', 'error'); - addLog('Enter Erupe ID and Password, then press [Log In]', 'normal'); - switchPrompt(); -} - -function doEval() { - try { - addLog(eval(document.getElementById('console').value), 'error'); - } catch (e) { - addLog('Error on doEval: '+e, 'error'); - } -} - -function init() { - document.addEventListener('keypress', function(e) { - switch (e.key) { - case '~': - document.getElementById('dev').style.display = 'block'; - break; - case 'Enter': - if (loginScreen) { - doLogin() - } else { - soundLogin();launch() - } - break; - case ',': - if (!loginScreen) { - soundOk();charselScrollUp() - } - break; - case '.': - if (!loginScreen) { - soundOk();charselScrollDown() - } - } - }); - let unselectable = document.getElementsByClassName('unselectable'); - let unselectableLen = unselectable.length; - for (var i = 0; i < unselectableLen; i++) { - unselectable[i].setAttribute('unselectable', 'on'); - unselectable[i].addEventListener('selectstart', function(){return false;}); - unselectable[i].addEventListener('mouseover', function(){window.external.beginDrag(false);}); - } - let grabbable = document.getElementsByClassName('grabbable'); - let grabbableLen = grabbable.length; - for (var i = 0; i < grabbableLen; i++) { - grabbable[i].addEventListener('selectstart', function(){window.external.beginDrag(true);}); - grabbable[i].addEventListener('mousedown', function(){window.external.beginDrag(true);}); - grabbable[i].addEventListener('mouseup', function(){window.external.beginDrag(true);}); - } - document.getElementById('login_save_text').addEventListener('click', function() { - let checkbox = document.getElementById('login_save'); - checkbox.checked = !checkbox.checked; - }); - document.getElementById('auto_text').addEventListener('click', function() { - let checkbox = document.getElementById('auto_box'); - checkbox.checked = !checkbox.checked; - }); - document.getElementById('username').focus(); - loadAccount(); - addLog('Winsock Ver. [2.2]', 'winsock'); - addLog('Enter Erupe ID and Password, then press [Log In]', 'normal'); -} - -init();